<?php
/**
 * -------------------------------------------------------------------------
 *
 * The module to access the Telegram Bot API.
 *
 * -------------------------------------------------------------------------
 *
 * It implements the following methods:
 *
 *     PUBLIC  setBotToken       ( $token                             )  -->  to provide your bot's token used by to access the API
 *     PUBLIC  hasBotToken       (                                    )  -->  to check if a valid bot token exists
 *     PUBLIC  isError           ( $response                          )  -->  to check if the response contains an error
 *     PUBLIC  extractParam      ( $response, $param[, $def]          )  -->  to extract a parameter from the response
 *     PUBLIC  extractId         ( $response[,         $def]          )  -->  to extract ID from the response
 *     PUBLIC  isAbsolute        ( $file                              )  -->  to check if the file name is an absolute path or a URL
 *     PUBLIC  isUrl             ( $file                              )  -->  to check if the file name is a URL
 *     PUBLIC  buildUrl          ( $method[, $params]                 )  -->  to return an API request URL
 *     PUBLIC  requestApi        ( $method[, $params[, $file, $type]] )  -->  to execute an API request
 *     PUBLIC  escapeForMarkdown ( $text                              )  -->  to prepare text for use in MarkdownV2 style
 *     PUBLIC  escapeForHtml     ( $text                              )  -->  to prepare text for use in HTML style
 *     PUBLIC  sendMessage       ( $chat, $text[, $format]            )  -->  to send a text message to a chat
 *     PUBLIC  deleteMessage     ( $chat, $id                         )  -->  to delete a message or multiple messages from a chat
 *
 * -------------------------------------------------------------------------
 *
 * @package    MimimiFramework
 * @subpackage Modules
 * @license    GPL-2.0
 *             https://opensource.org/license/gpl-2-0/
 * @copyright  2022 MiMiMi Community
 *             https://mimimi.software/
 *
 * -------------------------------------------------------------------------
 */

    /**
     * ---------------------------------------------------------------------
     *
     * Load the class of simplest module.
     *
     * ---------------------------------------------------------------------
     */

    mimimiInclude ( 'Module.php' );

    /**
     * ---------------------------------------------------------------------
     *
     * Declare an appropriate class for the Telegram module by extending it
     * from that module's class.
     *
     * ---------------------------------------------------------------------
     */

    class MimimiTelegram extends MimimiModule {

        /**
         * -----------------------------------------------------------------
         *
         * Specify the access token of your Telegram bot.
         *
         * -----------------------------------------------------------------
         *
         * @access protected
         * @var    string
         *
         * -----------------------------------------------------------------
         */

        protected $botToken = '';

        /**
         * -----------------------------------------------------------------
         *
         * Allows you to specify the token used to access your current bot.
         *
         * -----------------------------------------------------------------
         *
         * Please note that you must call this method once before the first
         * request to the Telegram API. If you then want to switch to another
         * bot, you must call this method again with that bot's token.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   string  $token  The token to be used.
         * @return  void
         *
         * -----------------------------------------------------------------
         */

        public function setBotToken ( $token ) {
            $this->botToken = $token;
        }

        /**
         * -----------------------------------------------------------------
         *
         * Checks if this module has a valid bot token.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @return  bool
         *
         * -----------------------------------------------------------------
         */

        public function hasBotToken ( ) {
            return is_string  (                       $this->botToken )
                && preg_match ( '~^[a-z0-9_:-]+$~ui', $this->botToken );
        }

        /**
         * -----------------------------------------------------------------
         *
         * Checks if the response to an API request contains an error.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   mixed  $response  The response received from an API request.
         * @return  bool
         *
         * -----------------------------------------------------------------
         */

        public function isError ( $response ) {
            return empty ( $response[ 'ok' ] );
        }

        /**
         * -----------------------------------------------------------------
         *
         * Extracts the specific parameter from the response to an API request.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   array   $response  The response received during the previous requestApi() call.
         * @param   string  $param     The name of the parameter you want to extract.
         * @param   mixed   $default   (optional) Default value if there is no such parameter.
         * @return  mixed              The value of that parameter or the default value.
         *
         * -----------------------------------------------------------------
         */

        public function extractParam ( $response, $param, $default = FALSE ) {
            return $this->isError ( $response ) ? $default
                                                : isset ( $response[ 'result' ][ $param ] ) ? $response[ 'result' ][ $param ]
                                                                                            : $default;
        }

        /**
         * -----------------------------------------------------------------
         *
         * Extracts the ID from the response to an API request.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   array  $response  The response received during the previous requestApi() call.
         * @param   mixed  $default   (optional) Default value if there is no ID parameter.
         * @return  mixed             ID or default value.
         *
         * -----------------------------------------------------------------
         */

        public function extractId ( $response, $default = 0 ) {
            return $this->extractParam ( $response, 'message_id', $default );
        }

        /**
         * -----------------------------------------------------------------
         *
         * Checks if the file name is an absolute path or a URL.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   string  $filename  The file name to be checked.
         * @return  bool
         *
         * -----------------------------------------------------------------
         */

        public function isAbsolute ( $filename ) {
            return preg_match ( '~^(https?:)?/~ui', $filename );
        }

        /**
         * -----------------------------------------------------------------
         *
         * Checks if the file name is a URL.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   string  $filename  The file name to be checked.
         * @return  bool
         *
         * -----------------------------------------------------------------
         */

        public function isUrl ( $filename ) {
            return preg_match ( '~^(https?:)?//~ui', $filename );
        }

        /**
         * -----------------------------------------------------------------
         *
         * Returns an API request URL based on the provided parameters.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   string  $method  The name of the API method you want to request.
         * @param   array   $params  (optional) A list of parameters you want to attach to the request as GET-parameters.
         * @return  string
         *
         * -----------------------------------------------------------------
         */

        public function buildUrl ( $method, $params = [ ] ) {
            if ( $params ) {
                foreach ( $params as $key => & $value ) {
                    if ( ! is_numeric ( $value )
                    &&   ! is_string  ( $value ) ) {
                        $value = @ json_encode ( $value );
                    }
                }
                $params = '?' . http_build_query ( $params );
            } else {
                $params = '';
            }
            return 'https://api.telegram.org/bot' . $this->botToken . '/' . $method . $params;
        }

        /**
         * -----------------------------------------------------------------
         *
         * Makes a request to the API.
         *
         * -----------------------------------------------------------------
         *
         * Note that if you want to attach a local file, please specify its
         * name and type using the $filename and $filetype pair. And if you
         * want Telegram to upload your file itself over a remote connection,
         * specify its URL as an element of the $params parameter. That
         * element is usually named one of the following: photo, audio,
         * movie, document, etc.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   string          $method    The name of the API method you want to request.
         *                                     For more details, see https://core.telegram.org/bots/api#available-methods
         * @param   array           $params    (optional) A list of parameters you want to attach to the request as POST-parameters.
         * @param   string          $filename  (optional) The absolute or relative name of a local file that will also be uploaded.
         * @param   string          $filetype  (optional) The type of a local file that will be uploaded (photo, audio, movie, document, etc.).
         * @return  bool|int|array             A response that can take the following values:
         *                                         FALSE   = No connection to the API server.
         *                                                   Or a valid bot token was not provided before calling this method.
         *                                                   Or an error occurred during the CURL operation.
         *                                         NULL    = Bad response.
         *                                         INTEGER = The request failed due to error NUMBER.
         *                                         ARRAY   = A response from the Telegram API:
         *                                                       'ok'          => TRUE or FALSE.
         *                                                       'result'      => The result of the query if successful.
         *                                                       'description' => A human-readable description of the result.
         *                                                       'error_code'  => (may change in the future) An error code.
         *                                                       'parameters'  => (optional) An error code explanation.
         *
         * -----------------------------------------------------------------
         */

        public function requestApi ( $method, $params = [ ], $filename = '', $filetype = '' ) {
            $response = FALSE;
            if ( $this->hasBotToken ( ) ) {
                $url = $this->buildUrl ( $method );
                $handle = @ curl_init ( $url );
                if ( $handle !== FALSE ) {
                    $params = ( array ) $params;
                    if ( $filename
                    &&   $filetype ) {
                        if ( $this->isUrl ( $filename ) ) {
                            $params[ $filetype ] = $filename;
                        } else {
                            $filename = $this->isAbsolute ( $filename )
                                                          ? $filename
                                                          : mimimiBasePath ( $filename, TRUE );
                            $params[ $filetype ] = new CURLFile ( $filename );
                        }
                    }
                    if ( $params ) {
                        @ curl_setopt ( $handle, CURLOPT_POST,       TRUE    );
                        @ curl_setopt ( $handle, CURLOPT_POSTFIELDS, $params );
                    }
                    @ curl_setopt ( $handle, CURLOPT_SSL_VERIFYPEER, FALSE );
                    @ curl_setopt ( $handle, CURLOPT_SSL_VERIFYHOST, FALSE );
                    @ curl_setopt ( $handle, CURLOPT_RETURNTRANSFER, TRUE  );
                    @ curl_setopt ( $handle, CURLOPT_CONNECTTIMEOUT, 5     );
                    @ curl_setopt ( $handle, CURLOPT_TIMEOUT,        10    );
                    $response = @ curl_exec ( $handle );
                    if ( $response !== FALSE) {
                        $info = @ curl_getinfo ( $handle );
                        $code = empty ( $info[ 'http_code' ] ) ? 0
                                                               : $info[ 'http_code' ];
                        if ( is_string ( $response ) ) {
                            try {
                                $response = @ json_decode ( $response, TRUE );
                                if ( empty ( $response ) ) {
                                    $response = intval ( $code );
                                }
                            } catch ( Exception $e ) {
                                $response = NULL;
                            }
                        } else {
                            $response = intval ( $code );
                        }
                    }
                    @ curl_close ( $handle );
                }
            }
            return $response;
        }

        /**
         * -----------------------------------------------------------------
         *
         * Prepares text for use in MarkdownV2.
         *
         * -----------------------------------------------------------------
         *
         * Please note that MakdownV2 has the following syntax:
         *
         *     *bold*
         *     _italic_
         *     __underline__
         *     ~strikethrough~
         *
         *     ||spoiler||
         *
         *     [inline URL](http://www.example.com/)
         *     [inline mention of a user](tg://user?id=123456789)
         *
         *     ![some icon](tg://emoji?id=5368324170671202286)
         *
         *     `inline fixed-width code`
         *
         *     ```
         *     pre-formatted fixed-width code block
         *     ```
         *
         *     ```python
         *     pre-formatted fixed-width code block written in the Python
         *     ```
         *
         *     >standard block quote
         *     >standard block quote
         *
         *     **>expandable block quote
         *     >expandable block quote||
         *
         * So all these characters must be escaped with a slash:
         *
         *     _ * [ ] ( ) ~ ` > # + - = | { } . ! (and \ too)
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   string  $text  The text to be escaped.
         *                         For more details, see https://core.telegram.org/bots/api#markdownv2-style
         * @return  string
         *
         * -----------------------------------------------------------------
         */

        public function escapeForMarkdown ( $text ) {
            return preg_replace ( '~([_\*\[\]\(\)\~`>#\+\-=\|\{\}\.!\\\\])~u', '\\\$1', $text );
        }

        /**
         * -----------------------------------------------------------------
         *
         * Prepares text for use in HTML style.
         *
         * -----------------------------------------------------------------
         *
         * Please note that HTML style has the following syntax:
         *
         *     <b>bold</b>
         *     <strong>bold</strong>
         *
         *     <i>italic</i>
         *     <em>italic</em>
         *
         *     <u>underline</u>
         *     <ins>underline</ins>
         *
         *     <s>strikethrough</s>
         *     <strike>strikethrough</strike>
         *     <del>strikethrough</del>
         *
         *     <span class="tg-spoiler">spoiler</span>
         *     <tg-spoiler>spoiler</tg-spoiler>
         *
         *     <a href="http://www.example.com/">inline URL</a>
         *     <a href="tg://user?id=123456789">inline mention of a user</a>
         *
         *     <tg-emoji emoji-id="5368324170671202286">some icon</tg-emoji>
         *
         *     <code>inline fixed-width code</code>
         *
         *     <pre>
         *         pre-formatted fixed-width code block
         *     </pre>
         *
         *     <pre>
         *         <code class="language-python">
         *             pre-formatted fixed-width code block written in the Python
         *         </code>
         *     </pre>
         *
         *     <blockquote>
         *         standard block quote
         *         standard block quote
         *     </blockquote>
         *
         *     <blockquote expandable>
         *         expandable block quote
         *         expandable block quote
         *     </blockquote>
         *
         * So all these characters must be replaced with an HTML entity:
         *
         *     < > &
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   string  $text  The text to be escaped.
         *                         For more details, see https://core.telegram.org/bots/api#html-style
         * @return  string
         *
         * -----------------------------------------------------------------
         */

        public function escapeForHtml ( $text ) {
            $text = preg_replace ( '~&(?!lt;|gt;|amp;|quot;|\#\d+;|\#x[\da-f]+;)~ui', '&amp;', $text );
            $text = preg_replace ( '~<~u',                                            '&lt;',  $text );
            return  preg_replace ( '~>~u',                                            '&gt;',  $text );
        }

        /**
         * -----------------------------------------------------------------
         *
         * Sends a text message to a Telegram chat.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   string  $chat     The name of the chat to post the message in.
         * @param   string  $message  The text to be posted.
         *                            For more details, see https://core.telegram.org/bots/api#sendmessage
         * @param   string  $format   (optional) Parsing mode name: HTML, MarkdownV2.
         * @return  mixed             Telegram server response. For possible values, see the description of requestApi() method above.
         *
         * -----------------------------------------------------------------
         */

        public function sendMessage ( $chat, $message, $format = 'HTML' ) {
            return $this->requestApi ( 'sendMessage', [ 'chat_id'                  => $chat    ,
                                                        'text'                     => $message ,
                                                        'disable_web_page_preview' => TRUE     ,
                                                        'parse_mode'               => $format  ] );
        }

        /**
         * -----------------------------------------------------------------
         *
         * Deletes a message or multiple messages from a Telegram chat.
         *
         * -----------------------------------------------------------------
         *
         * @public
         * @param   string     $chat  The name of the chat containing the message to be deleted.
         * @param   int|array  $id    INTEGER = The identifier of the message to be deleted.
         *                            ARRAY   = The array of message IDs to be deleted.
         *                            For more details, see https://core.telegram.org/bots/api#deletemessage
         *                                              and https://core.telegram.org/bots/api#deletemessages
         * @return  mixed             Telegram server response. For possible values, see the description of requestApi() method above.
         *
         * -----------------------------------------------------------------
         */

        public function deleteMessage ( $chat, $id ) {
            return is_array ( $id ) ? $this->requestApi ( 'deleteMessages', [ 'chat_id'     => $chat               ,
                                                                              'message_ids' => json_encode ( $id ) ] )
                                    : $this->requestApi ( 'deleteMessage',  [ 'chat_id'     => $chat ,
                                                                              'message_id'  => $id   ] );
        }
    };
