Skip to content

Instantly share code, notes, and snippets.

@giuseppeM99
Created March 15, 2021 17:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save giuseppeM99/c3c3aebac819dd0508d538bce5bf8863 to your computer and use it in GitHub Desktop.
Save giuseppeM99/c3c3aebac819dd0508d538bce5bf8863 to your computer and use it in GitHub Desktop.
<?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