diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d758690..8ac904e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog +MadelineProto can now be proxied. + Added `$no_updates` parameter to the deserialize method of `\danog\MadelineProto\Serialization`. diff --git a/README.md b/README.md index 09b54e7a..3f72cf40 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ $MadelineProto = new \danog\MadelineProto\API(); ### Settings The constructor accepts an optional parameter, which is the settings array. This array contains some other arrays, which are the settings for a specific MadelineProto function. -See [here](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/MTProto.php#L232) for the default values for the settings arrays and explanations for every setting. +See [here](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/MTProto.php#L405) for the default values for the settings arrays and explanations for every setting. You can provide part of any subsetting array, that way the remaining arrays will be automagically set to default and undefined values of specified subsetting arrays will be set to the default values. Example: @@ -272,6 +272,106 @@ array(3) { To specify a custom callback change the correct value in the settings. The specified callable must accept one parameter for the update. +### Using a proxy + +You can use a proxy with MadelineProto. + +To do that, simply create a class that implements the `\danog\MadelineProto\Proxy` interface, and enter its name in the settings. + +Your proxy class MUST use the `\Socket` class for all TCP/UDP communications. + +Your proxy class can also have a setExtra method that accepts an array as the first parameter, to pass the values provided in the proxy_extra setting. + +The `\Socket` class has the following methods (all of the following methods must also be implemented by your proxy class): + + +```public function __construct(int $domain, int $type, int $protocol);``` + +Works exactly like the [socket_connect](http://php.net/manual/en/function.socket-connect.php) function. + + + +```public function setOption(int $level, int $name, $value);``` + +Works exactly like the [socket_set_option](http://php.net/manual/en/function.socket-set-option.php) function. + + + +```public function getOption(int $name, $value);``` + +Works exactly like the [socket_get_option](http://php.net/manual/en/function.socket-get-option.php) function. + + + +```public function setBlocking(bool $blocking);``` + +Works like the [socket_block](http://php.net/manual/en/function.socket-set-block.php) or [socket_nonblock](http://php.net/manual/en/function.socket-set-nonblock.php) functions. + + + +```public function bind(string $address, [ int $port = 0 ]);``` + +Works exactly like the [socket_bind](http://php.net/manual/en/function.socket-bind.php) function. + + + +```public function listen([ int $backlog = 0 ]);``` + +Works exactly like the [socket_listen](http://php.net/manual/en/function.socket-listen.php) function. + + + +```public function accept();``` + +Works exactly like the [socket_accept](http://php.net/manual/en/function.socket-accept.php) function. + + + +```public function connect(string $address, [ int $port = 0 ]);``` + +Works exactly like the [socket_accept](http://php.net/manual/en/function.socket-connect.php) function. + + + +```public function select(array &$read, array &$write, array &$except, int $tv_sec, int $tv_usec = 0);``` + +Works exactly like the [socket_select](http://php.net/manual/en/function.socket-select.php) function. + + + +```public function read(int $length, [ int $flags = 0 ]);``` + +Works exactly like the [socket_read](http://php.net/manual/en/function.socket-read.php) function. + + + +```public function write(string $buffer, [ int $length ]);``` + +Works exactly like the [socket_read](http://php.net/manual/en/function.socket-write.php) function. + + + +```public function send(string $data, int $length, int $flags);``` + +Works exactly like the [socket_send](http://php.net/manual/en/function.socket-send.php) function. + + + +```public function close();``` + +Works exactly like the [socket_close](http://php.net/manual/en/function.socket-close.php) function. + + +```public function getPeerName(bool $port = true);``` + +Works like [socket_getpeername](http://php.net/manual/en/function.socket-getpeername.php): the difference is that it returns an array with the `host` and the `port`. + + +```public function getSockName(bool $port = true);``` + +Works like [socket_getsockname](http://php.net/manual/en/function.socket-getsockname.php): the difference is that it returns an array with the `host` and the `port`. + + ### Uploading and downloading files MadelineProto provides wrapper methods to upload and download files that support bot API file ids. diff --git a/docs/API_docs/constructors/documentAttributeAudio_46.md b/docs/API_docs/constructors/documentAttributeAudio_46.md new file mode 100644 index 00000000..2fed3727 --- /dev/null +++ b/docs/API_docs/constructors/documentAttributeAudio_46.md @@ -0,0 +1,37 @@ +--- +title: documentAttributeAudio +description: documentAttributeAudio attributes, type and example +--- +## Constructor: documentAttributeAudio\_46 +[Back to constructors index](index.md) + + + +### Attributes: + +| Name | Type | Required | +|----------|:-------------:|---------:| +|duration|[int](../types/int.md) | Yes| +|title|[string](../types/string.md) | Yes| +|performer|[string](../types/string.md) | Yes| + + + +### Type: [DocumentAttribute](../types/DocumentAttribute.md) + + +### Example: + +``` +$documentAttributeAudio_46 = ['_' => 'documentAttributeAudio', 'duration' => int, 'title' => string, 'performer' => string, ]; +``` + +Or, if you're into Lua: + + +``` +documentAttributeAudio_46={_='documentAttributeAudio', duration=int, title=string, performer=string, } + +``` + + diff --git a/docs/API_docs/constructors/documentAttributeSticker_55.md b/docs/API_docs/constructors/documentAttributeSticker_55.md new file mode 100644 index 00000000..f10216b3 --- /dev/null +++ b/docs/API_docs/constructors/documentAttributeSticker_55.md @@ -0,0 +1,36 @@ +--- +title: documentAttributeSticker +description: documentAttributeSticker attributes, type and example +--- +## Constructor: documentAttributeSticker\_55 +[Back to constructors index](index.md) + + + +### Attributes: + +| Name | Type | Required | +|----------|:-------------:|---------:| +|alt|[string](../types/string.md) | Yes| +|stickerset|[InputStickerSet](../types/InputStickerSet.md) | Yes| + + + +### Type: [DocumentAttribute](../types/DocumentAttribute.md) + + +### Example: + +``` +$documentAttributeSticker_55 = ['_' => 'documentAttributeSticker', 'alt' => string, 'stickerset' => InputStickerSet, ]; +``` + +Or, if you're into Lua: + + +``` +documentAttributeSticker_55={_='documentAttributeSticker', alt=string, stickerset=InputStickerSet, } + +``` + + diff --git a/docs/API_docs/constructors/documentAttributeVideo_66.md b/docs/API_docs/constructors/documentAttributeVideo_66.md new file mode 100644 index 00000000..e20de543 --- /dev/null +++ b/docs/API_docs/constructors/documentAttributeVideo_66.md @@ -0,0 +1,38 @@ +--- +title: documentAttributeVideo +description: documentAttributeVideo attributes, type and example +--- +## Constructor: documentAttributeVideo\_66 +[Back to constructors index](index.md) + + + +### Attributes: + +| Name | Type | Required | +|----------|:-------------:|---------:| +|round\_message|[Bool](../types/Bool.md) | Optional| +|duration|[int](../types/int.md) | Yes| +|w|[int](../types/int.md) | Yes| +|h|[int](../types/int.md) | Yes| + + + +### Type: [DocumentAttribute](../types/DocumentAttribute.md) + + +### Example: + +``` +$documentAttributeVideo_66 = ['_' => 'documentAttributeVideo', 'round_message' => Bool, 'duration' => int, 'w' => int, 'h' => int, ]; +``` + +Or, if you're into Lua: + + +``` +documentAttributeVideo_66={_='documentAttributeVideo', round_message=Bool, duration=int, w=int, h=int, } + +``` + + diff --git a/docs/index.md b/docs/index.md index 9a2bb994..14ef31c4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -123,7 +123,7 @@ $MadelineProto = new \danog\MadelineProto\API(); ### Settings The constructor accepts an optional parameter, which is the settings array. This array contains some other arrays, which are the settings for a specific MadelineProto function. -See [here](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/MTProto.php#L232) for the default values for the settings arrays and explanations for every setting. +See [here](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/MTProto.php#L405) for the default values for the settings arrays and explanations for every setting. You can provide part of any subsetting array, that way the remaining arrays will be automagically set to default and undefined values of specified subsetting arrays will be set to the default values. Example: @@ -276,6 +276,106 @@ array(3) { To specify a custom callback change the correct value in the settings. The specified callable must accept one parameter for the update. +### Using a proxy + +You can use a proxy with MadelineProto. + +To do that, simply create a class that implements the `\danog\MadelineProto\Proxy` interface, and enter its name in the settings. + +Your proxy class MUST use the `\Socket` class for all TCP/UDP communications. + +Your proxy class can also have a setExtra method that accepts an array as the first parameter, to pass the values provided in the proxy_extra setting. + +The `\Socket` class has the following methods (all of the following methods must also be implemented by your proxy class): + + +```public function __construct(int $domain, int $type, int $protocol);``` + +Works exactly like the [socket_connect](http://php.net/manual/en/function.socket-connect.php) function. + + + +```public function setOption(int $level, int $name, $value);``` + +Works exactly like the [socket_set_option](http://php.net/manual/en/function.socket-set-option.php) function. + + + +```public function getOption(int $name, $value);``` + +Works exactly like the [socket_get_option](http://php.net/manual/en/function.socket-get-option.php) function. + + + +```public function setBlocking(bool $blocking);``` + +Works like the [socket_block](http://php.net/manual/en/function.socket-set-block.php) or [socket_nonblock](http://php.net/manual/en/function.socket-set-nonblock.php) functions. + + + +```public function bind(string $address, [ int $port = 0 ]);``` + +Works exactly like the [socket_bind](http://php.net/manual/en/function.socket-bind.php) function. + + + +```public function listen([ int $backlog = 0 ]);``` + +Works exactly like the [socket_listen](http://php.net/manual/en/function.socket-listen.php) function. + + + +```public function accept();``` + +Works exactly like the [socket_accept](http://php.net/manual/en/function.socket-accept.php) function. + + + +```public function connect(string $address, [ int $port = 0 ]);``` + +Works exactly like the [socket_accept](http://php.net/manual/en/function.socket-connect.php) function. + + + +```public function select(array &$read, array &$write, array &$except, int $tv_sec, int $tv_usec = 0);``` + +Works exactly like the [socket_select](http://php.net/manual/en/function.socket-select.php) function. + + + +```public function read(int $length, [ int $flags = 0 ]);``` + +Works exactly like the [socket_read](http://php.net/manual/en/function.socket-read.php) function. + + + +```public function write(string $buffer, [ int $length ]);``` + +Works exactly like the [socket_read](http://php.net/manual/en/function.socket-write.php) function. + + + +```public function send(string $data, int $length, int $flags);``` + +Works exactly like the [socket_send](http://php.net/manual/en/function.socket-send.php) function. + + + +```public function close();``` + +Works exactly like the [socket_close](http://php.net/manual/en/function.socket-close.php) function. + + +```public function getPeerName(bool $port = true);``` + +Works like [socket_getpeername](http://php.net/manual/en/function.socket-getpeername.php): the difference is that it returns an array with the `host` and the `port`. + + +```public function getSockName(bool $port = true);``` + +Works like [socket_getsockname](http://php.net/manual/en/function.socket-getsockname.php): the difference is that it returns an array with the `host` and the `port`. + + ### Uploading and downloading files MadelineProto provides wrapper methods to upload and download files that support bot API file ids. diff --git a/src/Socket.php b/src/Socket.php index a6727555..b0360cfb 100644 --- a/src/Socket.php +++ b/src/Socket.php @@ -13,20 +13,13 @@ If not, see . if (!extension_loaded('pthreads')) { class Socket { + private $sock; + public function __construct(int $domain, int $type, int $protocol) { $this->sock = socket_create($domain, $type, $protocol); } - public function setBlocking(bool $blocking) - { - if ($blocking) { - return socket_set_block($this->sock); - } - - return socket_set_nonblock($this->sock); - } - public function setOption(int $level, int $name, $value) { if (in_array($name, [\SO_RCVTIMEO, \SO_SNDTIMEO])) { @@ -41,12 +34,76 @@ if (!extension_loaded('pthreads')) { return socket_get_option($this->sock, $level, $name); } - public function __call($name, $args) + public function setBlocking(bool $blocking) { - $name = 'socket_'.$name; - array_unshift($args, $this->sock); + if ($blocking) { + return socket_set_block($this->sock); + } - return $name(...$args); + return socket_set_nonblock($this->sock); + } + + public function bind(string $address, int $port = 0) + { + return socket_bind($this->sock, $address, $port); + } + + public function listen(int $backlog = 0) + { + return socket_listen($this->sock, $backlog); + } + + public function accept() + { + return socket_accept($this->sock); + } + + public function connect(string $address, int $port = 0) + { + return socket_connect($this->sock, $address, $port); + } + + public function select(array &$read, array &$write, array &$except, int $tv_sec, int $tv_usec = 0) + { + return socket_select($read, $write, $except, $tv_sec, $tv_usec); + } + + public function read(int $length, int $flags = 0) + { + return socket_read($this->sock, $length, $flags); + } + + public function write(string $buffer, int $length = -1) + { + return $length === -1 ? socket_write($this->sock, $buffer) : socket_write($this->sock, $buffer, $Length); + } + + public function send(string $data, int $length, int $flags) + { + return socket_send($data, $length, $flags); + } + + public function close() + { + return socket_close($this->sock); + } + + public function getPeerName(bool $port = true) + { + $address = ''; + $port = 0; + $port ? socket_getpeername($this->sock, $address, $ip) : socket_getpeername($this->sock, $address); + + return $port ? ['host' => $address, 'port' => $port] : ['host' => $address]; + } + + public function getSockName(bool $port = true) + { + $address = ''; + $port = 0; + $port ? socket_getsockname($this->sock, $address, $ip) : socket_getsockname($this->sock, $address); + + return $port ? ['host' => $address, 'port' => $port] : ['host' => $address]; } } } diff --git a/src/danog/MadelineProto/APIFactory.php b/src/danog/MadelineProto/APIFactory.php index 87ba215a..4c9511d7 100644 --- a/src/danog/MadelineProto/APIFactory.php +++ b/src/danog/MadelineProto/APIFactory.php @@ -112,7 +112,9 @@ class APIFactory public function __call($name, $arguments) { $this->API->get_config([], ['datacenter' => $this->API->datacenter->curdc]); + $aargs = isset($arguments[1]) && $this->is_array($arguments[1]) ? $arguments[1] : []; + $aargs['datacenter'] = $this->API->datacenter->curdc; - return method_exists($this->API, $this->namespace.$name) ? $this->API->{$this->namespace.$name}(...$arguments) : $this->API->method_call($this->namespace.$name, (isset($arguments[0]) && $this->is_array($arguments[0])) ? $arguments[0] : [], (isset($arguments[1]) && $this->is_array($arguments[1])) ? $arguments[1] : ['datacenter' => $this->API->datacenter->curdc]); + return method_exists($this->API, $this->namespace.$name) ? $this->API->{$this->namespace.$name}(...$arguments) : $this->API->method_call($this->namespace.$name, (isset($arguments[0]) && $this->is_array($arguments[0])) ? $arguments[0] : [], $aargs); } } diff --git a/src/danog/MadelineProto/Connection.php b/src/danog/MadelineProto/Connection.php index d5015c61..4a451f56 100644 --- a/src/danog/MadelineProto/Connection.php +++ b/src/danog/MadelineProto/Connection.php @@ -39,6 +39,8 @@ class Connection extends \Volatile public $new_outgoing = []; public $max_incoming_id; public $max_outgoing_id; + public $proxy = '\Socket'; + public $extra = []; public $call_queue = []; @@ -51,7 +53,7 @@ var_dump(is_null($this->{$name})); return $this->{$name}; }*/ - public function ___construct($ip, $port, $protocol, $timeout, $ipv6) + public function ___construct($proxy, $extra, $ip, $port, $protocol, $timeout, $ipv6) { // Can use: @@ -68,9 +70,18 @@ var_dump(is_null($this->{$name})); $this->ipv6 = $ipv6; $this->ip = $ip; $this->port = $port; + $this->proxy = $proxy; + $this->extra = $extra; + + if (($has_proxy = $proxy !== '\Socket') && !isset(class_implements($proxy)['\danog\MadelineProto\Proxy'])) { + throw new \danog\MadelineProto\Exception('Invalid proxy class provided!'); + } switch ($this->protocol) { case 'tcp_abridged': - $this->sock = new \Socket($ipv6 ? \AF_INET6 : \AF_INET, \SOCK_STREAM, getprotobyname('tcp')); + $this->sock = new $proxy($ipv6 ? \AF_INET6 : \AF_INET, \SOCK_STREAM, getprotobyname('tcp')); + if ($has_proxy && $this->extra !== []) { + $this->sock->setExtra($this->extra); + } if (!\danog\MadelineProto\Logger::$has_thread) { $this->sock->setOption(\SOL_SOCKET, \SO_RCVTIMEO, $timeout); $this->sock->setOption(\SOL_SOCKET, \SO_SNDTIMEO, $timeout); @@ -82,7 +93,10 @@ var_dump(is_null($this->{$name})); $this->write(chr(239)); break; case 'tcp_intermediate': - $this->sock = new \Socket($ipv6 ? \AF_INET6 : \AF_INET, \SOCK_STREAM, getprotobyname('tcp')); + $this->sock = new $proxy($ipv6 ? \AF_INET6 : \AF_INET, \SOCK_STREAM, getprotobyname('tcp')); + if ($has_proxy && $this->extra !== []) { + $this->sock->setExtra($this->extra); + } $this->sock->setOption(\SOL_SOCKET, \SO_RCVTIMEO, $timeout); $this->sock->setOption(\SOL_SOCKET, \SO_SNDTIMEO, $timeout); if (!$this->sock->connect($ip, $port)) { @@ -97,7 +111,10 @@ var_dump(is_null($this->{$name})); break; case 'tcp_full': - $this->sock = new \Socket($ipv6 ? \AF_INET6 : \AF_INET, \SOCK_STREAM, getprotobyname('tcp')); + $this->sock = new $proxy($ipv6 ? \AF_INET6 : \AF_INET, \SOCK_STREAM, getprotobyname('tcp')); + if ($has_proxy && $this->extra !== []) { + $this->sock->setExtra($this->extra); + } if (!$this->sock->connect($ip, $port)) { throw new Exception("Connection: couldn't connect to socket."); } @@ -113,7 +130,10 @@ var_dump(is_null($this->{$name})); case 'http': case 'https': $this->parsed = parse_url($ip); - $this->sock = new \Socket($ipv6 ? \AF_INET6 : \AF_INET, \SOCK_STREAM, getprotobyname($this->protocol === 'https' ? 'tls' : 'tcp')); + $this->sock = new $proxy($ipv6 ? \AF_INET6 : \AF_INET, \SOCK_STREAM, getprotobyname($this->protocol === 'https' ? 'tls' : 'tcp')); + if ($has_proxy && $this->extra !== []) { + $this->sock->setExtra($this->extra); + } if (!$this->sock->connect($this->parsed['host'], $port)) { throw new Exception("Connection: couldn't connect to socket."); } @@ -158,12 +178,12 @@ var_dump(is_null($this->{$name})); public function close_and_reopen() { $this->__destruct(); - $this->__construct($this->ip, $this->port, $this->protocol, $this->timeout, $this->ipv6); + $this->__construct($this->proxy, $this->extra, $this->ip, $this->port, $this->protocol, $this->timeout, $this->ipv6); } public function __sleep() { - return ['protocol', 'ip', 'port', 'timeout', 'parsed', 'time_delta', 'temp_auth_key', 'auth_key', 'session_id', 'session_out_seq_no', 'session_in_seq_no', 'ipv6', 'incoming_messages', 'outgoing_messages', 'new_incoming', 'new_outgoing', 'max_incoming_id', 'max_outgoing_id']; + return ['proxy', 'extra', 'protocol', 'ip', 'port', 'timeout', 'parsed', 'time_delta', 'temp_auth_key', 'auth_key', 'session_id', 'session_out_seq_no', 'session_in_seq_no', 'ipv6', 'incoming_messages', 'outgoing_messages', 'new_incoming', 'new_outgoing', 'max_incoming_id', 'max_outgoing_id', 'sock']; } public function __wakeup() diff --git a/src/danog/MadelineProto/DataCenter.php b/src/danog/MadelineProto/DataCenter.php index fc8057bc..f570ae66 100644 --- a/src/danog/MadelineProto/DataCenter.php +++ b/src/danog/MadelineProto/DataCenter.php @@ -88,7 +88,7 @@ var_dump(is_null($this->{$name})); } \danog\MadelineProto\Logger::log(['Connecting to DC '.$dc_number.' ('.$test.' server, '.$ipv6.', '.$this->settings[$dc_config_number]['protocol'].')...'], \danog\MadelineProto\Logger::VERBOSE); - $this->sockets[$dc_number] = new Connection($address, $port, $this->settings[$dc_config_number]['protocol'], $this->settings[$dc_config_number]['timeout'], $this->settings[$dc_config_number]['ipv6']); + $this->sockets[$dc_number] = new Connection($this->settings[$dc_config_number]['proxy'], $this->settings[$dc_config_number]['proxy_extra'], $address, $port, $this->settings[$dc_config_number]['protocol'], $this->settings[$dc_config_number]['timeout'], $this->settings[$dc_config_number]['ipv6']); return true; } diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php index f8194b7a..7a0a78f4 100644 --- a/src/danog/MadelineProto/MTProto.php +++ b/src/danog/MadelineProto/MTProto.php @@ -458,6 +458,8 @@ class MTProto extends \Volatile 'test_mode' => false, // decides whether to connect to the main telegram servers or to the testing servers (deep telegram) 'ipv6' => $this->ipv6, // decides whether to use ipv6, ipv6 attribute of API attribute of API class contains autodetected boolean 'timeout' => 2, // timeout for sockets + 'proxy' => '\Socket', // The proxy class to use + 'proxy_extra' => [], // Extra parameters to pass to the proxy class using setExtra ], ], 'app_info' => [ // obtained in https://my.telegram.org @@ -708,7 +710,7 @@ class MTProto extends \Volatile public function getV() { - return 40; + return 42; } public function get_self() diff --git a/src/danog/MadelineProto/Proxy.php b/src/danog/MadelineProto/Proxy.php new file mode 100644 index 00000000..20277cd7 --- /dev/null +++ b/src/danog/MadelineProto/Proxy.php @@ -0,0 +1,46 @@ +. +*/ + +namespace danog\MadelineProto; + +interface Proxy +{ + public function __construct(int $domain, int $type, int $protocol); + + public function setOption(int $level, int $name, $value); + + public function getOption(int $level, int $name); + + public function setBlocking(bool $blocking); + + public function bind(string $address, int $port = 0); + + public function listen(int $backlog = 0); + + public function accept(); + + public function connect(string $address, int $port = 0); + + public function select(array &$read, array &$write, array &$except, int $tv_sec, int $tv_usec = 0); + + public function read(int $length, int $flags = 0); + + public function write(string $buffer, int $length = -1); + + public function send(string $data, int $length, int $flags); + + public function close(); + + public function getPeerName(bool $port = true); + + public function getSockName(bool $port = true); +} diff --git a/src/danog/MadelineProto/TL/TL.php b/src/danog/MadelineProto/TL/TL.php index 7f4157c6..f9c6c101 100644 --- a/src/danog/MadelineProto/TL/TL.php +++ b/src/danog/MadelineProto/TL/TL.php @@ -359,7 +359,7 @@ trait TL $constructorData = $this->constructors->find_by_predicate($predicate, $layer); if ($constructorData === false) { \danog\MadelineProto\Logger::log([$object], \danog\MadelineProto\Logger::FATAL_ERROR); - throw new Exception('Could not extract type'); + throw new Exception('Could not extract type "'.$predicate.'"'); } if ($bare = ($type['type'] != '' && $type['type'][0] === '%')) {