| <?php
/**
 * @author: Joseluis Laso
 * based on the project https://github.com/zyberspace/php-telegram-cli-client
 */
namespace TelegramCliWrapper;
use TelegramCliWrapper\Models\Dialog;
use TelegramCliWrapper\Models\User;
class TelegramCliWrapper
{
    /** @var bool */
    protected $debug = false;
    protected $socket;
    /** @var string */
    protected $errorMessage = null;
    /** @var int */
    protected $errorCode = null;
    /** argument types */
    const PEER_ARG = 1;
    const MSG_ARG = 2;
    const PEER_LIST_ARG = 3;
    const TITLE_ARG = 4;
    const NAME_ARG = 5;
    const SURNAME_ARG = 6;
    const PHONE_ARG = 7;
    const NUMBER_ARG = 8;
    const URL_ARG = 9;
    /**
     * TelegramCliWrapper constructor.
     */
    public function __construct($telegramSocket = "unix:///tmp/tg.sck", $debug = false)
    {
        $this->debug = $debug;
        $this->socket = stream_socket_client($telegramSocket);
        if (false === $this->socket) {
            throw new \Exception(sprintf('Could not connect to socket "%s"', $telegramSocket));
        }
        // need to get dialog list in order that the rest of functions work
        $this->dialog_list();
    }
    public function __destruct()
    {
        fclose($this->socket);
    }
    protected $map = array(
        'status_online' => array(),
        'status_offline' => array(),
        'contact_list' => array(),
        'get_self' => array(),
        'stats' => array(),
        'quit' => array(),
        'send_typing' => array(),
        //sends message to the peer
        'msg' => array(
            'peer' => self::PEER_ARG,
            'msg' => self::MSG_ARG,
        ),
        'del_contact' => array(
            'peer' => self::PEER_ARG,
        ),
        'delete_msg' => array(
            'msg_seqno' => self::MSG_ARG,
        ),
        'delete_history' => array(
            'peer' => self::PEER_ARG,
        ),
        'chat_delete_user' => array(
            'peer' => self::PEER_ARG,
        ),
        'mark_read' => array(
            'peer' => self::PEER_ARG,
        ),
        'dialog_list' => array(),
        'chat_info' => array(
            'peer' => self::PEER_ARG,
        ),
        'user_info' => array(
            'peer' => self::PEER_ARG,
        ),
        'block_user' => array(
            'peer' => self::PEER_ARG,
        ),
        'unblock_user' => array(
            'peer' => self::PEER_ARG,
        ),
        'broadcast' => array(
            'peer_list' => self::PEER_LIST_ARG,
            'msg' => self::MSG_ARG,
        ),
        'create_group_chat' => array(
            'title' => self::TITLE_ARG,
            'peer_list' => self::PEER_LIST_ARG,
        ),
        'rename_chat' => array(
            'peer' => self::PEER_ARG,
            'title' => self::TITLE_ARG,
        ),
        'set_profile_name' => array(
            'name' => self::NAME_ARG,
            'surname' => self::SURNAME_ARG,
        ),
        'send_photo' => array(
            'peer' => self::PEER_ARG,
            'photo' => self::URL_ARG,
        ),
        'add_contact' => array(
            'phone' => self::PHONE_ARG,
            'name' => self::NAME_ARG,
            'surname' => self::SURNAME_ARG,
        ),
        'chat_add_user' => array(
            'chat' => self::NAME_ARG,
            'user' => self::NAME_ARG,
            'num_of_msgs' => self::NUMBER_ARG,
        ),
        'rename_contact' => array(
            'peer' => self::PEER_ARG,
            'name' => self::NAME_ARG,
            'surname' => self::NAME_ARG,
        ),
        'history' => array(
            'peer' => self::PEER_ARG,
            'limit' => self::NUMBER_ARG,
        ),
    );
    /**
     * @param $name
     * @param array $args
     * @return bool|mixed
     * @throws \Exception
     */
    public function __call($name, $args = array())
    {
        $method = trim(strtolower($name));
        $this->debug("Executing command '%s(%s)'\n", $method, implode(",",$args));
        if (isset($this->map[$method])) {
            $methodDef = $this->map[$method];
            if (count($args) != count($methodDef)) {
                throw new \Exception(sprintf("wrong number of parameters passed to '%s'", $method));
            }
            $arguments = array();
            if (count($methodDef)) {
                foreach ($methodDef as $fld => $methodArg) {
                    $this->debug("'%s' => '%s'\n", $fld, $methodArg);
                    switch ($methodArg) {
                        case self::PEER_ARG:
                            $arguments[] = $this->escapePeer(array_shift($args));
                            break;
                        case self::NUMBER_ARG:
                            $arguments[] = intval(array_shift($args));
                            break;
                        case self::PHONE_ARG:
                            $phoneNumber = array_shift($args);
                            $processedPhoneNumber = preg_replace('%[^0-9]%', '', (string)$phoneNumber);
                            if (empty($processedPhoneNumber)) {
                                throw new \Exception("Number '%s' is not a real number", $phoneNumber);
                            }
                            $arguments[] = $processedPhoneNumber;
                            break;
                        case self::PEER_LIST_ARG:
                            $arguments[] = $this->formatPeerList(array_shift($args));
                            break;
                        case self::MSG_ARG:
                        case self::NAME_ARG:
                        case self::SURNAME_ARG:
                        case self::TITLE_ARG:
                            $arguments[] = $this->escapeStringArgument(array_shift($args));
                            break;
                        case self::URL_ARG:
                            $arguments[] = array_shift($args);
                            break;
                    }
                }
            }
            switch ($method) {
                case "chat_delete_user":   // chat_delete_user needs to send twice the user name to be sure of the deletion
                    $a = $arguments[] = array_shift($arguments);
                    $arguments[] = $a;
                    break;
            }
            $result = $this->execCommand($method, $arguments);
            $this->debug("Result of '%s' is '%s'\n", $method, $result);
            return $result;
        } else {
            throw new \Exception(sprintf("Unrecognized '%s' invoked\n", $method));
        }
    }
    protected function debug()
    {
        if (!$this->debug) {
            return;
        }
        $args = func_get_args();
        if (count($args) === 1) {
            print $args[0];
            return;
        }
        $format = array_shift($args);
        foreach ($args as $k=>$arg) {
            if (!is_string($arg)) {
                $args[$k] = print_r($arg, true);
            }
        }
        vprintf($format, $args);
    }
    /**
     * @return string
     */
    protected function getErrorMessage()
    {
        return $this->errorMessage;
    }
    /**
     * @return int
     */
    protected function getErrorCode()
    {
        return $this->errorCode;
    }
    /**
     * @param $method
     * @param array $args
     * @return bool|mixed
     */
    protected function execCommand($method, $args = array())
    {
        $command = $method . ' ' . implode(' ', $args);
        $this->debug("::execCommand:: Executing command '%s'\n", $command);
        fwrite($this->socket, str_replace("\n", '\n', $command) . PHP_EOL);
        $answer = fgets($this->socket); //"ANSWER $bytes" or false if an error occurred
        if (is_string($answer)) {
            if (substr($answer, 0, 7) === 'ANSWER ') {
                $bytes = ((int)substr($answer, 7)) + 1; //+1 because the json-return seems to miss one byte
                if ($bytes > 0) {
                    $bytesRead = 0;
                    $jsonString = '';
                    //Run fread() till we have all the bytes we want
                    //(as fread() can only read a maximum of 8192 bytes from a read-buffered stream at once)
                    do {
                        $jsonString .= fread($this->socket, $bytes - $bytesRead);
                        $bytesRead = strlen($jsonString);
                    } while ($bytesRead < $bytes);
                    $json = json_decode($jsonString);
                    if (!isset($json->error)) {
                        //Reset error-message and error-code
                        $this->errorMessage = null;
                        $this->errorCode = null;
                        //For "status_online" and "status_offline"
                        if (isset($json->result) && $json->result === 'SUCCESS') {
                            return true;
                        }
                        //Return json-object
                        return $json;
                    } else {
                        $this->errorMessage = $json->error;
                        $this->errorCode = $json->error_code;
                    }
                }
            }
        }
        return false;
    }
    protected function escapeStringArgument($argument)
    {
        return '"' . addslashes($argument) . '"';
    }
    protected function escapePeer($peer)
    {
        return str_replace(' ', '_', $peer);
    }
    protected function formatPeerList(array $peerList)
    {
        return implode(' ', array_map(array($this, 'escapePeer'), $peerList));
    }
    protected function formatFileName($fileName)
    {
        return $this->escapeStringArgument(realpath($fileName));
    }
    /**
     * return the list of users with active dialogs
     *
     * @return User[]
     */
    public function getDialogList()
    {
        $dialogList = $this->dialog_list();
        return User::fromArray($dialogList);
    }
    /**
     * return the list of dialogs of the peer passed
     * recover messages mark it as read
     *
     * @param string $peer
     * @return Dialog[]
     */
    public function getHistory($peer, $numMsgs)
    {
        $history = $this->history($peer, $numMsgs);
        return Dialog::fromArray($history);
    }
    /**
     * @return User
     */
    public function whoAmI()
    {
        return new User($this->get_self());
    }
    /**
     * @param string $peer
     * @return User
     */
    public function getUserInfo($peer)
    {
        return new User($this->user_info($peer));
    }
    /**
     * @return User[]
     */
    public function getContactList()
    {
        return User::fromArray($this->contact_list());
    }
    /**
     * @param User $user
     * @return mixed
     */
    public function addContact(User $user)
    {
        return $this->add_contact($user->phone, $user->first_name, $user->last_name);
    }
}
 |