Reorganizing documentation, rewrote peer management

This commit is contained in:
Daniil Gentili 2018-03-12 21:10:49 +00:00
parent 0d2ed128ac
commit 071be3bb80
18 changed files with 1766 additions and 1022 deletions

693
PeerHandler.php Normal file
View File

@ -0,0 +1,693 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
namespace danog\MadelineProto\MTProtoTools;
/**
* Manages peers.
*/
trait PeerHandler
{
public function to_supergroup($id)
{
return -($id + pow(10, (int) floor(log($id, 10) + 3)));
}
public function is_supergroup($id)
{
$log = log(-$id, 10);
return ($log - intval($log)) * 1000 < 10;
}
public function add_users($users)
{
foreach ($users as $key => $user) {
if (!isset($user['access_hash'])) {
if (isset($user['username']) && !isset($this->chats[$user['id']])) {
try {
$this->get_pwr_chat($user['username'], false, true);
} catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
}
continue;
}
switch ($user['_']) {
case 'user':
if (!isset($this->chats[$user['id']]) || $this->chats[$user['id']] !== $user) {
$this->chats[$user['id']] = $user;
try {
$this->get_pwr_chat($user['id'], false, true);
} catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
}
case 'userEmpty':
break;
default:
throw new \danog\MadelineProto\Exception('Invalid user provided at key '.$key.': '.var_export($user, true));
break;
}
}
}
public function add_chats($chats)
{
foreach ($chats as $key => $chat) {
switch ($chat['_']) {
case 'chat':
case 'chatEmpty':
case 'chatForbidden':
if (!isset($this->chats[-$chat['id']]) || $this->chats[-$chat['id']] !== $chat) {
$this->chats[-$chat['id']] = $chat;
try {
$this->get_pwr_chat(-$chat['id'], $this->settings['peer']['full_fetch'], true);
} catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
}
case 'channelEmpty':
break;
case 'channel':
case 'channelForbidden':
$bot_api_id = $this->to_supergroup($chat['id']);
if (!isset($chat['access_hash'])) {
if (isset($chat['username']) && !isset($this->chats[$bot_api_id])) {
try {
$this->get_pwr_chat($chat['username'], $this->settings['peer']['full_fetch'], true);
} catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
}
continue;
}
if (!isset($this->chats[$bot_api_id]) || $this->chats[$bot_api_id] !== $chat) {
$this->chats[$bot_api_id] = $chat;
try {
if (!isset($this->full_chats[$bot_api_id]) || $this->full_chats[$bot_api_id]['full']['participants_count'] !== $this->get_full_info($bot_api_id)['full']['participants_count']) {
$this->get_pwr_chat($this->to_supergroup($chat['id']), $this->settings['peer']['full_fetch'], true);
}
} catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
}
break;
default:
throw new \danog\MadelineProto\Exception('Invalid chat provided at key '.$key.': '.var_export($chat, true));
break;
}
}
}
public function peer_isset($id)
{
try {
return isset($this->chats[$this->get_info($id)['bot_api_id']]);
} catch (\danog\MadelineProto\Exception $e) {
return false;
} catch (\danog\MadelineProto\RPCErrorException $e) {
if ($e->rpc === 'CHAT_FORBIDDEN') {
return true;
}
if ($e->rpc === 'CHANNEL_PRIVATE') {
return true;
}
return false;
}
}
public function entities_peer_isset($entities)
{
try {
foreach ($entities as $entity) {
if ($entity['_'] === 'messageEntityMentionName' || $entity['_'] === 'inputMessageEntityMentionName') {
if (!$this->peer_isset($entity['user_id'])) {
return false;
}
}
}
} catch (\danog\MadelineProto\Exception $e) {
return false;
}
return true;
}
public function fwd_peer_isset($fwd)
{
try {
if (isset($fwd['user_id']) && !$this->peer_isset($fwd['user_id'])) {
return false;
}
if (isset($fwd['channel_id']) && !$this->peer_isset($this->to_supergroup($fwd['channel_id']))) {
return false;
}
} catch (\danog\MadelineProto\Exception $e) {
return false;
}
return true;
}
public function get_info($id, $recursive = true)
{
if (is_array($id)) {
switch ($id['_']) {
case 'inputUserSelf':
case 'inputPeerSelf':
$id = $this->authorization['user']['id'];
break;
case 'user':
$id = $id['id'];
break;
case 'userFull':
$id = $id['user']['id'];
break;
case 'inputPeerUser':
case 'inputUser':
case 'peerUser':
$id = $id['user_id'];
break;
case 'chat':
case 'chatFull':
$id = -$id['id'];
break;
case 'inputPeerChat':
case 'peerChat':
$id = -$id['chat_id'];
break;
case 'channel':
case 'channelFull':
$id = $this->to_supergroup($id['id']);
break;
case 'inputPeerChannel':
case 'inputChannel':
case 'peerChannel':
$id = $this->to_supergroup($id['channel_id']);
break;
case 'chatForbidden':
case 'channelForbidden':
throw new \danog\MadelineProto\RPCErrorException('CHAT_FORBIDDEN');
default:
throw new \danog\MadelineProto\Exception('Invalid constructor given '.var_export($id, true));
break;
}
}
if (is_string($id) && strpos($id, '#') !== false) {
if (preg_match('/^channel#(\d*)/', $id, $matches)) {
$id = $this->to_supergroup($matches[1]);
}
if (preg_match('/^chat#(\d*)/', $id, $matches)) {
$id = '-'.$matches[1];
}
if (preg_match('/^user#(\d*)/', $id, $marches)) {
$id = $matches[1];
}
}
if (is_numeric($id)) {
if (is_string($id)) {
$id = \danog\MadelineProto\Logger::$bigint ? (float) $id : (int) $id;
}
if (!isset($this->chats[$id]) && $id < 0 && !$this->is_supergroup($id)) {
$this->method_call('messages.getFullChat', ['chat_id' => -$id], ['datacenter' => $this->datacenter->curdc]);
}
if (isset($this->chats[$id])) {
try {
return $this->gen_all($this->chats[$id]);
} catch (\danog\MadelineProto\Exception $e) {
if ($e->getMessage() === 'This peer is not present in the internal peer database') {
unset($this->chats[$id]);
} else {
throw $e;
}
}
}
if (!isset($this->settings['pwr']['requests']) || $this->settings['pwr']['requests'] === true && $recursive) {
$dbres = json_decode(@file_get_contents('https://id.pwrtelegram.xyz/db/getusername?id='.$id, false, stream_context_create(['http' => ['timeout' => 2]])), true);
if (isset($dbres['ok']) && $dbres['ok']) {
$this->resolve_username('@'.$dbres['result']);
return $this->get_info($id, false);
}
}
throw new \danog\MadelineProto\Exception('This peer is not present in the internal peer database');
}
if (preg_match('@(?:t|telegram)\.(?:me|dog)/(joinchat/)?([a-z0-9_-]*)@i', $id, $matches)) {
if ($matches[1] === '') {
$id = $matches[2];
} else {
$invite = $this->method_call('messages.checkChatInvite', ['hash' => $matches[2]], ['datacenter' => $this->datacenter->curdc]);
if (isset($invite['chat'])) {
return $this->get_info($invite['chat']);
} else {
throw new \danog\MadelineProto\Exception('You have not joined this chat');
}
}
}
$id = strtolower(str_replace('@', '', $id));
foreach ($this->chats as $chat) {
if (isset($chat['username']) && strtolower($chat['username']) === $id) {
return $this->gen_all($chat);
}
}
if ($recursive) {
$this->resolve_username($id);
return $this->get_info($id, false);
}
throw new \danog\MadelineProto\Exception('This peer is not present in the internal peer database');
}
public function gen_all($constructor)
{
$res = [$this->constructors->find_by_predicate($constructor['_'])['type'] => $constructor];
switch ($constructor['_']) {
case 'user':
if ($constructor['self']) {
$res['InputPeer'] = ['_' => 'inputPeerSelf'];
$res['InputUser'] = ['_' => 'inputUserSelf'];
} elseif (isset($constructor['access_hash'])) {
$res['InputPeer'] = ['_' => 'inputPeerUser', 'user_id' => $constructor['id'], 'access_hash' => $constructor['access_hash']];
$res['InputUser'] = ['_' => 'inputUser', 'user_id' => $constructor['id'], 'access_hash' => $constructor['access_hash']];
} else {
throw new \danog\MadelineProto\Exception('This peer is not present in the internal peer database');
}
$res['Peer'] = ['_' => 'peerUser', 'user_id' => $constructor['id']];
$res['user_id'] = $constructor['id'];
$res['bot_api_id'] = $constructor['id'];
$res['type'] = $constructor['bot'] ? 'bot' : 'user';
break;
case 'chat':
case 'chatForbidden':
$res['InputPeer'] = ['_' => 'inputPeerChat', 'chat_id' => $constructor['id']];
$res['Peer'] = ['_' => 'peerChat', 'chat_id' => $constructor['id']];
$res['chat_id'] = $constructor['id'];
$res['bot_api_id'] = -$constructor['id'];
$res['type'] = 'chat';
break;
case 'channel':
if (!isset($constructor['access_hash'])) {
throw new \danog\MadelineProto\Exception('This peer is not present in the internal peer database');
}
$res['InputPeer'] = ['_' => 'inputPeerChannel', 'channel_id' => $constructor['id'], 'access_hash' => $constructor['access_hash']];
$res['InputChannel'] = ['_' => 'inputChannel', 'channel_id' => $constructor['id'], 'access_hash' => $constructor['access_hash']];
$res['Peer'] = ['_' => 'peerChannel', 'channel_id' => $constructor['id']];
$res['channel_id'] = $constructor['id'];
$res['bot_api_id'] = $this->to_supergroup($constructor['id']);
$res['type'] = $constructor['megagroup'] ? 'supergroup' : 'channel';
break;
case 'channelForbidden':
throw new \danog\MadelineProto\RPCErrorException('CHAT_FORBIDDEN');
break;
default:
throw new \danog\MadelineProto\Exception('Invalid constructor given '.var_export($constructor, true));
break;
}
return $res;
}
public function full_chat_last_updated($id)
{
return isset($this->full_chats[$id]['last_update']) ? $this->full_chats[$id]['last_update'] : 0;
}
public function get_full_info($id)
{
$partial = $this->get_info($id);
if (time() - $this->full_chat_last_updated($partial['bot_api_id']) < (isset($this->settings['peer']['full_info_cache_time']) ? $this->settings['peer']['full_info_cache_time'] : 0)) {
return array_merge($partial, $this->full_chats[$partial['bot_api_id']]);
}
switch ($partial['type']) {
case 'user':
case 'bot':
$full = $this->method_call('users.getFullUser', ['id' => $partial['InputUser']], ['datacenter' => $this->datacenter->curdc]);
break;
case 'chat':
$full = $this->method_call('messages.getFullChat', $partial, ['datacenter' => $this->datacenter->curdc])['full_chat'];
break;
case 'channel':
case 'supergroup':
$full = $this->method_call('channels.getFullChannel', ['channel' => $partial['InputChannel']], ['datacenter' => $this->datacenter->curdc])['full_chat'];
break;
}
$res = [];
$res['full'] = $full;
$res['last_update'] = time();
$this->full_chats[$partial['bot_api_id']] = $res;
return array_merge($partial, $res);
}
public function get_pwr_chat($id, $fullfetch = true, $send = true)
{
$full = $fullfetch ? $this->get_full_info($id) : $this->get_info($id);
$res = ['id' => $full['bot_api_id'], 'type' => $full['type']];
switch ($full['type']) {
case 'user':
case 'bot':
foreach (['first_name', 'last_name', 'username', 'verified', 'restricted', 'restriction_reason', 'status', 'bot_inline_placeholder', 'access_hash', 'phone', 'lang_code', 'bot_nochats'] as $key) {
if (isset($full['User'][$key])) {
$res[$key] = $full['User'][$key];
}
}
if (isset($full['full']['about'])) {
$res['about'] = $full['full']['about'];
}
if (isset($full['full']['bot_info'])) {
$res['bot_info'] = $full['full']['bot_info'];
}
if (isset($full['full']['phone_calls_available'])) {
$res['phone_calls_available'] = $full['full']['phone_calls_available'];
}
if (isset($full['full']['phone_calls_private'])) {
$res['phone_calls_private'] = $full['full']['phone_calls_private'];
}
if (isset($full['full']['common_chats_count'])) {
$res['common_chats_count'] = $full['full']['common_chats_count'];
}
if (isset($full['full']['profile_photo']['sizes'])) {
$res['photo'] = $this->photosize_to_botapi(end($full['full']['profile_photo']['sizes']), []);
}
/*$bio = '';
if ($full['type'] === 'user' && isset($res['username']) && !isset($res['about']) && $fullfetch) {
if (preg_match('/meta property="og:description" content=".+/', file_get_contents('https://telegram.me/'.$res['username']), $biores)) {
$bio = html_entity_decode(preg_replace_callback('/(&#[0-9]+;)/', function ($m) {
return mb_convert_encoding($m[1], 'UTF-8', 'HTML-ENTITIES');
}, str_replace(['meta property="og:description" content="', '">'], '', $biores[0])));
}
if ($bio != '' && $bio != 'You can contact @'.$res['username'].' right away.') {
$res['about'] = $bio;
}
}*/
break;
case 'chat':
foreach (['title', 'participants_count', 'admin', 'admins_enabled'] as $key) {
if (isset($full['Chat'][$key])) {
$res[$key] = $full['Chat'][$key];
}
}
if (isset($res['admins_enabled'])) {
$res['all_members_are_administrators'] = $res['admins_enabled'];
}
if (isset($full['full']['chat_photo']['sizes'])) {
$res['photo'] = $this->photosize_to_botapi(end($full['full']['chat_photo']['sizes']), []);
}
if (isset($full['full']['exported_invite']['link'])) {
$res['invite'] = $full['full']['exported_invite']['link'];
}
if (isset($full['full']['participants']['participants'])) {
$res['participants'] = $full['full']['participants']['participants'];
}
break;
case 'channel':
case 'supergroup':
foreach (['title', 'democracy', 'restricted', 'restriction_reason', 'access_hash', 'username', 'signatures'] as $key) {
if (isset($full['Chat'][$key])) {
$res[$key] = $full['Chat'][$key];
}
}
foreach (['can_set_stickers', 'stickerset', 'can_view_participants', 'can_set_username', 'participants_count', 'admins_count', 'kicked_count', 'banned_count', 'migrated_from_chat_id', 'migrated_from_max_id', 'pinned_msg_id', 'about', 'hidden_prehistory', 'available_min_id'] as $key) {
if (isset($full['full'][$key])) {
$res[$key] = $full['full'][$key];
}
}
if (isset($full['full']['chat_photo']['sizes'])) {
$res['photo'] = $this->photosize_to_botapi(end($full['full']['chat_photo']['sizes']), []);
}
if (isset($full['full']['exported_invite']['link'])) {
$res['invite'] = $full['full']['exported_invite']['link'];
}
if (isset($full['full']['participants']['participants'])) {
$res['participants'] = $full['full']['participants']['participants'];
}
break;
}
if (isset($res['participants']) && $fullfetch) {
foreach ($res['participants'] as $key => $participant) {
$newres = [];
$newres['user'] = $this->get_pwr_chat($participant['user_id'], false, true);
if (isset($participant['inviter_id'])) {
$newres['inviter'] = $this->get_pwr_chat($participant['inviter_id'], false, true);
}
if (isset($participant['promoted_by'])) {
$newres['promoted_by'] = $this->get_pwr_chat($participant['promoted_by'], false, true);
}
if (isset($participant['kicked_by'])) {
$newres['kicked_by'] = $this->get_pwr_chat($participant['kicked_by'], false, true);
}
if (isset($participant['date'])) {
$newres['date'] = $participant['date'];
}
if (isset($participant['admin_rights'])) {
$newres['admin_rights'] = $participant['admin_rights'];
}
if (isset($participant['banned_rights'])) {
$newres['banned_rights'] = $participant['banned_rights'];
}
if (isset($participant['can_edit'])) {
$newres['can_edit'] = $participant['can_edit'];
}
if (isset($participant['left'])) {
$newres['left'] = $participant['left'];
}
switch ($participant['_']) {
case 'chatParticipant':
$newres['role'] = 'user';
break;
case 'chatParticipantAdmin':
$newres['role'] = 'admin';
break;
case 'chatParticipantCreator':
$newres['role'] = 'creator';
break;
}
$res['participants'][$key] = $newres;
}
}
if (!isset($res['participants']) && isset($res['can_view_participants']) && $res['can_view_participants'] && $fullfetch) {
$total_count = (isset($res['participants_count']) ? $res['participants_count'] : 0) + (isset($res['admins_count']) ? $res['admins_count'] : 0) + (isset($res['kicked_count']) ? $res['kicked_count'] : 0) + (isset($res['banned_count']) ? $res['banned_count'] : 0);
$res['participants'] = [];
$limit = 200;
$filters = ['channelParticipantsAdmins', 'channelParticipantsBots'];
foreach ($filters as $filter) {
$this->fetch_participants($full['InputChannel'], $filter, '', $total_count, $res);
}
$q = '';
$filters = ['channelParticipantsSearch', 'channelParticipantsKicked', 'channelParticipantsBanned'];
foreach ($filters as $filter) {
$this->recurse_alphabet_search_participants($full['InputChannel'], $filter, $q, $total_count, $res);
}
\danog\MadelineProto\Logger::log('Fetched '.count($res['participants'])." out of $total_count");
$res['participants'] = array_values($res['participants']);
}
if (!$fullfetch) {
unset($res['participants']);
}
if ($fullfetch || $send) {
$this->store_db($res);
}
return $res;
}
public function recurse_alphabet_search_participants($channel, $filter, $q, $total_count, &$res)
{
if (!$this->fetch_participants($channel, $filter, $q, $total_count, $res)) {
return false;
}
for ($x = 'a'; $x !== 'aa' && $total_count > count($res['participants']); $x++) {
$this->recurse_alphabet_search_participants($channel, $filter, $q.$x, $total_count, $res);
}
}
public function fetch_participants($channel, $filter, $q, $total_count, &$res)
{
$offset = 0;
$limit = 200;
$has_more = false;
$cached = false;
do {
try {
$gres = $this->method_call('channels.getParticipants', ['channel' => $channel, 'filter' => ['_' => $filter, 'q' => $q], 'offset' => $offset, 'limit' => $limit, 'hash' => $hash = $this->get_participants_hash($channel, $filter, $q, $offset, $limit)], ['datacenter' => $this->datacenter->curdc, 'heavy' => true]);
} catch (\danog\MadelineProto\RPCErrorException $e) {
if ($e->rpc === 'CHAT_ADMIN_REQUIRED') {
return $has_more;
} else {
throw $e;
}
}
if ($cached = $gres['_'] === 'channels.channelParticipantsNotModified') {
$gres = $this->fetch_participants_cache($channel, $filter, $q, $offset, $limit);
} else {
$this->store_participants_cache($gres, $channel, $filter, $q, $offset, $limit);
}
$has_more = $gres['count'] === 10000;
foreach ($gres['participants'] as $participant) {
$newres = [];
$newres['user'] = $this->get_pwr_chat($participant['user_id'], false, true);
if (isset($participant['inviter_id'])) {
$newres['inviter'] = $this->get_pwr_chat($participant['inviter_id'], false, true);
}
if (isset($participant['kicked_by'])) {
$newres['kicked_by'] = $this->get_pwr_chat($participant['kicked_by'], false, true);
}
if (isset($participant['promoted_by'])) {
$newres['promoted_by'] = $this->get_pwr_chat($participant['promoted_by'], false, true);
}
if (isset($participant['date'])) {
$newres['date'] = $participant['date'];
}
switch ($participant['_']) {
case 'channelParticipantSelf':
$newres['role'] = 'user';
if (isset($newres['admin_rights'])) {
$newres['admin_rights'] = $full['Chat']['admin_rights'];
}
if (isset($newres['banned_rights'])) {
$newres['banned_rights'] = $full['Chat']['banned_rights'];
}
break;
case 'channelParticipant':
$newres['role'] = 'user';
break;
case 'channelParticipantCreator':
$newres['role'] = 'creator';
break;
case 'channelParticipantAdmin':
$newres['role'] = 'admin';
break;
case 'channelParticipantBanned':
$newres['role'] = 'banned';
break;
}
$res['participants'][$participant['user_id']] = $newres;
}
\danog\MadelineProto\Logger::log("Fetched channel participants with filter $filter, query $q, offset $offset, limit $limit, hash $hash: ".($cached ? 'cached' : 'not cached').', '.count($gres['participants']).' participants out of '.$gres['count'].', in total fetched '.count($res['participants']).' out of '.$total_count);
$offset += count($gres['participants']);
} while (count($gres['participants']));
return $has_more;
}
public function fetch_participants_cache($channel, $filter, $q, $offset, $limit)
{
return $this->channel_participants[$channel['channel_id']][$filter][$q][$offset][$limit];
}
public function store_participants_cache($gres, $channel, $filter, $q, $offset, $limit)
{
return;
unset($gres['users']);
if (\danog\MadelineProto\Logger::$bigint) {
$hash = new \phpseclib\Math\BigInteger(0);
foreach ($gres['participants'] as $participant) {
$hash = $hash->multiply($this->twozerotwosixone)->add($this->zeroeight)->add(new \phpseclib\Math\BigInteger($participant['user_id']))->divide($this->zeroeight)[1];
}
$gres['hash'] = $this->unpack_signed_int(strrev(str_pad($hash->toBytes(), 4, "\0", STR_PAD_LEFT)));
} else {
$hash = 0;
foreach ($gres['participants'] as $participant) {
$hash = (($hash * 20261) + 0x80000000 + $participant['user_id']) % 0x80000000;
}
$gres['hash'] = $hash;
}
$this->channel_participants[$channel['channel_id']][$filter][$q][$offset][$limit] = $gres;
}
public function get_participants_hash($channel, $filter, $q, $offset, $limit)
{
return isset($this->channel_participants[$channel['channel_id']][$filter][$q][$offset][$limit]) ? $this->channel_participants[$channel['channel_id']][$filter][$q][$offset][$limit]['hash'] : 0;
}
public function store_db($res, $force = false)
{
$settings = isset($this->settings['connection_settings'][$this->datacenter->curdc]) ? $this->settings['connection_settings'][$this->datacenter->curdc] : $this->settings['connection_settings']['all'];
if (!isset($this->settings['pwr']) || $this->settings['pwr']['pwr'] === false || $settings['test_mode']) {
/*
try {
if (isset($res['username'])) {
shell_exec('curl '.escapeshellarg('https://api.pwrtelegram.xyz/getchat?chat_id=@'.$res['username']).' -s -o /dev/null >/dev/null 2>/dev/null & ');
}
} catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log([$e->getMessage());
}
*/
return;
}
if (!empty($res)) {
if (isset($res['participants'])) {
unset($res['participants']);
}
$this->qres[] = $res;
}
if ($this->last_stored > time() && !$force) {
return false;
}
if (empty($this->qres)) {
return false;
}
try {
$payload = json_encode($this->qres);
$path = '/tmp/ids'.hash('sha256', $payload);
file_put_contents($path, $payload);
$id = isset($this->authorization['user']['username']) ? $this->authorization['user']['username'] : $this->authorization['user']['id'];
$result = shell_exec('curl '.escapeshellarg('https://id.pwrtelegram.xyz/db'.$this->settings['pwr']['db_token'].'/addnewmadeline?d=pls&from='.$id).' -d '.escapeshellarg('@'.$path).' -s -o '.escapeshellarg($path.'.log').' >/dev/null 2>/dev/null & ');
\danog\MadelineProto\Logger::log($result, \danog\MadelineProto\Logger::VERBOSE);
$this->qres = [];
$this->last_stored = time() + 10;
} catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log('======= COULD NOT STORE IN DB DUE TO '.$e->getMessage().' =============', \danog\MadelineProto\Logger::VERBOSE);
}
}
public function resolve_username($username)
{
try {
$res = $this->method_call('contacts.resolveUsername', ['username' => str_replace('@', '', $username)], ['datacenter' => $this->datacenter->curdc]);
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log('Username resolution failed with error '.$e->getMessage(), \danog\MadelineProto\Logger::ERROR);
return false;
}
if ($res['_'] === 'contacts.resolvedPeer') {
return $res;
}
return false;
}
}

974
README.md

File diff suppressed because it is too large Load Diff

View File

@ -23,11 +23,9 @@ try {
$authorization = $MadelineProto->bot_login(readline('Enter a bot token: '));
\danog\MadelineProto\Logger::log($authorization, \danog\MadelineProto\Logger::NOTICE);
}
//\danog\MadelineProto\Logger::log($MadelineProto->API->get_config([], ['datacenter' => $MadelineProto->API->datacenter->curdc]));
//\danog\MadelineProto\Logger::log($MadelineProto->API->settings['connection']);
$MadelineProto->session = 'bot.madeline';
echo 'Wrote '.\danog\MadelineProto\Serialization::serialize('bot.madeline', $MadelineProto).' bytes'.PHP_EOL;
$offset = 0;
while (true) {
$updates = $MadelineProto->get_updates(['offset' => $offset, 'limit' => 50, 'timeout' => 0]); // Just like in the bot API, you can specify an offset, a limit and a timeout
\danog\MadelineProto\Logger::log($updates);

View File

@ -0,0 +1,24 @@
# Composer
Once you have all the requirements installed properly (on dev as well as production), add this to the ```composer.json``` file:
```
"repositories": [
{
"type": "git",
"url": "https://github.com/danog/phpseclib"
}
],
```
Make sure you also have these set in the composer.json:
```
"minimum-stability": "dev",
```
Then you can require the package by addding the following line to the require section:
```
"danog/madelineproto":"dev-master"
```

830
docs/FULL_README.md Normal file
View File

@ -0,0 +1,830 @@
# MadelineProto, a PHP MTProto telegram client
Do join the official channel, [@MadelineProto](https://t.me/MadelineProto)!
This library can be used to create php telegram bots (like bot API bots, only better) and php telegram userbots (like tg-cli userbots, only better).
This library can also be used to create lua telegram bots (like bot API bots, only better) and lua telegram userbots (like tg-cli userbots, only better).
## Features
* It allows you to do everything official clients can do, programmatically!
* *It can make phone calls!* [See here for instructions](#calls)
* It can be proxied!
* It is very fast!
* It can be easily serialized!
* It featured update handling with callbacks or long polling!
* Easy to use wrappers to upload/download files and call mtproto methods
* Documentation for EVERY mtproto method!
* Internal peer management: you can provide a simple bot API chat id or a username to send a message or to call other mtproto methods!
* You can easily login as a user (2FA is supported) or as a bot!
* Simple error handling!
* It is highly customizable with a lot of different settings!
* Bot API file id/object support (even for users)!
* A Lua binding
* A lua wrapper for td-cli scripts
* Secret chats
* MTProto 2.0
* PFS
* PFS in secret chats
* [Clickable inline buttons](#inline-buttons)!
## Requirements
MadelineProto needs the xml extension to function properly (if not, you will get DOMDocument errors).
MadelineProto uses the gmp, PrimeModule extensions (if not, it will be VERY slow).
Usually these extension is installed by default, [skip to next section](#installation).
If not, here's how to install everything on:
* [Ubuntu](UBUNTU_INSTALLATION.md)
* [Ubuntu (full)](UBUNTU_INSTALLATION_FULL.md)
## Installation
### Simple
Download [madeline.php](https://phar.madelineproto.xyz/madeline.php).
Other methods:
* [composer](COMPOSER_INSTALLATION.md)
* [git](GIT_INSTALLATION.md)
## Inline buttons
You can easily click inline buttons using MadelineProto, just access the correct button:
```
$button = $update['update']['message']['reply_markup']['rows'][0]['buttons'][0];
```
You can then access properties (they vary depending on the [type of button](https://docs.madelineproto.xyz/API_docs/types/KeyboardButton.html)):
```
$text = $button['text'];
```
And click them:
```
$button->click();
```
## Storing sessions
To store information about an account session, serialization must be done.
An istance of MadelineProto is automatically serialized every `$settings['serialization']['serialization_interval']` seconds (by default 30 seconds), and on shutdown.
To set the serialization destination file, do the following:
When creating a new session:
```
$MadelineProto = new \danog\MadelineProto\API($settings);
$MadelineProto->session = 'session.madeline'; // The session will be serialized to session.madeline
$MadelineProto->serialize(); // Force first serialization
```
To load a serialized session:
```
$MadelineProto = new \danog\MadelineProto\API('session.madeline');
```
To load a serialized session, replacing settings on deserialization:
```
$MadelineProto = new \danog\MadelineProto\API('session.madeline', $settings);
```
If the scripts shutsdown normally (without ctrl+c or fatal errors/exceptions), the session will be serialized automatically.
## Methods
A list of all of the methods that can be called with MadelineProto can be found here: [here (layer 75)](https://daniil.it/MadelineProto/API_docs/).
If an object of type User, InputUser, Chat, InputChannel, Peer or InputPeer must be provided as a parameter to a method, you can substitute it with the user/group/channel's username (`@username`) or bot API id (`-1029449`, `1249421`, `-100412412901`).
Methods that allow sending message entities ([messages.sendMessage](http://docs.madelineproto.xyz/API_docs/methods/messages_sendMessage.html) for example) also have an additional `parse_mode` parameter that enables or disables html/markdown parsing of the message to be sent. See the [method-specific](http://docs.madelineproto.xyz/API_docs/methods/messages_sendMessage.html) documentation for more info.
To convert the results of methods to bot API objects you must provide a second parameter to method wrappers, containing an array with the `botAPI` key set to true:
```
$bot_API_object = $MadelineProto->messages->sendMessage(['peer' => '@danogentili', 'message' => 'lel'], ['botAPI' => true]);
```
To disable fetching the result of a method, the array that must be provided as second parameter to method wrapper must have the `noResponse` key set to true.
```
$MadelineProto->messages->sendMessage(['peer' => '@danogentili', 'message' => 'lel'], ['noResponse' => true]);
```
reply_markup accepts bot API reply markup objects as well as MTProto ones.
```
$MadelineProto->messages->sendMessage(['peer' => '@danogentili', 'message' => 'lel', 'reply_markup' => $MTProto_markup]);
$MadelineProto->messages->sendMessage(['peer' => '@danogentili', 'message' => 'lel', 'reply_markup' => $bot_API_markup]);
```
Use `phone_login` to login, see [here for the parameters and the result](https://daniil.it/MadelineProto/phone_login.html).
Use `complete_phone_login` to complete the login, see [here for the parameters and the result](https://daniil.it/MadelineProto/complete_phone_login.html).
Use `complete_2FA_login` to complete the login to an account with 2FA enabled, see [here for the parameters and the result](https://daniil.it/MadelineProto/complete_2FA_login.html).
Use `complete_signup` to signup, see [here for the parameters and the result](https://daniil.it/MadelineProto/complete_signup.html).
Use `bot_login` to login as a bot, see [here for the parameters and the result](https://daniil.it/MadelineProto/bot_login.html).
Note that when you login as a bot, MadelineProto also logins using the [PWRTelegram](https://pwrtelegram.xyz) API, to allow persistant storage of peers, even after a logout and another login.
Use `logout` to logout, see [here for the parameters and the result](https://daniil.it/MadelineProto/logout.html).
Use `get_pwr_chat` to get chat info, see [here for the parameters and the result](https://daniil.it/MadelineProto/get_pwr_chat.html).
You can also use `get_info` to get chat info, see [here for the parameters and the result](https://daniil.it/MadelineProto/get_info.html)
You can also use `get_full_info` to get chat info, see [here for the parameters and the result](https://daniil.it/MadelineProto/get_full_info.html).
You must use `get_dialogs` to get a list of all of the chats, see [here for the parameters and the result](https://daniil.it/MadelineProto/get_dialogs.html)
You must use `get_self` to get info about the current user, see [here for the parameters and the result](https://daniil.it/MadelineProto/get_self.html)
## Uploading and downloading files
MadelineProto provides wrapper methods to upload and download files that support bot API file ids.
Every method described in this section accepts a last optional paramater with a callable function that will be called during the upload/download using the first parameter to pass a floating point number indicating the upload/download status in percentage.
The upload method returns an [InputFile](https://daniil.it/MadelineProto/API_docs/types/InputFile.html) object that must be used to generate an [InputMedia](https://daniil.it/MadelineProto/API_docs/types/InputMedia.html) object, that can be later sent using the [sendmedia method](https://daniil.it/MadelineProto/API_docs/methods/messages_sendMedia.html).
The `upload_encrypted` method returns an [InputEncryptedFile](https://daniil.it/MadelineProto/API_docs/types/InputEncryptedFile.html) object that must be used to generate an [EncryptedMessage](https://daniil.it/MadelineProto/API_docs/types/EncryptedMessage.html) object, that can be later sent using the [sendEncryptedFile method](https://daniil.it/MadelineProto/API_docs/methods/messages_sendEncryptedFile.html).
```
$inputFile = $MadelineProto->upload('file', 'optional new file name.ext');
// Generate an inputMedia object and store it in $inputMedia, see tests/testing.php
$MadelineProto->messages->sendMedia(['peer' => '@pwrtelegramgroup', 'media' => $inputMedia]);
$inputEncryptedFile = $MadelineProto->upload_encrypted('file', 'optional new file name.ext');
```
To convert the result of sendMedia to a bot API file id select the messageMedia object from the output of the method and pass it to `$MadelineProto->API->MTProto_to_botAPI()`.
See tests/testing.php for more examples.
There are multiple download methods that allow you to download a file to a directory, to a file or to a stream.
The first parameter of these functions must always be either a [messageMediaPhoto](https://daniil.it/MadelineProto/API_docs/constructors/messageMediaPhoto.html) or a [messageMediaDocument](https://daniil.it/MadelineProto/API_docs/constructors/messageMediaDocument.html) object, an [EncryptedMessage](https://daniil.it/MadelineProto/API_docs/types/EncryptedMessage.html) or a bot API file id. These objects are usually received in updates, see `bot.php` for examples
```
$output_file_name = $MadelineProto->download_to_dir($message_media, '/tmp/dldir');
$custom_output_file_name = $MadelineProto->download_to_file($message_media, '/tmp/dldir/customname.ext');
$stream = fopen('php://output', 'w'); // Stream to browser like with echo
$MadelineProto->download_to_stream($message_media, $stream, $cb, $offset, $endoffset); // offset and endoffset are optional parameters that specify the byte from which to start downloading and the byte where to stop downloading (the latter non-inclusive), if not specified default to 0 and the size of the file
```
### 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.
Here are the default values for the settings arrays and explanations for every setting:
```
[
'authorization' => [ // Authorization settings
'default_temp_auth_key_expires_in' => 31557600, // validity of temporary keys and the binding of the temporary and permanent keys
'rsa_keys' => [
"-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6\nlyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS\nan9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw\nEfzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+\n8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n\nSlv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB\n-----END RSA PUBLIC KEY-----",
"-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAxq7aeLAqJR20tkQQMfRn+ocfrtMlJsQ2Uksfs7Xcoo77jAid0bRt\nksiVmT2HEIJUlRxfABoPBV8wY9zRTUMaMA654pUX41mhyVN+XoerGxFvrs9dF1Ru\nvCHbI02dM2ppPvyytvvMoefRoL5BTcpAihFgm5xCaakgsJ/tH5oVl74CdhQw8J5L\nxI/K++KJBUyZ26Uba1632cOiq05JBUW0Z2vWIOk4BLysk7+U9z+SxynKiZR3/xdi\nXvFKk01R3BHV+GUKM2RYazpS/P8v7eyKhAbKxOdRcFpHLlVwfjyM1VlDQrEZxsMp\nNTLYXb6Sce1Uov0YtNx5wEowlREH1WOTlwIDAQAB\n-----END RSA PUBLIC KEY-----",
"-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAsQZnSWVZNfClk29RcDTJQ76n8zZaiTGuUsi8sUhW8AS4PSbPKDm+\nDyJgdHDWdIF3HBzl7DHeFrILuqTs0vfS7Pa2NW8nUBwiaYQmPtwEa4n7bTmBVGsB\n1700/tz8wQWOLUlL2nMv+BPlDhxq4kmJCyJfgrIrHlX8sGPcPA4Y6Rwo0MSqYn3s\ng1Pu5gOKlaT9HKmE6wn5Sut6IiBjWozrRQ6n5h2RXNtO7O2qCDqjgB2vBxhV7B+z\nhRbLbCmW0tYMDsvPpX5M8fsO05svN+lKtCAuz1leFns8piZpptpSCFn7bWxiA9/f\nx5x17D7pfah3Sy2pA+NDXyzSlGcKdaUmwQIDAQAB\n-----END RSA PUBLIC KEY-----",
"-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAwqjFW0pi4reKGbkc9pK83Eunwj/k0G8ZTioMMPbZmW99GivMibwa\nxDM9RDWabEMyUtGoQC2ZcDeLWRK3W8jMP6dnEKAlvLkDLfC4fXYHzFO5KHEqF06i\nqAqBdmI1iBGdQv/OQCBcbXIWCGDY2AsiqLhlGQfPOI7/vvKc188rTriocgUtoTUc\n/n/sIUzkgwTqRyvWYynWARWzQg0I9olLBBC2q5RQJJlnYXZwyTL3y9tdb7zOHkks\nWV9IMQmZmyZh/N7sMbGWQpt4NMchGpPGeJ2e5gHBjDnlIf2p1yZOYeUYrdbwcS0t\nUiggS4UeE8TzIuXFQxw7fzEIlmhIaq3FnwIDAQAB\n-----END RSA PUBLIC KEY-----",
], // RSA public keys
],
'connection' => [ // List of datacenters/subdomains where to connect
'ssl_subdomains' => [ // Subdomains of web.telegram.org for https protocol
1 => 'pluto',
2 => 'venus',
3 => 'aurora',
4 => 'vesta',
5 => 'flora', // musa oh wait no :(
],
'test' => [ // Test datacenters
'ipv4' => [ // ipv4 addresses
2 => [ // The rest will be fetched automatically
'ip_address' => '149.154.167.40',
'port' => 443,
'media_only' => false,
'tcpo_only' => false,
],
],
'ipv6' => [ // ipv6 addresses
2 => [ // The rest will be fetched automatically
'ip_address' => '2001:067c:04e8:f002:0000:0000:0000:000e',
'port' => 443,
'media_only' => false,
'tcpo_only' => false,
],
],
],
'main' => [ // Main datacenters
'ipv4' => [ // ipv4 addresses
2 => [ // The rest will be fetched automatically
'ip_address' => '149.154.167.51',
'port' => 443,
'media_only' => false,
'tcpo_only' => false,
],
],
'ipv6' => [ // ipv6 addresses
2 => [ // The rest will be fetched automatically
'ip_address' => '2001:067c:04e8:f002:0000:0000:0000:000a',
'port' => 443,
'media_only' => false,
'tcpo_only' => false,
],
],
],
],
'connection_settings' => [ // connection settings
'all' => [ // These settings will be applied on every datacenter that hasn't a custom settings subarray...
'protocol' => 'tcp_full', // can be tcp_full, tcp_abridged, tcp_intermediate, http, https, obfuscated2, udp (unsupported)
'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
'pfs' => true, // Should we use PFS for this socket?
],
],
'app_info' => [ // obtained in https://my.telegram.org
'api_id' => you should put an API id in the settings array you provide
'api_hash' => you should put an API hash in the settings array you provide
'device_model' => $device_model,
'system_version' => $system_version,
'app_version' => 'Unicorn',
'lang_code' => $lang_code,
],
'tl_schema' => [ // TL scheme files
'layer' => 75, // layer version
'src' => [
'mtproto' => __DIR__.'/TL_mtproto_v1.json', // mtproto TL scheme
'telegram' => __DIR__.'/TL_telegram_v75.tl', // telegram TL scheme
'secret' => __DIR__.'/TL_secret.tl', // secret chats TL scheme
'calls' => __DIR__.'/TL_calls.tl', // calls TL scheme
'botAPI' => __DIR__.'/TL_botAPI.tl', // bot API TL scheme for file ids
],
],
'logger' => [ // Logger settings
/*
* logger modes:
* 0 - No logger
* 1 - Log to the default logger destination
* 2 - Log to file defined in second parameter
* 3 - Echo logs
* 4 - Call callable provided in logger_param. logger_param must accept two parameters: array $message, int $level
* $message is an array containing the messages the log, $level, is the logging level
*/
'logger' => 1, // write to
'logger_param' => '/tmp/MadelineProto.log',
'logger' => 3, // overwrite previous setting and echo logs
'logger_level' => Logger::VERBOSE, // Logging level, available logging levels are: ULTRA_VERBOSE, VERBOSE, NOTICE, WARNING, ERROR, FATAL_ERROR. Can be provided as last parameter to the logging function.
'rollbar_token' => 'f9fff6689aea4905b58eec75f66c791d' // You can provide a token for the rollbar log management system
],
'max_tries' => [
'query' => 5, // How many times should I try to call a method or send an object before throwing an exception
'authorization' => 5, // How many times should I try to generate an authorization key before throwing an exception
'response' => 5, // How many times should I try to get a response of a query before throwing an exception
],
'flood_timeout' => [
'wait_if_lt' => 20, // Sleeps if flood block time is lower than this
],
'msg_array_limit' => [ // How big should be the arrays containing the incoming and outgoing messages?
'incoming' => 200,
'outgoing' => 200,
'call_queue' => 200,
],
'peer' => [
'full_info_cache_time' => 3600, // Full peer info cache validity
'full_fetch' => false, // Should madeline fetch the full member list of every group it meets?
'cache_all_peers_on_startup' => false, // Should madeline fetch the full chat list on startup?
],
'requests' => [
'gzip_encode_if_gt' => 500, // Should I try using gzip encoding for requests bigger than N bytes? Set to -1 to disable.
],
'updates' => [
'handle_updates' => true, // Should I handle updates?
'handle_old_updates' => true, // Should I handle old updates on startup?
'getdifference_interval' => 30, // Manual difference polling interval
'callback' => 'get_updates_update_handler', // A callable function that will be called every time an update is received, must accept an array (for the update) as the only parameter
],
'secret_chats' => [
'accept_chats' => true, // Should I accept secret chats? Can be true, false or on array of user ids from which to accept chats
],
'serialization' => [
'serialization_interval' => 30, // Automatic serialization interval
],
'threading' => [
'allow_threading' => false, // Should I use threading, if it is enabled?
'handler_workers' => 10, // How many workers should every message handler pool of each socket reader have
],
'pwr' => [
'pwr' => false, // Need info ?
'db_token' => false, // Need info ?
'strict' => false, // Need info ?
'requests' => true, // Should I get info about unknown peers from PWRTelegram?
],
];
```
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:
```
$settings = [
'authorization' => [ // Authorization settings
'default_temp_auth_key_expires_in' => 86400, // a day
]
]
```
Becomes:
```
$settings = [
'authorization' => [ // Authorization settings
'default_temp_auth_key_expires_in' => 86400,
'rsa_keys' => array with default rsa keys
]
// The remaining subsetting arrays are the set to default
]
```
Note that only settings arrays or values of a settings array will be set to default.
The settings array can be accessed and modified in the instantiated class by accessing the `settings` attribute of the API class:
```
$yoursettings = ['updates' => ['handle_updates' => false]]; // disable update handlig
$MadelineProto = new \danog\MadelineProto\API($yoursettings);
var_dump($MadelineProto->settings);
$MadelineProto->settings['updates']['handle_updates'] = true; // reenable update fetching
```
### Handling updates
When an update is received, the update callback function (see settings) is called. By default, the get_updates_update_handler MadelineProto method is called. This method stores all incoming updates into an array (its size limit is specified by the updates\_array\_limit parameter in the settings) and can be fetched by running the `get_updates` method.
IMPORTANT Note that you should turn off update handling if you don't plan to use it because the default get_updates update handling stores updates in an array inside the MadelineProto class, without deleting old ones unless they are read using get_updates. This will eventually fill up the RAM of your server if you don't disable updates or read them using get_updates.
This method accepts an array of options as the first parameter, and returns an array of updates (an array containing the update id and an object of type [Update](https://daniil.it/MadelineProto/API_docs/types/Update.html)). Example:
```
$MadelineProto = new \danog\MadelineProto\API();
// Login or deserialize
$offset = 0;
while (true) {
$updates = $MadelineProto->API->get_updates(['offset' => $offset, 'limit' => 50, 'timeout' => 1]); // Just like in the bot API, you can specify an offset, a limit and a timeout
foreach ($updates as $update) {
$offset = $update['update_id']; // Just like in the bot API, the offset must be set to the last update_id
// Parse $update['update'], that is an object of type Update
}
var_dump($updates);
}
array(3) {
[0]=>
array(2) {
["update_id"]=>
int(0)
["update"]=>
array(5) {
["_"]=>
string(22) "updateNewAuthorization"
["auth_key_id"]=>
int(-8182897590766478746)
["date"]=>
int(1483110797)
["device"]=>
string(3) "Web"
["location"]=>
string(25) "IT, 05 (IP = 79.2.51.203)"
}
}
[1]=>
array(2) {
["update_id"]=>
int(1)
["update"]=>
array(3) {
["_"]=>
string(23) "updateReadChannelOutbox"
["channel_id"]=>
int(1049295266)
["max_id"]=>
int(8288)
}
}
[2]=>
array(2) {
["update_id"]=>
int(2)
["update"]=>
array(4) {
["_"]=>
string(23) "updateNewChannelMessage"
["message"]=>
array(12) {
["_"]=>
string(7) "message"
["out"]=>
bool(false)
["mentioned"]=>
bool(false)
["media_unread"]=>
bool(false)
["silent"]=>
bool(false)
["post"]=>
bool(false)
["id"]=>
int(11521)
["from_id"]=>
int(262946027)
["to_id"]=>
array(2) {
["_"]=>
string(11) "peerChannel"
["channel_id"]=>
int(1066910227)
}
["date"]=>
int(1483110798)
["message"]=>
string(3) "yay"
["entities"]=>
array(1) {
[0]=>
array(4) {
["_"]=>
string(24) "messageEntityMentionName"
["offset"]=>
int(0)
["length"]=>
int(3)
["user_id"]=>
int(101374607)
}
}
}
["pts"]=>
int(13010)
["pts_count"]=>
int(1)
}
}
}
```
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`.
### Calls
MadelineProto provides an easy wrapper to work with phone calls.
The wrapper consists in the `\danog\MadelineProto\VoIP` class, that can be installed by compiling the [php-libtgvoip](https://github.com/danog/php-libtgvoip) extension.
Please read the whole [VoIP API documentation](https://daniil.it/MadelineProto/API_docs/types/PhoneCall.html) before proceeding.
You can also run [this script](https://daniil.it/php.sh), that will compile the latest version of ZTS PHP, PrimeModule, pthreads, and php-libtgvoip.
It accepts one parameter with the ID of the person to call, and returns a VoIP object that can be used to play audio files, set the hold files, change the configuration and set the output file.
Input/output audio can be converted from/to any audio/video file using ffmpeg (just don't forget to provide the correct number of channels, sample rate and bit depth, `ffmpeg -i anyaudioorvideo -f s"$bitnumber"le -ac $channelNumber -ar $bitRate -acodec pcm_s"$bitnumber"le output.raw`).
You can also stream the audio track of video streams (even from youtube), or audio streams. Just stream the data to a FIFO, and use ffmpeg to output the converted audio to another FIFO that will be used as input file.
MadelineProto works using raw signed PCM audio with the sample rate and the bit depth specified in the configuration (see [here](https://daniil.it/MadelineProto/API_docs/types/PhoneCall.html) for info on how to fetch it).
Requesting calls is easy, just run the `request_call` method.
```
$controller = $MadelineProto->request_call('@danogentili')->play('input.raw')->then('inputb.raw')->playOhHold(['hold.raw'])->setOutputFile('output.raw');
$controller->configuration['log_file_path'] = $controller->getOtherID().'.log';
// We need to receive updates in order to know that the other use accepted the call
while ($controller->getCallState() < \danog\MadelineProto\VoIP::CALL_STATE_READY) {
$MadelineProto->get_updates();
}
```
Accepting calls is just as easy: you will receive an [updatePhoneCall](https://daniil.it/MadelineProto/API_docs/constructors/updatePhoneCall.html) object from your update source (see [update handling](#update-handling)).
This array will contain a VoIP object under the `phone_call` key.
```
$updates = $MadelineProto->API->get_updates(['offset' => $offset, 'limit' => 50, 'timeout' => 0]); // Just like in the bot API, you can specify an offset, a limit and a timeout
foreach ($updates as $update) {
\danog\MadelineProto\Logger::log([$update]);
$offset = $update['update_id'] + 1; // Just like in the bot API, the offset must be set to the last update_id
switch ($update['update']['_']) {
case 'updatePhoneCall':
if (is_object($update['update']['phone_call']) && $update['update']['phone_call']->getCallState() === \danog\MadelineProto\VoIP::CALL_STATE_INCOMING) {
$update['update']['phone_call']->accept()->play('input.raw')->then('inputb.raw')->playOnHold(['hold.raw'])->setOutputFile('output.raw');
}
}
}
```
### Secret chats
MadelineProto provides some wrappers to work with secret chats:
```
$secret_chat = $MadelineProto->request_secret_chat($InputUser);
```
`request_secret_chat` requests a secret secret chat to the [InputUser](https://daniil.it/MadelineProto/API_docs/types/InputUser.html) specified, and returns a number that can be used instead of an [InputEncryptedChat](https://daniil.it/MadelineProto/API_docs/constructors/inputEncryptedChat.html).
Secret chats are accepted or refused automatically, based on a value in the settings array (by default MadelineProto is set to accept all secret chats).
Before sending any message, you must check if the secret chat was accepted by the other client with the following method:
```
$status = $MadelineProto->secret_chat_status($chat);
```
Returns 0 if the chat cannot be found in the local database, 1 if the chat was requested but not yet accepted, and 2 if it is a valid accepted secret chat.
To send messages/files/service messages, simply use the sendEncrypted methods with objects that use the same layer used by the other client (specified by the number after the underscore in decryptedMessage object names, to obtain the layer that must be used for a secret chat use the following wrapper method).
```
$secret_chat = $MadelineProto->get_secret_chat($chat);
/*
[
'key' => [ // The authorization key
'auth_key' => 'string', // 256 bytes long
'fingerprint' => 10387574747492, // a 64 bit signed integer
'visualization_orig' => 'string', // 16 bytes long
'visualization_46' => 'string', // 20 bytes long
// The two visualization strings must be concatenated to generate a visual fingerprint
],
'admin' => false, // Am I the creator of the chat?
'user_id' => 101374607, // The user id of the other user
'InputEncryptedChat' => [...], // An inputEncryptedChat object that represents the current chat
'in_seq_no_x' => number, // in_seq_no must be multiplied by two and incremented by this before being sent over the network
'out_seq_no_x' => number, // out_seq_no must be multiplied by two and incremeneted this begore being sent over the network
'layer' => number, // The secret chat TL layer used by the other client
'ttl' => number, // The default time to live of messages in this chat
'ttr' => 100, // Time left before rekeying must be done, decremented by one every time a message as encrypted/decrypted with this key
'updated' => time(), // Last time the key of the current chat was changed
'incoming' => [], // Incoming messages, TL serialized strings
'outgoing' => [], // Outgoing ~
'created' => time(), // When was this chat created
'rekeying' => [0] // Info for rekeying
];
*/
```
This method gets info about a certain chat.
### Lua binding
The lua binding makes use of the Lua php extension.
When istantiating the `\danog\MadelineProto\Lua` class, the first parameter provided to the constructor must be the path to the lua script, and the second parameter a logged in instance of MadelineProto.
The class is basically a wrapper for the lua environment, so by setting an attribute you're setting a variable in the Lua environment, by reading an attribute you're reading a variable from the lua environment, and by calling a function you're actually calling a Lua function you declared in the script.
By assigning a callable to an attribute, you're actually assigning a new function in the lua environment that once called, will call the php callable.
Passing lua callables to a parameter of a PHP callable will throw an exception due to a bug in the PHP lua extension that I gotta fix (so passing the usual cb and cb_extra parameters to the td-cli wrappers isn't yet possible).
All MadelineProto wrapper methods (for example upload, download, upload_encrypted, get_self, and others) are imported in the Lua environment, as well as all MTProto wrappers (see the API docs for more info).
td-cli wrappers are also present: you can use the tdcli_function in lua and pass mtproto updates to the tdcli_update_callback via PHP, they will be automatically converted to/from td objects. Please note that the object conversion is not complete, feel free to contribute to the conversion module in `src/danog/MadelineProto/Conversion/TD.php`.
For examples, see `lua/*`.
### Exceptions
MadelineProto can throw lots of different exceptions:
* \danog\MadelineProto\Exception - Default exception, thrown when a php error occures and in a lot of other cases
* \danog\MadelineProto\RPCErrorException - Thrown when an RPC error occurres (an error received via the mtproto API)
* \danog\MadelineProto\TL\Exception - Thrown on TL serialization/deserialization errors
* \danog\MadelineProto\NothingInTheSocketException - Thrown if no data can be read from the TCP socket
* \danog\MadelineProto\PTSException - Thrown if the PTS is unrecoverably corrupted
* \danog\MadelineProto\SecurityException - Thrown on security problems (invalid params during generation of auth key or similar)
* \danog\MadelineProto\TL\Conversion\Exception - Thrown if some param/object can't be converted to/from bot API/TD/TD-CLI format (this includes markdown/html parsing)
## Credits
Created by [Daniil Gentili](https://daniil.it), licensed under AGPLv3, based on [telepy](https://github.com/griganton/telepy_old).
While writing this client, I looked at many projects for inspiration and help. Here's the full list:
* [tgl](https://github.com/vysheng/tgl)
* [Kotlogram](https://github.com/badoualy/kotlogram)
* [Webogram](https://github.com/zhukov/webogram)
* [Telethon](https://github.com/LonamiWebs/Telethon/)
Thanks to the devs that contributed to these projects, MadelineProto is now an easy, well-written and complete MTProto client.
## Contributing
[Here](https://github.com/danog/MadelineProto/projects/1) you can find this project's roadmap.
You can use this scheme of the structure of this project to help yourself:
```
build_docs.php - Builds API docs from TL scheme file
src/danog/MadelineProto/
MTProtoTools/
AckHandler - Handles acknowledgement of incoming and outgoing mtproto messages
AuthKeyHandler - Handles generation of the temporary and permanent authorization keys
CallHandler - Handles synchronous calls to mtproto methods or objects, also basic response management (waits until the socket receives a response)
Crypt - Handles ige and aes encryption
MessageHandler - Handles sending and receiving of mtproto messages (packs TL serialized data with message id, auth key id and encrypts it with Crypt if needed, adds them to the arrays of incoming and outgoing messages)
MsgIdHandler - Handles message ids (checks if they are valid, adds them to the arrays of incoming and outgoing messages)
ResponseHandler - Handles the content of responses received, service messages, rpc results, errors, and stores them into the response section of the outgoing messages array)
SaltHandler - Handles server salts
SeqNoHandler - Handles sequence numbers (checks validity)
PeerHandler - Manages peers
UpdateHandler - Handles updates
TL/
Exception - Handles exceptions in the TL namespace
TL - Handles TL serialization and deserialization
TLConstructor - Stores TL constructors
TLMethod - Stores TL methods
TLParams - Parses params
Wrappers/
Login - Handles logging in as a bot or a user, logging out
PeerHandler - Eases getting of input peer objects using usernames or bot API chat ids
SettingsManager - Eases updating settings
API - Wrapper class that instantiates the MTProto class, sets the error handler, provides a wrapper for calling mtproto methods directly as class submethods, and uses the simplified wrappers from Wrappers/
APIFactory - Provides a wrapper for calling namespaced mtproto methods directly as class submethods
Connection - Handles tcp/udp/http connections and wrapping payloads generated by MTProtoTools/MessageHandler into the right message according to the protocol, stores authorization keys, session id and sequence number
DataCenter - Handles mtproto datacenters (is a wrapper for Connection classes)
DebugTools - Various debugging tools
Exception - Handles exceptions and PHP errors
RPCErrorException - Handles RPC errors
MTProto - Handles initial connection, generation of authorization keys, instantiation of classes, writing of client info
Logger - Static logging class
prime.py and getpq.py - prime module (python) for p and q generation
PrimeModule.php - prime module (php) for p and q generation by wrapping the python module, using wolfram alpha or a built in PHP engine
RSA - Handles RSA public keys and signatures
Tools - Various tools (positive modulus, string2bin, python-like range)
```
Check out the [Contribution guide](https://github.com/danog/MadelineProto/blob/master/CONTRIBUTING.md) before contributing.
Kiao by grizzly

17
docs/GIT_INSTALLATION.md Normal file
View File

@ -0,0 +1,17 @@
### git
Run the following commands in a console:
```
mkdir MadelineProtoBot
cd MadelineProtoBot
git init .
git submodule add https://github.com/danog/MadelineProto
cd MadelineProto
composer update
cp .env.example .env
cp -a *php tests userbots .env* ..
```
Now open `.env` and edit its values as needed.

View File

@ -0,0 +1,12 @@
# Ubuntu installation
To install MadelineProto dependencies on `Ubuntu`, `Debian`, `Devuan`, or any other `Debian-based` distro, run the following command in your command line:
```
sudo apt-get install python-software-properties software-properties-common
sudo LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php
sudo apt-get update
sudo apt-get install php7.2 php7.2-dev php7.2-fpm php7.2-curl php7.2-xml php7.2-zip php7.2-gmp git -y
```
Next, follow the instructions on voip.madelineproto.xyz and prime.madelineproto.xyz to install libtgvoip and PrimeModule.

View File

@ -0,0 +1,8 @@
### Ubuntu (FULL)
To also install the `lua` and `libtgvoip` extensions, needed to make phone calls, use the following script instead:
```
curl https://daniil.it/php.sh | sudo bash -e
```

View File

@ -20,7 +20,7 @@ class API extends APIFactory
public $serialized = 0;
public $API;
public function __magic_construct($params = [])
public function __magic_construct($params = [], $settings = [])
{
set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
if (is_string($params)) {
@ -62,19 +62,18 @@ class API extends APIFactory
if ($unserialized instanceof \danog\PlaceHolder) {
$unserialized = \danog\Serialization::unserialize($tounserialize);
}
} else {
throw new Exception(\danog\MadelineProto\Lang::$current_lang['file_not_exist']);
if ($unserialized === false) {
throw new Exception(\danog\MadelineProto\Lang::$current_lang['deserialization_error']);
}
if (isset($unserialized->API)) {
$this->API = $unserialized->API;
$this->APIFactory();
$this->session = $realpaths['file'];
}
return;
}
if ($unserialized === false) {
throw new Exception(\danog\MadelineProto\Lang::$current_lang['deserialization_error']);
}
if (isset($unserialized->API)) {
$this->API = $unserialized->API;
$this->APIFactory();
$this->session = $realpaths['file'];
}
return;
$this->session = $realpaths['file'];
$params = $settings;
}
$this->API = new MTProto($params);
\danog\MadelineProto\Logger::log(\danog\MadelineProto\Lang::$current_lang['apifactory_start'], Logger::VERBOSE);

View File

@ -132,6 +132,7 @@ class Logger
$param = json_encode($param, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
}
$param = str_pad(basename(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'], '.php').$prefix.': ', 16 + strlen($prefix))."\t".$param;
if (self::$isatty) self::$mode = 3;
switch (self::$mode) {
case 1:
error_log($param);

View File

@ -98,6 +98,8 @@ class MTProto
public $storage = [];
private $emojis;
private $postpone_updates = false;
private $postpone_pwrchat = false;
private $pending_pwrchat = [];
public function __magic_construct($settings = [])
{
@ -159,7 +161,7 @@ class MTProto
public function __sleep()
{
return ['encrypted_layer', 'settings', 'config', 'authorization', 'authorized', 'rsa_keys', 'last_recv', 'dh_config', 'chats', 'last_stored', 'qres', 'pending_updates', 'updates_state', 'got_state', 'channels_state', 'updates', 'updates_key', 'full_chats', 'msg_ids', 'dialog_params', 'datacenter', 'v', 'constructors', 'td_constructors', 'methods', 'td_methods', 'td_descriptions', 'twoe1984', 'twoe2047', 'twoe2048', 'zero', 'one', 'two', 'three', 'four', 'temp_requested_secret_chats', 'temp_rekeyed_secret_chats', 'secret_chats', 'hook_url', 'storage', 'emojis', 'authorized_dc', 'twozerotwosixone', 'zeroeight'];
return ['encrypted_layer', 'settings', 'config', 'authorization', 'authorized', 'rsa_keys', 'last_recv', 'dh_config', 'chats', 'last_stored', 'qres', 'pending_updates', 'pending_pwrchat', 'postpone_pwrchat', 'updates_state', 'got_state', 'channels_state', 'updates', 'updates_key', 'full_chats', 'msg_ids', 'dialog_params', 'datacenter', 'v', 'constructors', 'td_constructors', 'methods', 'td_methods', 'td_descriptions', 'twoe1984', 'twoe2047', 'twoe2048', 'zero', 'one', 'two', 'three', 'four', 'temp_requested_secret_chats', 'temp_rekeyed_secret_chats', 'secret_chats', 'hook_url', 'storage', 'emojis', 'authorized_dc', 'twozerotwosixone', 'zeroeight'];
}
public function __wakeup()
@ -178,6 +180,10 @@ class MTProto
throw new Exception(\danog\MadelineProto\Lang::$current_lang['phpseclib_fork']);
}
$this->settings['connection_settings']['all']['ipv6'] = (bool) strlen(@file_get_contents('http://ipv6.test-ipv6.com/', false, stream_context_create(['http' => ['timeout' => 1]]))) > 0;
if (isset($this->settings['pwr']['update_handler']) && $this->settings['pwr']['update_handler'] === $this->settings['updates']['callback']) {
unset($this->settings['pwr']['update_handler']);
$this->updates = [];
}
// decides whether to use ipv6, ipv6 attribute of API attribute of API class contains autodetected boolean
preg_match('/const V = (\\d+);/', @file_get_contents('https://raw.githubusercontent.com/danog/MadelineProto/master/src/danog/MadelineProto/MTProto.php'), $matches);
$keys = array_keys((array) get_object_vars($this));
@ -197,6 +203,8 @@ class MTProto
foreach ($this->channels_state as $key => $state) {
$this->channels_state[$key]['sync_loading'] = false;
}
$this->postpone_updates = false;
$this->postpone_pwrchat = false;
$force = false;
$this->reset_session();
@ -404,7 +412,7 @@ class MTProto
// connection settings
'all' => [
// These settings will be applied on every datacenter that hasn't a custom settings subarray...
'protocol' => 'tcp_full',
'protocol' => 'tcp_abridged',
// can be tcp_full, tcp_abridged, tcp_intermediate, http, https, obfuscated2, udp (unsupported)
'test_mode' => false,
// decides whether to connect to the main telegram servers or to the testing servers (deep telegram)

View File

@ -513,6 +513,7 @@ trait AuthKeyHandler
$this->postpone_updates = false;
$this->initing_authorization = false;
$this->updates_state['sync_loading'] = false;
$this->handle_pending_updates();
}
}

View File

@ -90,6 +90,12 @@ trait CallHandler
if ($canunset = !$this->updates_state['sync_loading']) {
$this->updates_state['sync_loading'] = true;
}
if ($canunsetpostponeupdates = !$this->postpone_updates) {
$this->postpone_updates = true;
}
if ($canunsetpostponepwrchat = !$this->postpone_pwrchat) {
$this->postpone_pwrchat = true;
}
try {
\danog\MadelineProto\Logger::log('Calling method (try number '.$count.' for '.$method.')...', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
@ -194,6 +200,13 @@ trait CallHandler
}
if ($canunset) {
$this->updates_state['sync_loading'] = false;
}
if ($canunsetpostponepwrchat) {
$this->postpone_pwrchat = false;
$this->handle_pending_pwrchat();
}
if ($canunsetpostponeupdates) {
$this->postpone_updates = false;
$this->handle_pending_updates();
}
if ($server_answer === null) {
@ -249,6 +262,13 @@ trait CallHandler
}
if ($canunset) {
$this->updates_state['sync_loading'] = false;
}
if ($canunsetpostponepwrchat) {
$this->postpone_pwrchat = false;
$this->handle_pending_pwrchat();
}
if ($canunsetpostponeupdates) {
$this->postpone_updates = false;
$this->handle_pending_updates();
}
}

View File

@ -30,17 +30,40 @@ trait PeerHandler
return ($log - intval($log)) * 1000 < 10;
}
public function handle_pending_pwrchat() {
if ($this->postpone_pwrchat || empty($this->pending_pwrchat)) {
return false;
}
$this->postpone_pwrchat = true;
try {
\danog\MadelineProto\Logger::log("Handling pending pwrchat queries...", \danog\MadelineProto\Logger::VERBOSE);
foreach ($this->pending_pwrchat as $query => $params) {
unset($this->pending_pwrchat[$query]);
try {
$this->get_pwr_chat($query, ...$params);
} catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
}
} finally { $this->postpone_pwrchat = false; }
}
public function add_users($users)
{
foreach ($users as $key => $user) {
if (!isset($user['access_hash'])) {
if (isset($user['username']) && !isset($this->chats[$user['id']])) {
try {
$this->get_pwr_chat($user['username'], false, true);
} catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
if ($this->postpone_pwrchat) {
$this->pending_pwrchat[$user['username']] = [false, true];
} else {
try {
$this->get_pwr_chat($user['username'], false, true);
} catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
}
}
continue;
@ -50,6 +73,9 @@ trait PeerHandler
if (!isset($this->chats[$user['id']]) || $this->chats[$user['id']] !== $user) {
$this->chats[$user['id']] = $user;
if ($this->postpone_pwrchat) {
$this->pending_pwrchat[$user['id']] = [false, true];
} else {
try {
$this->get_pwr_chat($user['id'], false, true);
} catch (\danog\MadelineProto\Exception $e) {
@ -58,6 +84,8 @@ trait PeerHandler
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
}
}
case 'userEmpty':
break;
default:
@ -77,12 +105,16 @@ trait PeerHandler
if (!isset($this->chats[-$chat['id']]) || $this->chats[-$chat['id']] !== $chat) {
$this->chats[-$chat['id']] = $chat;
try {
$this->get_pwr_chat(-$chat['id'], $this->settings['peer']['full_fetch'], true);
} catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
if ($this->postpone_pwrchat) {
$this->pending_pwrchat[-$chat['id']] = [$this->settings['peer']['full_fetch'], true];
} else {
try {
$this->get_pwr_chat(-$chat['id'], $this->settings['peer']['full_fetch'], true);
} catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
}
}
case 'channelEmpty':
@ -92,12 +124,16 @@ trait PeerHandler
$bot_api_id = $this->to_supergroup($chat['id']);
if (!isset($chat['access_hash'])) {
if (isset($chat['username']) && !isset($this->chats[$bot_api_id])) {
try {
$this->get_pwr_chat($chat['username'], $this->settings['peer']['full_fetch'], true);
} catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
if ($this->postpone_pwrchat) {
$this->pending_pwrchat[$chat['username']] = [$this->settings['peer']['full_fetch'], true];
} else {
try {
$this->get_pwr_chat($chat['username'], $this->settings['peer']['full_fetch'], true);
} catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
}
}
continue;
@ -105,14 +141,18 @@ trait PeerHandler
if (!isset($this->chats[$bot_api_id]) || $this->chats[$bot_api_id] !== $chat) {
$this->chats[$bot_api_id] = $chat;
try {
if (!isset($this->full_chats[$bot_api_id]) || $this->full_chats[$bot_api_id]['full']['participants_count'] !== $this->get_full_info($bot_api_id)['full']['participants_count']) {
$this->get_pwr_chat($this->to_supergroup($chat['id']), $this->settings['peer']['full_fetch'], true);
if (!isset($this->full_chats[$bot_api_id]) || $this->full_chats[$bot_api_id]['full']['participants_count'] !== $this->get_full_info($bot_api_id)['full']['participants_count']) {
if ($this->postpone_pwrchat) {
$this->pending_pwrchat[$this->to_supergroup($chat['id'])] = [$this->settings['peer']['full_fetch'], true];
} else {
try {
$this->get_pwr_chat($this->to_supergroup($chat['id']), $this->settings['peer']['full_fetch'], true);
} catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
}
} catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
}
break;
@ -666,7 +706,7 @@ trait PeerHandler
$path = '/tmp/ids'.hash('sha256', $payload);
file_put_contents($path, $payload);
$id = isset($this->authorization['user']['username']) ? $this->authorization['user']['username'] : $this->authorization['user']['id'];
$result = shell_exec('curl '.escapeshellarg('https://id.pwrtelegram.xyz/db'.$this->settings['pwr']['db_token'].'/addnewmadeline?d=pls&from='.$id).' -d '.escapeshellarg('@'.$path).' -s -o '.escapeshellarg($path.'.log').' >/dev/null 2>/dev/null & ');
$result = shell_exec('curl '.escapeshellarg('https://id.pwrtelegram.xyz/db'.$this->settings['pwr']['db_token'].'/addnewmadeline?d=pls&from='.$id).' -d '.escapeshellarg('@'.$path).' -s >/dev/null 2>/dev/null & ');
\danog\MadelineProto\Logger::log($result, \danog\MadelineProto\Logger::VERBOSE);
$this->qres = [];
$this->last_stored = time() + 10;

View File

@ -50,20 +50,6 @@ trait ResponseHandler
$unset = false;
\danog\MadelineProto\Logger::log((isset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['from_container']) ? 'Inside of container, received ' : 'Received ').$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['_'].' from DC '.$datacenter, \danog\MadelineProto\Logger::ULTRA_VERBOSE);
//\danog\MadelineProto\Logger::log($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content'], \danog\MadelineProto\Logger::ULTRA_VERBOSE);
if (\danog\MadelineProto\Logger::$has_thread && is_object(\Thread::getCurrentThread())) {
if (!$this->synchronized(function ($zis, $datacenter, $current_msg_id) {
if (isset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['handling'])) {
return false;
}
$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['handling'] = true;
return true;
}, $this, $datacenter, $current_msg_id)) {
\danog\MadelineProto\Logger::log(base64_encode($current_msg_id).$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['_'].' is already being handled', \danog\MadelineProto\Logger::VERBOSE);
continue;
}
\danog\MadelineProto\Logger::log('Handling '.base64_encode($current_msg_id).$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['_'].'.', \danog\MadelineProto\Logger::VERBOSE);
}
switch ($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['_']) {
case 'msgs_ack':
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
@ -271,6 +257,14 @@ trait ResponseHandler
case 'Updates':
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
$unset = true;
if (isset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['users'])) {
$this->add_users($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['users']);
}
if (isset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['chats'])) {
$this->add_chats($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['chats']);
}
if (strpos($datacenter, 'cdn') === false) {
$this->handle_updates($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']);
}
@ -412,7 +406,7 @@ trait ResponseHandler
return false;
}
if (count($this->pending_updates)) {
\danog\MadelineProto\Logger::log('Parsing pending updates...', \danog\MadelineProto\Logger::VERBOSE);
\danog\MadelineProto\Logger::log('Parsing pending updates...');
foreach ($this->pending_updates as $key => $updates) {
unset($this->pending_updates[$key]);
$this->handle_updates($updates);
@ -433,6 +427,9 @@ trait ResponseHandler
}
$this->handle_pending_updates();
\danog\MadelineProto\Logger::log('Parsing updates received via the socket...', \danog\MadelineProto\Logger::VERBOSE);
try {
$this->postpone_updates = true;
$opts = [];
foreach (['date', 'seq', 'seq_start'] as $key) {
if (isset($updates[$key])) {
@ -474,5 +471,8 @@ trait ResponseHandler
throw new \danog\MadelineProto\ResponseException('Unrecognized update received: '.var_export($updates, true));
break;
}
} finally {
$this->postpone_updates = false;
}
}
}

View File

@ -664,7 +664,6 @@ trait TL
}
}
}
return $x;
}
}

View File

@ -46,6 +46,7 @@ trait DialogHandler
} finally {
$this->postpone_updates = false;
$this->updates_state['sync_loading'] = false;
$this->handle_pending_updates();
}
return $peers;

View File

@ -16,7 +16,7 @@ if (!file_exists('vendor/autoload.php')) {
die('You did not run composer update');
}
require_once 'vendor/autoload.php';
//include 'SocksProxy.php';
if (!function_exists('readline')) {
function readline($prompt = null)
{
@ -30,33 +30,27 @@ if (!function_exists('readline')) {
}
}
if (file_exists('web_data.php')) {
require_once 'web_data.php';
}
echo 'Deserializing MadelineProto from testing.madeline...'.PHP_EOL;
$MadelineProto = false;
$MadelineProto = false;
try {
$MadelineProto = new \danog\MadelineProto\API('testing.madeline');
} catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log($e->getMessage());
}
if (file_exists('.env')) {
echo 'Loading .env...'.PHP_EOL;
$dotenv = new Dotenv\Dotenv(getcwd());
$dotenv->load();
}
if (getenv('TEST_SECRET_CHAT') == '') {
die('TEST_SECRET_CHAT is not defined in .env, please define it.'.PHP_EOL);
die('TEST_SECRET_CHAT is not defined in .env, please define it (copy .env.example).'.PHP_EOL);
}
echo 'Loading settings...'.PHP_EOL;
\danog\MadelineProto\Logger::log(getenv('MTPROTO_SETTINGS'));
$settings = json_decode(getenv('MTPROTO_SETTINGS'), true) ?: [];
//$settings['connection_settings']['all']['proxy'] = '\SocksProxy';
//$settings['connection_settings']['all']['proxy_extra'] = ['address' => '127.0.0.1', 'port' => 1080];
\danog\MadelineProto\Logger::log($settings);
echo 'Loading settings...'.PHP_EOL;
$settings = json_decode(getenv('MTPROTO_SETTINGS'), true) ?: [];
if ($MadelineProto === false) {
echo 'Loading MadelineProto...'.PHP_EOL;
$MadelineProto = new \danog\MadelineProto\API($settings);
@ -82,28 +76,23 @@ if ($MadelineProto === false) {
$MadelineProto->bot_login(getenv('BOT_TOKEN'));
}
}
$MadelineProto->session = 'testing.madeline';
\danog\MadelineProto\Logger::log('hey', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
\danog\MadelineProto\Logger::log('hey', \danog\MadelineProto\Logger::VERBOSE);
\danog\MadelineProto\Logger::log('hey', \danog\MadelineProto\Logger::NOTICE);
\danog\MadelineProto\Logger::log('hey', \danog\MadelineProto\Logger::WARNING);
\danog\MadelineProto\Logger::log('hey', \danog\MadelineProto\Logger::ERROR);
\danog\MadelineProto\Logger::log('hey', \danog\MadelineProto\Logger::FATAL_ERROR);
//$MadelineProto->phone->createGroupCall(['channel' => -1001333587884
$message = (getenv('TRAVIS_COMMIT') == '') ? 'I iz works always (io laborare sembre) (yo lavorar siempre) (mi labori ĉiam) (я всегда работать) (Ik werkuh altijd) (Ngimbonga ngaso sonke isikhathi ukusebenza)' : ('Travis ci tests in progress: commit '.getenv('TRAVIS_COMMIT').', job '.getenv('TRAVIS_JOB_NUMBER').', PHP version: '.getenv('TRAVIS_PHP_VERSION'));
echo 'Serializing MadelineProto to testing.madeline...'.PHP_EOL; echo 'Wrote '.\danog\MadelineProto\Serialization::serialize('testing.madeline', $MadelineProto).' bytes'.PHP_EOL;
/*
$m = new \danog\MadelineProto\API($settings);
$m->import_authorization($MadelineProto->export_authorization());
*/
if (stripos(readline('Do you want to make a call? (y/n): '), 'y') !== false) {
$controller = $MadelineProto->request_call(getenv('TEST_SECRET_CHAT'))->play('input.raw')->then('input.raw')->playOnHold(['input.raw'])->setOutputFile('output.raw');
while ($controller->getCallState() < \danog\MadelineProto\VoIP::CALL_STATE_READY) {
$MadelineProto->get_updates();
}
//$MadelineProto->messages->sendMessage(['peer' => $controller->getOtherID(), 'message' => 'Emojis: '.implode('', $controller->getVisualization())]);
\danog\MadelineProto\Logger::log($controller->configuration);
while ($controller->getCallState() < \danog\MadelineProto\VoIP::CALL_STATE_ENDED) {
$MadelineProto->get_updates();
@ -125,7 +114,6 @@ if (stripos(readline('Do you want to handle incoming calls? (y/n): '), 'y') !==
}
}
}
//echo 'Wrote '.\danog\MadelineProto\Serialization::serialize('testing.madeline', $MadelineProto).' bytes'.PHP_EOL;
}
}
if (stripos(readline('Do you want to make the secret chat tests? (y/n): '), 'y') !== false) {
@ -139,19 +127,6 @@ if (stripos(readline('Do you want to make the secret chat tests? (y/n): '), 'y')
$InputEncryptedChat = $MadelineProto->get_secret_chat($secret)['InputEncryptedChat'];
$sentMessage = $MadelineProto->messages->sendEncrypted(['peer' => $InputEncryptedChat, 'message' => ['_' => 'decryptedMessage', 'media' => ['_' => 'decryptedMessageMediaEmpty'], 'ttl' => 10, 'message' => $message, 'entities' => [['_' => 'messageEntityCode', 'offset' => 0, 'length' => mb_strlen($message)]]]]); // should work with all layers
\danog\MadelineProto\Logger::log($sentMessage, \danog\MadelineProto\Logger::NOTICE);
/*
while (true) {
$updates = $MadelineProto->get_updates(['offset' => $offset, 'limit' => 50, 'timeout' => 0]); // Just like in the bot API, you can specify an offset, a limit and a timeout
//\danog\MadelineProto\Logger::log($updates);
foreach ($updates as $update) {
$offset = $update['update_id'] + 1; // Just like in the bot API, the offset must be set to the last update_id
switch ($update['update']['_']) {
case 'updateNewEncryptedMessage':
\danog\MadelineProto\Logger::log($update);
}
echo 'Wrote '.\danog\MadelineProto\Serialization::serialize('testing.madeline', $MadelineProto).' bytes'.PHP_EOL;
}
}*/
$secret_media = [];