•  


Handling updates (new messages & other events) Link Search Menu Expand Document

Handling updates (new messages & other events)

Update handling can be done in different ways:

Async Event driven

Plugins » are also supported!

Simple example:

<?
php
 declare
(
strict_types
=
1
);


// Simple example bot.
// PHP 8.2.4+ is required.

// Run via CLI (recommended: `screen php bot.php`) or via web.

// To reduce RAM usage, follow these instructions: https://docs.madelineproto.xyz/docs/DATABASE.html

use
 danog\MadelineProto\EventHandler\Attributes\Handler
;

use
 danog\MadelineProto\EventHandler\Message
;

use
 danog\MadelineProto\EventHandler\Plugin\RestartPlugin
;

use
 danog\MadelineProto\EventHandler\SimpleFilter\Incoming
;

use
 danog\MadelineProto\SimpleEventHandler
;


// Load via composer (RECOMMENDED, see https://docs.madelineproto.xyz/docs/INSTALLATION.html#composer-from-scratch)
if
 (
file_exists
(
'vendor/autoload.php'
))
 {

    require_once
 'vendor/autoload.php'
;

}
 else
 {

    // Otherwise download an !!! alpha !!! version of MadelineProto via madeline.php
    if
 (
!
file_exists
(
'madeline.php'
))
 {

        copy
(
'https://phar.madelineproto.xyz/madeline.php'
,
 'madeline.php'
);

    }

    require_once
 'madeline.php'
;

}


class
 BasicEventHandler
 extends
 SimpleEventHandler

{

    // !!! Change this to your username !!!
    public
 const
 ADMIN
 =
 "@me"
;


    /**
     * Get peer(s) where to report errors.
     */

    public
 function
 getReportPeers
()

    {

        return
 [
self
::
ADMIN
];

    }


    /**
     * Returns a set of plugins to activate.
     *
     * See here for more info on plugins: https://docs.madelineproto.xyz/docs/PLUGINS.html
     */

    public
 static
 function
 getPlugins
()
:
 array

    {

        return
 [

            // Offers a /restart command to admins that can be used to restart the bot, applying changes.
            // Make sure to run in a bash while loop when running via CLI to allow self-restarts.
            RestartPlugin
::
class
,

        ];

    }


    /**
     * Handle incoming updates from users, chats and channels.
     */

    #[Handler]
    public
 function
 handleMessage
(
Incoming
&
Message
 $message
)
:
 void

    {

        // Code that uses $message...
        // See the following pages for more examples and documentation:
        // - https://github.com/danog/MadelineProto/blob/v8/examples/bot.php
        // - https://docs.madelineproto.xyz/docs/UPDATES.html
        // - https://docs.madelineproto.xyz/docs/FILTERS.html
        // - https://docs.madelineproto.xyz/
    }

}


BasicEventHandler
::
startAndLoop
(
'bot.madeline'
);


Advanced example:

<?
php
 declare
(
strict_types
=
1
);

/**
 * Example bot.
 *
 * PHP 8.2.4+ is required.
 *
 * Copyright 2016-2020 Daniil Gentili
 * (https://daniil.it)
 * This file is part of MadelineProto.
 * MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 * MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 * You should have received a copy of the GNU General Public License along with MadelineProto.
 * If not, see <http://www.gnu.org/licenses/>.
 *
 * @author    Daniil Gentili <daniil@daniil.it>
 * @copyright 2016-2023 Daniil Gentili <daniil@daniil.it>
 * @license   https://opensource.org/licenses/AGPL-3.0 AGPLv3
 * @link https://docs.madelineproto.xyz MadelineProto documentation
 */


use
 danog\MadelineProto\API
;

use
 danog\MadelineProto\Broadcast\Progress
;

use
 danog\MadelineProto\Broadcast\Status
;

use
 danog\MadelineProto\EventHandler\Attributes\Cron
;

use
 danog\MadelineProto\EventHandler\Attributes\Handler
;

use
 danog\MadelineProto\EventHandler\Filter\FilterCommand
;

use
 danog\MadelineProto\EventHandler\Filter\FilterRegex
;

use
 danog\MadelineProto\EventHandler\Filter\FilterText
;

use
 danog\MadelineProto\EventHandler\Filter\FilterTextCaseInsensitive
;

use
 danog\MadelineProto\EventHandler\Message
;

use
 danog\MadelineProto\EventHandler\Message\ChannelMessage
;

use
 danog\MadelineProto\EventHandler\Message\Service\DialogPhotoChanged
;

use
 danog\MadelineProto\EventHandler\Plugin\RestartPlugin
;

use
 danog\MadelineProto\EventHandler\SimpleFilter\FromAdmin
;

use
 danog\MadelineProto\EventHandler\SimpleFilter\Incoming
;

use
 danog\MadelineProto\EventHandler\SimpleFilter\IsReply
;

use
 danog\MadelineProto\Logger
;

use
 danog\MadelineProto\ParseMode
;

use
 danog\MadelineProto\RemoteUrl
;

use
 danog\MadelineProto\Settings
;

use
 danog\MadelineProto\Settings\Database\Mysql
;

use
 danog\MadelineProto\Settings\Database\Postgres
;

use
 danog\MadelineProto\Settings\Database\Redis
;

use
 danog\MadelineProto\SimpleEventHandler
;

use
 danog\MadelineProto\VoIP
;


use
 function
 Amp\Socket\SocketAddress\fromString
;


// MadelineProto is already loaded
if
 (
class_exists
(
API
::
class
))
 {

    // Otherwise, if a stable version of MadelineProto was installed via composer, load composer autoloader
}
 elseif
 (
file_exists
(
'vendor/autoload.php'
))
 {

    require_once
 'vendor/autoload.php'
;

}
 else
 {

    // Otherwise download an !!! alpha !!! version of MadelineProto via madeline.php
    if
 (
!
file_exists
(
'madeline.php'
))
 {

        copy
(
'https://phar.madelineproto.xyz/madeline.php'
,
 'madeline.php'
);

    }

    require_once
 'madeline.php'
;

}

/**
 * Event handler class.
 *
 * NOTE: ALL of the following methods are OPTIONAL.
 * You can even provide an empty event handler if you want.
 *
 * All properties returned by __sleep are automatically stored in the database.
 */

class
 MyEventHandler
 extends
 SimpleEventHandler

{

    /**
     * @var int|string Username or ID of bot admin
     */

    public
 const
 ADMIN
 =
 "@me"
;
 // !!! Change this to your username !!!

    /**
     * @var array<int, bool>
     */

    private
 array
 $notifiedChats
 =
 [];


    /**
     * Returns a list of names for properties that will be automatically saved to the session database (MySQL/postgres/redis if configured, the session file otherwise).
     */

    public
 function
 __sleep
()
:
 array

    {

        return
 [
'notifiedChats'
];

    }


    /**
     * Get peer(s) where to report errors.
     *
     * @return int|string|array
     */

    public
 function
 getReportPeers
()

    {

        return
 [
self
::
ADMIN
];

    }

    /**
     * Initialization logic.
     */

    public
 function
 onStart
()
:
 void

    {

        $this
->
logger
(
"The bot was started!"
);

        $this
->
logger
(
$this
->
getFullInfo
(
'MadelineProto'
));


        $this
->
sendMessageToAdmins
(
"The bot was started!"
);

    }


    /**
     * Returns a set of plugins to activate.
     */

    public
 static
 function
 getPlugins
()
:
 array

    {

        return
 [

            // Offers a /restart command to admins that can be used to restart the bot, applying changes.
            // Make sure to run in a bash while loop when running via CLI to allow self-restarts.
            RestartPlugin
::
class
,

        ];

    }


    /**
     * This cron function will be executed forever, every 60 seconds.
     */

    #[Cron(period: 60.0)]
    public
 function
 cron1
()
:
 void

    {

        $this
->
sendMessageToAdmins
(
"The bot is online, current time "
.
date
(
DATE_RFC850
)
.
"!"
);

    }


    /**
     * Handle incoming updates from users, chats and channels.
     */

    #[Handler]
    public
 function
 handleMessage
(
Incoming
&
Message
 $message
)
:
 void

    {

        // In this example code, send the "This userbot is powered by MadelineProto!" message only once per chat.
        // Ignore all further messages coming from this chat.
        if
 (
!
isset
(
$this
->
notifiedChats
[
$message
->
chatId
]))
 {

            $this
->
notifiedChats
[
$message
->
chatId
]
 =
 true
;


            $message
->
reply
(

                message
:
 "This userbot is powered by [MadelineProto](https://t.me/MadelineProto)!"
,

                parseMode
:
 ParseMode
::
MARKDOWN

            );

        }

    }


    /**
     * Reposts a media file as a Telegram story.
     */

    #[FilterCommand('story')]
    public
 function
 storyCommand
(
Message
 &
 FromAdmin
 $message
)
:
 void

    {

        if
 (
$this
->
isSelfBot
())
 {

            $message
->
reply
(
"Only users can post Telegram Stories!"
);

            return
;

        }

        $media
 =
 $message
->
getReply
(
Message
::
class
)
?->
media
;

        if
 (
!
$media
)
 {

            $message
->
reply
(
"You should reply to a photo or video to repost it as a story!"
);

            return
;

        }


        $this
->
stories
->
sendStory
(

            peer
:
 'me'
,

            media
:
 $media
,

            caption
:
 "This story was posted using [MadelineProto](https://t.me/MadelineProto)!"
,

            parse_mode
:
 ParseMode
::
MARKDOWN
,

            privacy_rules
:
 [[
'_'
 =>
 'inputPrivacyValueAllowAll'
]]

        );

    }


    /**
     * Automatically sends a comment to all new incoming channel posts.
     */

    #[Handler]
    public
 function
 makeComment
(
Incoming
&
ChannelMessage
 $message
)
:
 void

    {

        if
 (
$this
->
isSelfBot
())
 {

            return
;

        }

        $message
->
getDiscussion
()
->
reply
(

            message
:
 "This comment is powered by [MadelineProto](https://t.me/MadelineProto)!"
,

            parseMode
:
 ParseMode
::
MARKDOWN

        );

    }


    #[FilterCommand('broadcast')]
    public
 function
 broadcastCommand
(
Message
 &
 FromAdmin
 $message
)
:
 void

    {

        // We can broadcast messages to all users with /broadcast
        if
 (
!
$message
->
replyToMsgId
)
 {

            $message
->
reply
(
"You should reply to the message you want to broadcast."
);

            return
;

        }

        $this
->
broadcastForwardMessages
(

            from_peer
:
 $message
->
senderId
,

            message_ids
:
 [
$message
->
replyToMsgId
],

            drop_author
:
 true
,

            pin
:
 true
,

        );

    }


    private
 int
 $lastLog
 =
 0
;

    /**
     * Handles updates to an in-progress broadcast.
     */

    #[Handler]
    public
 function
 handleBroadcastProgress
(
Progress
 $progress
)
:
 void

    {

        if
 (
time
()
 -
 $this
->
lastLog
 >
 5
 ||
 $progress
->
status
 ===
 Status
::
FINISHED
)
 {

            $this
->
lastLog
 =
 time
();

            $this
->
sendMessageToAdmins
((
string
)
 $progress
);

        }

    }


    #[FilterCommand('echo')]
    public
 function
 echoCmd
(
Message
 $message
)
:
 void

    {

        // Contains the arguments of the command
        $args
 =
 $message
->
commandArgs
;


        $message
->
reply
(
$args
[
0
]
 ??
 ''
);

    }


    #[FilterRegex('/.*(mt?proto)[^.]?.*/i')]
    public
 function
 testRegex
(
Incoming
 &
 Message
 $message
)
:
 void

    {

        $message
->
reply
(
"Did you mean to write MadelineProto instead of "
.
$message
->
matches
[
1
]
.
'?'
);

    }


    #[FilterText('test')]
    public
 function
 pingCommand
(
Message
 $message
)
:
 void

    {

        $message
->
reply
(
'test reply'
);

    }


    #[FilterCommand('react')]
    public
 function
 reactCommand
(
Message
&
IsReply
 $message
)
:
 void

    {

        $message
->
getReply
(
Message
::
class
)
->
addReaction
(
'??'
);

    }


    #[FilterCommand('unreact')]
    public
 function
 unreactCommand
(
Message
&
IsReply
 $message
)
:
 void

    {

        $message
->
getReply
(
Message
::
class
)
->
delReaction
(
'??'
);

    }


    #[FilterTextCaseInsensitive('hi')]
    public
 function
 pingCommandCaseInsensitive
(
Message
 $message
)
:
 void

    {

        $message
->
reply
(
'hello'
);

    }


    /**
     * Called when the dialog photo of a chat or channel changes.
     */

    #[Handler]
    public
 function
 logPhotoChanged
(
Incoming
&
DialogPhotoChanged
 $message
)
:
 void

    {

        if
 (
$message
->
photo
)
 {

            $message
->
reply
(
"Nice! Here's a download link for the photo: "
.
$message
->
photo
->
getDownloadLink
());

        }

        // The group photo was deleted
    }


    /**
     * Gets a download link for any file up to 4GB!
     *
     * The bot must be started via web for this command to work.
     *
     * You can also start it via CLI but you'll have to specify a download script URL in the settings: https://docs.madelineproto.xyz/docs/FILES.html#getting-a-download-link-cli-bots.
     */

    #[FilterCommand('dl')]
    public
 function
 downloadLink
(
Incoming
&
Message
 $message
)
:
 void

    {

        $reply
 =
 $message
->
getReply
(
Message
::
class
);

        if
 (
!
$reply
?->
media
)
 {

            $message
->
reply
(
"This command must reply to a media message!"
);

            return
;

        }

        $reply
->
reply
(
"Download link: "
.
$reply
->
media
->
getDownloadLink
());

    }


    #[FilterCommand('call')]
    public
 function
 callVoip
(
Incoming
&
Message
 $message
)
:
 void

    {

        $this
->
requestCall
(
$message
->
senderId
)
->
play
(
new
 RemoteUrl
(
'http://icestreaming.rai.it/1.mp3'
));

    }


    #[Handler]
    public
 function
 handleIncomingCall
(
VoIP
&
Incoming
 $call
)
:
 void

    {

        $call
->
accept
()
->
play
(
new
 RemoteUrl
(
'http://icestreaming.rai.it/1.mp3'
));

    }


    public
 static
 function
 getPluginPaths
()
:
 string
|
array
|
null

    {

        return
 'plugins/'
;

    }

}


$settings
 =
 new
 Settings
;

$settings
->
getLogger
()
->
setLevel
(
Logger
::
LEVEL_ULTRA_VERBOSE
);


// You can also use Redis, MySQL or PostgreSQL.
// Data is migrated automatically.
//
// $settings->setDb((new Redis)->setDatabase(0)->setPassword('pony'));
// $settings->setDb((new Postgres)->setDatabase('MadelineProto')->setUsername('daniil')->setPassword('pony'));
// $settings->setDb((new Mysql)->setDatabase('MadelineProto')->setUsername('daniil')->setPassword('pony'));

// You can also enable collection of additional prometheus metrics.
// $settings->getMetrics()->setEnablePrometheusCollection(true);

// You can also enable collection of additional memory profiling metrics.
// Note: you must also set the MEMPROF_PROFILE=1 environment variable or GET parameter.
// $settings->getMetrics()->setEnableMemprofCollection(true);

// Metrics can be returned by an autoconfigured http://127.0.0.1:12345 HTTP server.
//
// Endpoints:
//
// /metrics - Prometheus metrics
// /debug/pprof - PProf memory profile for pyroscope
//
// $settings->getMetrics()->setMetricsBindTo(fromString("127.0.0.1:12345"));

// Metrics can also be returned by the current script via web, if called with a specific query string:
//
// ?metrics - Prometheus metrics
// ?pprof - PProf memory profile for pyroscope
//
// $settings->getMetrics()->setReturnMetricsFromStartAndLoop(true);

// For users or bots
MyEventHandler
::
startAndLoop
(
'bot.madeline'
,
 $settings
);


// For bots only
// MyEventHandler::startAndLoopBot('bot.madeline', 'bot token', $settings);

The example code above defines an event handler class MyEventHandler , creates a MadelineProto session, and sets the event handler class to our newly created event handler.

The new startAndLoop method automatically initializes MadelineProto, enables async , logs in the user/bot, initializes error reporting, catches and reports all errors surfacing from the event loop to the peers returned by the getReportPeers method.

All events are handled concurrently thanks to async, here’s a full explanation .

All incoming events are always handled , including old events that occurred while the script was turned off .

To access the $MadelineProto instance inside of the event handler, simply access $this :

$this
->
messages
->
sendMessage
([
'peer'
 =>
 '@danogentili'
,
 'message'
 =>
 'hi'
]);

Bound methods

MadelineProto offers a large number of helper bound methods and properties, depending on the filter type you specify in the typehint of #[Handler] methods.

See here » for more info on how to use bound methods, properties and filters.

Here’s a full list of the concrete object types on which bound methods and properties are defined:

Filters

MadelineProto offers three different filter types, used to filter updates by type or other attributes, click on the following links for more info:

Plugins

Plugins are also supported, check out the plugin docs » for more info!

Cron

All event handler methods marked by the danog\MadelineProto\EventHandler\Attributes\Cron attribute are periodically invoked by MadelineProto every period seconds:

use
 danog\MadelineProto\EventHandler\Attributes\Cron
;


class
 MyEventHandler
 extends
 SimpleEventHandler

{

    /**
     * This cron function will be executed forever, every 60 seconds.
     */

    #[Cron(period: 60.0)]
    public
 function
 cron1
()
:
 void

    {

        $this
->
sendMessageToAdmins
(
"The bot is online, current time "
.
date
(
DATE_RFC850
)
.
"!"
);

    }

}

You can also specify millisecond intervals like 0.5 (500 milliseconds).

MadelineProto’s crons are based on the danog/loop library: the associated PeriodicLoop instance that can be used to stop or restart the loop is passed as first parameter to the cron, and can also be fetched using $this->getPeriodicLoop('methodName') , in this case $this->getPeriodicLoop('cron1') .

Persisting data and IPC

All property names returned by the __sleep method will be saved in the database/session file, and then automatically loaded when the bot is restarted.

<?
php
 declare
(
strict_types
=
1
);


namespace
 MadelinePlugin\Danogentili
;


use
 danog\MadelineProto\EventHandler\Attributes\Cron
;

use
 danog\MadelineProto\EventHandler\Filter\FilterText
;

use
 danog\MadelineProto\EventHandler\Message
;

use
 danog\MadelineProto\EventHandler\SimpleFilter\Incoming
;

use
 danog\MadelineProto\PluginEventHandler
;


/**
 * Plugin event handler class.
 *
 * All properties returned by __sleep are automatically stored in the database.
 */

class
 PingPlugin
 extends
 PluginEventHandler

{

    private
 int
 $pingCount
 =
 0
;


    private
 string
 $pongText
 =
 'pong'
;


    /**
     * You can set a custom pong text from the outside of the plugin:.
     *
     * ```
     * if (!file_exists('madeline.php')) {
     *     copy('https://phar.madelineproto.xyz/madeline.php', 'madeline.php');
     * }
     * include 'madeline.php';
     *
     * $a = new API('bot.madeline');
     * $plugin = $a->getPlugin(PingPlugin::class);
     *
     * $plugin->setPongText('UwU');
     * ```
     *
     * This will automatically connect to the running instance of the plugin and call the specified method.
     */

    public
 function
 setPongText
(
string
 $pong
)
:
 void

    {

        $this
->
pongText
 =
 $pong
;

    }


    /**
     * Returns a list of names for properties that will be automatically saved to the session database (MySQL/postgres/redis if configured, the session file otherwise).
     */

    public
 function
 __sleep
()
:
 array

    {

        return
 [
'pingCount'
,
 'pongText'
];

    }

    /**
     * Initialization logic.
     */

    public
 function
 onStart
()
:
 void

    {

        $this
->
logger
(
"The bot was started!"
);

        $this
->
logger
(
$this
->
getFullInfo
(
'MadelineProto'
));


        $this
->
sendMessageToAdmins
(
"The bot was started!"
);

    }


    /**
     * Plugins may be enabled or disabled at startup by returning true or false from this function.
     */

    public
 function
 isPluginEnabled
()
:
 bool

    {

        return
 true
;

    }


    /**
     * This cron function will be executed forever, every 60 seconds.
     */

    #[Cron(period: 60.0)]
    public
 function
 cron1
()
:
 void

    {

        $this
->
sendMessageToAdmins
(
"The ping plugin is online, total pings so far: "
.
$this
->
pingCount
);

    }


    #[FilterText('ping')]
    public
 function
 pingCommand
(
Incoming
&
Message
 $message
)
:
 void

    {

        $message
->
reply
(
$this
->
pongText
);

        $this
->
pingCount
++
;

    }

}


You can read and write to those properties from the outside using getter and setter methods, for example:

use
 danog\MadelineProto\API
;

use
 MadelinePlugin\Danogentili\PingPlugin
;


if
 (
!
file_exists
(
'madeline.php'
))
 {

     copy
(
'https://phar.madelineproto.xyz/madeline.php'
,
 'madeline.php'
);

}

include
 'madeline.php'
;


$a
 =
 new
 API
(
'bot.madeline'
);


$handler
 =
 $a
->
getEventHandler
(
PingPlugin
::
class
);


$handler
->
setPongText
(
'UwU'
);

Built-in ORM

You can also directly connect to any database using the same async MySQL/Postgres/Redis ORM used by MadelineProto internally, danog/AsyncOrm !

To do so, simply specify the database settings , and use the OrmMappedArray attribute to initialize the async database mapper:


use
 danog\AsyncOrm\Annotations\OrmMappedArray
;

use
 danog\AsyncOrm\DbArray
;

use
 danog\AsyncOrm\KeyType
;

use
 danog\AsyncOrm\ValueType
;

use
 danog\MadelineProto\SimpleEventHandler
;


class
 MyEventHandler
 extends
 SimpleEventHandler
 {

    /**
     * @var DbArray<string, int>
     * 
     * This ORM property is also persisted to the database, and is *not* fully kept in RAM at all times.
     * 
     * You can also provide more specific type parameters (i.e. <string, int>; <int, someClass> etc),
     * as well as custom caching settings.  
     * 
     * See https://github.com/danog/AsyncOrm for full documentation and more examples.  
     */

    #[OrmMappedArray(KeyType::STRING, ValueType::INT)]
    protected
 DbArray
 $ormProperty
;


    /**
     * This raw property is also persisted to the database, but is always kept in RAM at all times.
     */

    private
 array
 $rawProperty
 =
 [];


    /**
     * Returns a list of names for properties that will be automatically saved to the session database (MySQL/postgres/redis if configured, the session file otherwise).
     */

    public
 function
 __sleep
()
:
 array

    {

        return
 [
'ormProperty'
,
 'rawProperty'
];

    }

    // ...
}

And use the newly created $dataStoredOnDb property to access the database:

// Can be anything serializable, an array, an int, an object
$myData
 =
 [];


// Use the isset method to check whether some data exists in the database
if
 (
isset
(
$this
->
dataStoredOnDb
[
'yourKey'
]))
 {

    // Always when fetching data
    $myData
 =
 $this
->
dataStoredOnDb
[
'yourKey'
];

}

$this
->
dataStoredOnDb
[
'yourKey'
]
 =
 123
;


$this
->
dataStoredOnDb
[
'otherKey'
]
 =
 0
;

unset
(
$this
->
dataStoredOnDb
[
'otherKey'
]);


$this
->
logger
(
"Count: "
.
count
(
$this
->
dataStoredOnDb
));


foreach
 (
$this
->
dataStoredOnDb
 as
 $key
 =>
 $value
)
 {

    $this
->
logger
(
$key
);

    $this
->
logger
(
$value
);

}

Psalm generic typing is supported.

Each element of the array is stored in a separate database row (MySQL, Postgres or Redis, configured as specified here » ), and is only kept in memory for the number of seconds specified in the cache TTL setting; when the TTL of an element expires, it is individually flushed to the database (if its value was changed), and then the row is removed from RAM.

Pros of using ORM DbArray properties instead of raw properties:

  • Much lower RAM usage, as the entire array is not kept in RAM at all times, only the most frequently used elements, according to the configured TTL.
  • Added possibility of storing even gigabytes of data in a single DbArray , without keeping it all in memory.
  • If caching is disabled, the array is never kept in RAM, significantly hindering performance but further reducing RAM usage for truly huge elements (gigabyte-level).

Cons of using ORM DbArray properties:

  • Reads and writes are not atomic. Since each handler is started in a concurrent green thread (fiber), race conditions may ensue, thus accesses must be syncronized where and if needed using amphp/sync .
  • Slower than raw properties ( much slower if caching is fully disabled).

Both raw properties and ORM DbArray properties are ultimately persisted on the database.

If no database is configured in the global settings, ORM properties behave pretty much like raw array properties, kept entirely in RAM and persisted to the session file.

IPC

You can communicate with the event handler from the outside, by invoking methods on the proxy returned by getEventHandler:

bot.php:

<?
php
 declare
(
strict_types
=
1
);


use
 danog\MadelineProto\EventHandler\Attributes\Handler
;

use
 danog\MadelineProto\EventHandler\Message
;

use
 danog\MadelineProto\EventHandler\Plugin\RestartPlugin
;

use
 danog\MadelineProto\EventHandler\SimpleFilter\Incoming
;

use
 danog\MadelineProto\SimpleEventHandler
;


if
 (
!
file_exists
(
'madeline.php'
))
 {

     copy
(
'https://phar.madelineproto.xyz/madeline.php'
,
 'madeline.php'
);

}

include
 'madeline.php'
;


final
 class
 MyEventHandler
 extends
 SimpleEventHandler
 {


    public
 function
 someMethod
()
:
 string
 {

        return
 "Some data"
;

    }



    /**
     * Handle incoming updates from users, chats and channels.
     */

    #[Handler]
    public
 function
 handleMessage
(
Incoming
&
Message
 $message
)
:
 void

    {

        // Code that uses $message...
        // See the following pages for more examples and documentation:
        // - https://github.com/danog/MadelineProto/blob/v8/examples/bot.php
        // - https://docs.madelineproto.xyz/docs/UPDATES.html
        // - https://docs.madelineproto.xyz/docs/FILTERS.html
        // - https://docs.madelineproto.xyz/
    }

}


MyEventHandler
::
startAndLoop
(
'bot.madeline'
);

script.php:

use
 danog\MadelineProto\API
;


if
 (
!
file_exists
(
'madeline.php'
))
 {

     copy
(
'https://phar.madelineproto.xyz/madeline.php'
,
 'madeline.php'
);

}

include
 'madeline.php'
;


$a
 =
 new
 API
(
'bot.madeline'
);


$handler
 =
 $a
->
getEventHandler
(
MyEventHandler
::
class
);


$handler
->
someMethod
();

Restarting

To forcefully restart and apply changes made to the event handler class, call $this->restart(); .

When running via cli, the bot must run in the official docker image with restart: always or inside of a bash while true loop in order for restart() to work.

Self-restart on webhosts

When running the event handler via web, MadelineProto will automatically enable a magical self-restart hack (callback ID restarter ), to keep the bot running even on webhosts with limited execution time.

Locking will also be handled automatically (as well as disconnection from the user that opened the page), so even if you start the script via web several times, only one instance will be running at a time (no need to do flocking manually!).

Please note that this self-restart logic may fail in case of a physical server reboot or web server/php-fpm restart, so it’s always a better idea to run via CLI, or use a cron to periodically ping the bot’s URL.

It relies on the shutdown function, so you must not set a custom shutdown function in your code, and instead use the MadelineProto shutdown static API :

use
 danog\MadelineProto\Shutdown
;


$id
 =
 Shutdown
::
addCallback
(
static
 function
 ()
 {

    // This function will run on shutdown
});


$id
 =
 Shutdown
::
addCallback
(
static
 function
 ()
 {

    // This function will run on shutdown
},
 'custom id'
);


$id
 =
 Shutdown
::
addCallback
(
static
 function
 ()
 {

    // This function will overwrite the previously set function with custom id
},
 'custom id'
);


$ok
 =
 Shutdown
::
removeCallback
(
$id
);

You can of course pass non-static functions, any type of callable is accepted.
A second optional parameter can also be accepted, containing the ID of the callable: you can use this if you want to later overwrite the callable with another callback, or remove it altogether.

The removeCallback will return true if the callback exists and it was removed correctly, false otherwise.

Multiaccount

use
 danog\MadelineProto\EventHandler
;

use
 danog\MadelineProto\Tools
;

use
 danog\MadelineProto\API
;

use
 danog\MadelineProto\Logger
;


// Normal event handler definition as above

$MadelineProtos
 =
 [];

foreach
 ([

    'bot.madeline'
 =>
 'Bot Login'
,

    'user.madeline'
 =>
 'Userbot login'
,

    'user2.madeline'
 =>
 'Userbot login (2)'

]
 as
 $session
 =>
 $message
)
 {

    $MadelineProtos
 []
=
 new
 API
(
$session
);

}


API
::
startAndLoopMulti
(
$MadelineProtos
,
 MyEventHandler
::
class
);

This will create an event handler class EventHandler , create a combined MadelineProto session with session files bot.madeline , user.madeline , user2.madeline , and set the event handler class to our newly created event handler.

Usage is the same as for the normal event handler , with the difference is that multiple accounts can receive and handle updates in parallel, each with its own event handler instance.

Errors thrown inside of the event loop will be reported to the report peers specified by each separate instance.

Note that for performance reasons, some internal or connection exceptions not thrown from the EventHandler and exceptions thrown from onStart may still get reported (only to, or also to) the last started event handler.

To dynamically start a new event handler in the background, use EventLoop::queue(MyEventHandler::startAndLoop(...), 'session.madeline', $settings)) .

Warning : this can only be done with already logged-in sessions, if your sessions aren’t logged in yet use startAndLoopMulti , or login first.

use
 danog\MadelineProto\EventHandler
;

use
 danog\MadelineProto\Tools
;

use
 Revolt\EventLoop
;


// Normal event handler definition as above

foreach
 ([

    'bot.madeline'
 =>
 'Bot Login'
,

    'user.madeline'
 =>
 'Userbot login'
,

    'user2.madeline'
 =>
 'Userbot login (2)'

]
 as
 $session
 =>
 $message
)
 {

    EventLoop
::
queue
(
MyEventHandler
::
startAndLoop
(
...
),
 $session
);

}


EventLoop
::
run
();
 // Or continue using some other async code...

Automatic static analysis

MadelineProto will automatically analyze the event handler code, blocking execution if performance or security issues are detected!

For example, the following functions and classes are banned , and the specified async counterparts must be used, instead:

Avoiding the use of filesystem functions

For performance reasons, it is heavily recommended you do not read files from the filesystem at all, even using async functions.

MadelineProto does not block the usage of async file functions, but 99% of the time they can be replaced with a much faster alternative.

Here’s a list of common uses for files, and what they can be replaced with:

Configuration

Configuration can be done entirely using persistent properties, for example DON’T do this:

<?
php


class
 OnlinePlugin
 extends
 PluginEventHandler

{

    #[Cron(period: 60.0)]
    public
 function
 cron
()
:
 void

    {

        // WRONG!
        if
 (
file_get_contents
(
'online.txt'
)
 ===
 'on'
)
 {

            $this
->
account
->
updateStatus
(
offline
:
 false
);

        }
 else
 {

            $this
->
account
->
updateStatus
(
offline
:
 true
);

        }

    }

}

Do this, instead:

<?
php
 declare
(
strict_types
=
1
);


namespace
 MadelinePlugin\Danogentili
;


use
 danog\MadelineProto\EventHandler\Attributes\Cron
;

use
 danog\MadelineProto\EventHandler\Filter\FilterCommand
;

use
 danog\MadelineProto\EventHandler\Message
;

use
 danog\MadelineProto\EventHandler\SimpleFilter\FromAdmin
;

use
 danog\MadelineProto\EventHandler\SimpleFilter\Incoming
;

use
 danog\MadelineProto\PluginEventHandler
;


final
 class
 OnlinePlugin
 extends
 PluginEventHandler

{

    private
 bool
 $isOnline
 =
 true
;


    /**
     * Returns a list of names for properties that will be automatically saved to the session database (MySQL/postgres/redis if configured, the session file otherwise).
     */

    public
 function
 __sleep
()
:
 array

    {

        return
 [
'isOnline'
];

    }


    public
 function
 setOnline
(
bool
 $online
)
:
 void

    {

        $this
->
isOnline
 =
 $online
;

    }


    public
 function
 isPluginEnabled
()
:
 bool

    {

        // Only users can be online/offline
        return
 $this
->
getSelf
()[
'bot'
]
 ===
 false
;

    }


    #[Cron(period: 60.0)]
    public
 function
 cron
()
:
 void

    {

        $this
->
account
->
updateStatus
(
offline
:
 !
$this
->
isOnline
);

    }


    #[FilterCommand('online')]
    public
 function
 toggleOnline
(
Incoming
&
Message
&
FromAdmin
 $message
)
:
 void

    {

        $this
->
isOnline
 =
 true
;

    }


    #[FilterCommand('offline')]
    public
 function
 toggleOffline
(
Incoming
&
Message
&
FromAdmin
 $message
)
:
 void

    {

        $this
->
isOnline
 =
 false
;

    }

}


And, to toggle the settings from the outside of the bot (for example using a helper bot, or another program):

<?
php


$online
 =
 true
;

//$online = false;

$API
 =
 new
 \danog\MadelineProto\API
(
'session.madeline'
);

$API
->
getEventHandler
(
\MadelinePlugin\Danogentili\OnlinePlugin
::
class
)
->
setOnline
(
$online
);

Creating and uploading text files

Instead of writing to a file and then uploading it, you can use a ReadableBuffer to upload a file from a string, instead:

use
 Amp\ReadableBuffer
;


$contents
 =
 "Something"
;


$this
->
sendDocument
(

    peer
:
 'danogentili'
,

    file
:
 new
 ReadableBuffer
(
$contents
)

);

Logging

Instead of logging to separate files, you can use MadelineProto’s built-in logger, which will write everything to MadelineProto.log :

$this
->
logger
(
"Some text"
);

You can also use the new openFileAppendOnly function, to open a file in write-only append-only mode in onStart and use it in your bot.
You may also wrap the File resource returned by openFileAppendOnly in a proper PSR logger using amphp/log .

use
 danog\MadelineProto\SimpleEventHandler
;

use
 danog\MadelineProto\Tools
;

use
 Amp\Log\ConsoleFormatter
;

use
 Amp\Log\StreamHandler
;

use
 Monolog\Logger
;


class
 MyEventHandler
 extends
 SimpleEventHandler
 {

    private
 Logger
 $customLogger
;

    public
 function
 onStart
()

    {

        // As documented in https://github.com/amphp/log
        $handler
 =
 new
 StreamHandler
(
Tools
::
openFileAppendOnly
(
'file.log'
));


        $this
->
customLogger
 =
 new
 Logger
(
'main'
);

        $this
->
customLogger
->
pushHandler
(
$handler
);

    }


    public
 function
 someOtherMethod
()
:
 void
 {

        $this
->
customLogger
->
debug
(
"Hello, world!"
);

        $this
->
customLogger
->
info
(
"Hello, world!"
);

        $this
->
customLogger
->
notice
(
"Hello, world!"
);

        $this
->
customLogger
->
error
(
"Hello, world!"
);

        $this
->
customLogger
->
alert
(
"Hello, world!"
);

    }

}

Noop

$MadelineProto
 =
 new
 \danog\MadelineProto\API
(
'bot.madeline'
);


$MadelineProto
->
start
();

$MadelineProto
->
setNoop
();

When an Update is received, nothing is done. This is useful if you need to populate the internal peer database with peers to avoid This peer is not present in the internal peer database errors , but don’t need to handle updates.
This is the default.

Webhook

Useful when consuming MadelineProto updates through an API, not recommended when directly writing MadelineProto bots .

Webhooks will greatly slow down your bot if used directly inside of PHP code.

Only use the event handler when writing a MadelineProto bot , because update handling in the event handler is completely parallelized and non-blocking.

Webhooks must only be used when consuming MadelineProto updates from another programming language, like for example Javascript .

If your bot is written in PHP, use the event handler, instead .

$MadelineProto
 =
 new
 \danog\MadelineProto\API
(
'bot.madeline'
);


// NOT recommended when directly writing MadelineProto bots.
// ONLY use when exposing updates via an HTTP API to another language (like Javascript).

$MadelineProto
->
setWebhook
(
'https://example.com'
);

getUpdates

Only useful when consuming MadelineProto updates through an API in another language (like Javascript), absolutely not recommended when directly writing MadelineProto bots .

getUpdates will greatly slow down your bot if used directly inside of PHP code.

Only use the event handler when writing a MadelineProto bot , because update handling in the event handler is completely parallelized and non-blocking.

getUpdates must only be used when consuming MadelineProto updates from another programming language, like for example Javascript .

If your bot is written in PHP, use the event handler, instead .

$MadelineProto
 =
 new
 \danog\MadelineProto\API
(
'bot.madeline'
);


// NOT recommended when directly writing MadelineProto bots.
// ONLY use when exposing updates via an HTTP API to another language (like Javascript).

// DO NOT use this to handle updates in PHP code, it will cause crashes.

// Same parameters as for bot API getUpdates
echo
 json_encode
(
$MadelineProto
->
getUpdates
(
$_GET
));

Next section


- "漢字路" 한글한자자동변환 서비스는 교육부 고전문헌국역지원사업의 지원으로 구축되었습니다.
- "漢字路" 한글한자자동변환 서비스는 전통문화연구회 "울산대학교한국어처리연구실 옥철영(IT융합전공)교수팀"에서 개발한 한글한자자동변환기를 바탕하여 지속적으로 공동 연구 개발하고 있는 서비스입니다.
- 현재 고유명사(인명, 지명등)을 비롯한 여러 변환오류가 있으며 이를 해결하고자 많은 연구 개발을 진행하고자 하고 있습니다. 이를 인지하시고 다른 곳에서 인용시 한자 변환 결과를 한번 더 검토하시고 사용해 주시기 바랍니다.
- 변환오류 및 건의,문의사항은 juntong@juntong.or.kr로 메일로 보내주시면 감사하겠습니다. .
Copyright ⓒ 2020 By '전통문화연구회(傳統文化硏究會)' All Rights reserved.
 한국   대만   중국   일본