Created
March 15, 2021 17:34
-
-
Save giuseppeM99/c3c3aebac819dd0508d538bce5bf8863 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* HashUtils | |
* Decode and encode telegram file ids, inline message ids and more | |
* This code is under GPLv3 license, see <https://www.gnu.org/licenses/#GPL>. | |
* | |
* @author Sys-001 <https://github.com/sys-001> | |
* @author Giuseppe Marino <https://github.com/giuseppeM99/> | |
* @copyright 2018 Sys-001 | |
* @copyright 2018-2021 Giuseppe Marino | |
* @license https://opensource.org/licenses/GPL-3.0 GPLv3 | |
* | |
*/ | |
class HashUtil | |
{ | |
public const WEB_LOCATION_FLAG = 1 << 24; | |
public const FILE_REFERENCE_FLAG = 1 << 25; | |
public const THUMBNAIL = 0x00; | |
public const PROFILE_PHOTO = 0x01; | |
public const PHOTO = 0x02; | |
public const VOICE = 0x03; | |
public const VIDEO = 0x04; | |
public const DOCUMENT = 0x05; | |
public const ENCRYPTED = 0x06; | |
public const TEMP = 0x07; | |
public const STICKER = 0x08; | |
public const AUDIO = 0x09; | |
public const GIF = 0x0A; | |
public const ENCRYPTED_THUMBNAIL = 0x0B; | |
public const WALLPAPER = 0x0C; | |
public const VIDEO_NOTE = 0x0D; | |
public const SECURE_RAW = 0x0F; | |
public const SECURE = 0x10; | |
public const BACKGROUND = 0x11; | |
public const SIZE = 0x12; | |
/** | |
* @param string $type | |
* @param int $dc_id | |
* @param int $id | |
* @param int $access_hash | |
* @param int|null $volume_id | |
* @param int|null $secret | |
* @param int|null $local_id | |
* @return string | |
* @throws Exception | |
*/ | |
public static function encodeFileIdInfo( | |
string $type, | |
int $dc_id, | |
int $id, | |
int $access_hash, | |
int $volume_id = null, | |
int $secret = null, | |
int $local_id = null | |
) { | |
switch ($type) { | |
case 'Thumb': | |
$file_type = self::THUMBNAIL; | |
case 'Photo': | |
if (empty($file_type)) { | |
$file_type = self::PHOTO; | |
} | |
if (empty($volume_id) or empty($secret) or empty($local_id)) { | |
throw new Exception("Missing required parameters for file type"); | |
} | |
$file_id = pack('VVqqqqV', $file_type, $dc_id, $id, $access_hash, $volume_id, $secret, $local_id); | |
break; | |
case 'Voice': | |
$file_type = self::VOICE; | |
case 'Video': | |
if (empty($file_type)) { | |
$file_type = self::VIDEO; | |
} | |
case 'Document': | |
if (empty($file_type)) { | |
$file_type = self::DOCUMENT; | |
} | |
case 'Sticker': | |
if (empty($file_type)) { | |
$file_type = self::STICKER; | |
} | |
case 'Audio': | |
if (empty($file_type)) { | |
$file_type = self::AUDIO; | |
} | |
case 'Animation': | |
if (empty($file_type)) { | |
$file_type = self::GIF; | |
} | |
case 'VideoNote': | |
if (empty($file_type)) { | |
$file_type = self::VIDEO_NOTE; | |
} | |
$file_id = pack('VVqq', $file_type, $dc_id, $id, $access_hash); | |
break; | |
default: | |
$file_id = null; | |
break; | |
} | |
return self::base64UrlEncode(self::rleEncode($file_id . chr(2))); | |
} | |
/** | |
* @param string $string | |
* @return string | |
*/ | |
protected static function base64UrlEncode(string $string): string | |
{ | |
return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($string)); | |
} | |
/** | |
* @param string $string | |
* @return string | |
*/ | |
protected static function rleEncode(string $string): string | |
{ | |
$new = ''; | |
$count = 0; | |
$null = chr(0); | |
foreach (str_split($string) as $cur) { | |
if ($cur === $null) { | |
$count++; | |
} else { | |
if ($count > 0) { | |
$new .= $null . chr($count); | |
$count = 0; | |
} | |
$new .= $cur; | |
} | |
} | |
return $new; | |
} | |
/** | |
* @param string $file_id | |
* @return array | |
*/ | |
public static function decodeFileIdInfo(string $file_id) | |
{ | |
$file_info = self::rleDecode(self::base64UrlDecode($file_id)); | |
$file_type = unpack('C', substr($file_info, 0, 1))[1]; | |
$file_flags = unpack('V', chr(0) . substr($file_info, 1, 3))[1]; | |
$version = \ord($file_info[\strlen($file_info) - 1]); | |
$subVersion = $version === 4 ? \ord($file_info[\strlen($file_info) - 2]) : 0; | |
$file_info = substr($file_info, 4); | |
$dc_id = unpack('V', $file_info)[1]; | |
$file_info = substr($file_info, 4); | |
switch ($file_type) { | |
case self::THUMBNAIL: | |
if (empty($type)) { | |
$type = 'Thumb'; | |
} | |
case self::PROFILE_PHOTO: | |
if (empty($type)) { | |
$type = 'profile_photo'; | |
} | |
case self::PHOTO: | |
if (empty($type)) { | |
$type = 'Photo'; | |
} | |
$infostring = 'qid/qaccess_hash/qvolume_id/qsecret/Vrobastrana/Vlocal_id'; | |
//$local_id = unpack('V', substr($file_info, -6))[1]; | |
break; | |
case self::VOICE: | |
$type = 'Voice'; | |
case self::VIDEO: | |
if (empty($type)) { | |
$type = 'Video'; | |
} | |
case self::DOCUMENT: | |
if (empty($type)) { | |
$type = 'Document'; | |
} | |
case self::ENCRYPTED: | |
if (empty($type)) { | |
$type = 'Encrypted'; | |
} | |
case self::TEMP: | |
if (empty($type)) { | |
$type = 'Temp'; | |
} | |
case self::STICKER: | |
if (empty($type)) { | |
$type = 'Sticker'; | |
} | |
case self::AUDIO: | |
if (empty($type)) { | |
$type = 'Audio'; | |
} | |
case self::GIF: | |
if (empty($type)) { | |
$type = 'Animation'; | |
} | |
case self::ENCRYPTED_THUMBNAIL: | |
if (empty($type)) { | |
$type = 'EncryptedThumbnail'; | |
} | |
case self::WALLPAPER: | |
if (empty($type)) { | |
$type = 'Wallpaper'; | |
} | |
case self::VIDEO_NOTE: | |
if (empty($type)) { | |
$type = 'VideoNote'; | |
} | |
//case 0x0E: | |
// :shrug: | |
case self::SECURE_RAW: | |
if (empty($type)) { | |
$type = 'SecureRaw'; | |
} | |
case self::SECURE: | |
if (empty($type)) { | |
$type = 'Secure'; | |
} | |
case self::BACKGROUND: | |
if (empty($type)) { | |
$type = 'Background'; | |
} | |
case self::SIZE: | |
if (empty($type)) { | |
$type = 'Size'; | |
} | |
$infostring = 'qid/qaccess_hash'; | |
break; | |
default: | |
$infostring = ''; | |
$type = $file_type; | |
break; | |
} | |
if ($file_flags & self::FILE_REFERENCE_FLAG) { | |
$frlen = ord($file_info[0]); | |
$file_info[0] = chr(0); | |
if ($frlen == 254) { | |
$frlen = unpack('V', $file_info)[1]; | |
$pad = self::posmod(-$frlen, 4); | |
} else { | |
$pad = self::posmod(-$frlen, -4); | |
} | |
$file_reference = substr($file_info, 0, $frlen); | |
$file_info = substr($file_info, $frlen+$pad); | |
} | |
$decrypted_info = unpack($infostring, $file_info); | |
$decrypted_info['type'] = $type; | |
$decrypted_info['dc_id'] = $dc_id; | |
$decrypted_info['version'] = "$version.$subVersion"; | |
$decrypted_info['flags'] = $file_flags; | |
if (isset($file_reference)) { | |
$decrypted_info['file_reference'] = self::base64UrlEncode($file_reference); | |
} | |
if (isset($local_id)) { | |
$decrypted_info['local_id'] = $local_id; | |
} | |
return $decrypted_info; | |
} | |
/** | |
* @param string $string | |
* @return string | |
*/ | |
protected static function rleDecode(string $string): string | |
{ | |
$new = ''; | |
$last = ''; | |
$null = chr(0); | |
foreach (str_split($string) as $cur) { | |
if ($last === $null) { | |
$new .= str_repeat($last, ord($cur)); | |
$last = ''; | |
} else { | |
$new .= $last; | |
$last = $cur; | |
} | |
} | |
return $new . $last; | |
} | |
/** | |
* @param string $string | |
* @return string | |
*/ | |
protected static function base64UrlDecode(string $string): string | |
{ | |
return base64_decode(str_pad(strtr($string, '-_', '+/'), strlen($string) % 4, '=', STR_PAD_RIGHT)); | |
} | |
/** | |
* @param int $chat_creator | |
* @param int $chat_id | |
* @param int $access_hash | |
* @return string | |
*/ | |
public static function encodeChatInviteHashInfo(int $chat_creator, int $chat_id, int $access_hash): string | |
{ | |
$inverted_hash = pack('q', $access_hash); | |
$inverted_hash = unpack('q', strrev($inverted_hash))[1]; | |
if (strpos((string)$chat_id, '-100') === 0) { | |
$chat_id = substr($chat_id, 4); | |
$chat_id = (int)$chat_id; | |
} else { | |
$chat_id = substr($chat_id, 1); | |
} | |
$chat_id = (int)$chat_id; | |
$chat_invite_hash = pack('NNq', $chat_creator, $chat_id, $inverted_hash); | |
return self::base64UrlEncode($chat_invite_hash); | |
} | |
/** | |
* @param string $chat_invite_hash | |
* @return array | |
*/ | |
public static function decodeChatInviteHashInfo(string $chat_invite_hash): array | |
{ | |
$chat_invite_info = self::base64UrlDecode($chat_invite_hash); | |
$decrypted_info = unpack('Nchat_creator/Nchat_id/qaccess_hash', $chat_invite_info); | |
if (strlen($decrypted_info['chat_id']) < 10) { | |
$chat_id = '-' . $decrypted_info['chat_id']; | |
$decrypted_info['chat_id'] = (int)$chat_id; | |
return $decrypted_info; | |
} | |
$chat_id = '-100' . $decrypted_info['chat_id']; | |
$decrypted_info['chat_id'] = (int)$chat_id; | |
$real_hash = pack('q', $decrypted_info['access_hash']); | |
$decrypted_info['access_hash'] = unpack('q', strrev($real_hash))[1]; | |
return $decrypted_info; | |
} | |
/** | |
* @param int $dc_id | |
* @param int $message_id | |
* @param int $chat_id | |
* @param int $access_hash | |
* @return string | |
*/ | |
public static function encodeInlineMessageIdInfo( | |
int $dc_id, | |
int $message_id, | |
int $chat_id, | |
int $access_hash | |
): string { | |
if ($chat_id < 0) { | |
$chat_id = substr($chat_id, 4); | |
$chat_id = (int)$chat_id * -1; | |
} | |
$inline_message_id = pack('VVlq', $dc_id, $message_id, $chat_id, $access_hash); | |
return self::base64UrlEncode($inline_message_id); | |
} | |
/** | |
* @param string $inline_message_id | |
* @return array | |
*/ | |
public static function decodeInlineMessageIdInfo(string $inline_message_id): array | |
{ | |
$inline_message_info = self::base64UrlDecode($inline_message_id); | |
$decrypted_info = unpack('Vdc_id/Vmessage_id/lchat_id/qaccess_hash', $inline_message_info); | |
if ($decrypted_info['chat_id'] < 0) { | |
$chat_id = '-100' . ($decrypted_info['chat_id'] * -1); | |
$decrypted_info['chat_id'] = (int)$chat_id; | |
return $decrypted_info; | |
} | |
$decrypted_info['user_id'] = $decrypted_info['chat_id']; | |
unset($decrypted_info['chat_id']); | |
return $decrypted_info; | |
} | |
/** | |
* @param int $a | |
* @param int $b | |
* @return int | |
*/ | |
public static function posmod(int $a, int $b): int | |
{ | |
$resto = $a % $b; | |
return $resto < 0 ? $resto + \abs($b) : $resto; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment