Properly handle FILE_REFERENCE_EXPIRED (#1772)

This commit is contained in:
Alexander Zinchuk 2022-03-25 13:15:15 +01:00
parent 6e0f8de4fc
commit 20f710b345
4 changed files with 134 additions and 43 deletions

View File

@ -21,7 +21,7 @@ type GramJsAppConfig = {
function buildEmojiSounds(appConfig: GramJsAppConfig) {
const { emojies_sounds } = appConfig;
return Object.keys(emojies_sounds).reduce((acc: Record<string, string>, key) => {
return emojies_sounds ? Object.keys(emojies_sounds).reduce((acc: Record<string, string>, key) => {
const l = emojies_sounds[key];
localDb.documents[l.id] = new GramJs.Document({
id: BigInt(l.id),
@ -35,7 +35,7 @@ function buildEmojiSounds(appConfig: GramJsAppConfig) {
acc[key] = l.id;
return acc;
}, {});
}, {}) : {};
}
export function buildApiConfig(json: GramJs.TypeJSONValue): ApiAppConfig {

View File

@ -21,10 +21,11 @@ import {
} from './auth';
import { updater } from '../updater';
import { setMessageBuilderCurrentUserId } from '../apiBuilders/messages';
import downloadMediaWithClient from './media';
import downloadMediaWithClient, { parseMediaUrl } from './media';
import { buildApiUserFromFull } from '../apiBuilders/users';
import localDb from '../localDb';
import { buildApiPeerId } from '../apiBuilders/peers';
import { addMessageToLocalDb } from '../helpers';
const DEFAULT_USER_AGENT = 'Unknown UserAgent';
const DEFAULT_PLATFORM = 'Unknown platform';
@ -265,7 +266,21 @@ export function downloadMedia(
args: { url: string; mediaFormat: ApiMediaFormat; start?: number; end?: number; isHtmlAllowed?: boolean },
onProgress?: ApiOnProgress,
) {
return downloadMediaWithClient(args, client, isConnected, onProgress);
return downloadMediaWithClient(args, client, isConnected, onProgress).catch(async (err) => {
if (err.message.startsWith('FILE_REFERENCE')) {
const isFileReferenceRepaired = await repairFileReference({ url: args.url });
if (!isFileReferenceRepaired) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.error('Failed to repair file reference', args.url);
}
return undefined;
}
return downloadMediaWithClient(args, client, isConnected, onProgress);
}
return undefined;
});
}
export function uploadFile(file: File, onProgress?: ApiOnProgress) {
@ -338,3 +353,50 @@ async function handleTerminatedSession() {
}
}
}
export async function repairFileReference({
url,
}: {
url: string;
}) {
const parsed = parseMediaUrl(url);
if (!parsed) return undefined;
const {
entityType, entityId, mediaMatchType,
} = parsed;
if (mediaMatchType === 'file') {
return false;
}
if (entityType === 'msg') {
const entity = localDb.messages[entityId]!;
const messageId = entity.id;
const peer = 'channelId' in entity.peerId ? new GramJs.InputChannel({
channelId: entity.peerId.channelId,
accessHash: (localDb.chats[buildApiPeerId(entity.peerId.channelId, 'channel')] as GramJs.Channel).accessHash!,
}) : undefined;
const result = await invokeRequest(
peer
? new GramJs.channels.GetMessages({
channel: peer,
id: [new GramJs.InputMessageID({ id: messageId })],
})
: new GramJs.messages.GetMessages({
id: [new GramJs.InputMessageID({ id: messageId })],
}),
);
if (!result || result instanceof GramJs.messages.MessagesNotModified) return false;
const message = result.messages[0];
if (message instanceof GramJs.MessageEmpty) return false;
addMessageToLocalDb(message);
return true;
}
return false;
}

View File

@ -1,5 +1,5 @@
export {
destroy, disconnect, downloadMedia, fetchCurrentUser,
destroy, disconnect, downloadMedia, fetchCurrentUser, repairFileReference,
} from './client';
export {

View File

@ -11,13 +11,8 @@ import {
MEDIA_CACHE_NAME_AVATARS,
} from '../../../config';
import localDb from '../localDb';
import { getEntityTypeById } from '../gramjsBuilders';
import * as cacheApi from '../../../util/cacheApi';
type EntityType = (
'msg' | 'sticker' | 'wallpaper' | 'gif' | 'channel' | 'chat' | 'user' | 'photo' | 'stickerSet' | 'webDocument' |
'document'
);
import { getEntityTypeById } from '../gramjsBuilders';
const MEDIA_ENTITY_TYPES = new Set(['msg', 'sticker', 'gif', 'wallpaper', 'photo', 'webDocument', 'document']);
const TGS_MIME_TYPE = 'application/x-tgsticker';
@ -64,6 +59,11 @@ export default async function downloadMedia(
};
}
export type EntityType = (
'msg' | 'sticker' | 'wallpaper' | 'gif' | 'channel' | 'chat' | 'user' | 'photo' | 'stickerSet' | 'webDocument' |
'document'
);
async function download(
url: string,
client: TelegramClient,
@ -74,19 +74,16 @@ async function download(
mediaFormat?: ApiMediaFormat,
isHtmlAllowed?: boolean,
) {
const mediaMatch = url.startsWith('staticMap')
? url.match(/(staticMap):([0-9-]+)(\?.+)/)
: url.startsWith('webDocument')
? url.match(/(webDocument):(.+)/)
: url.match(
/(avatar|profile|photo|msg|stickerSet|sticker|wallpaper|gif|file|document)([-\d\w./]+)(?::\d+)?(\?size=\w+)?/,
);
if (!mediaMatch) {
return undefined;
}
const parsed = parseMediaUrl(url);
if (mediaMatch[1] === 'file') {
const response = await fetch(mediaMatch[2]);
if (!parsed) return undefined;
const {
entityType, entityId, sizeType, params, mediaMatchType,
} = parsed;
if (entityType === 'file') {
const response = await fetch(entityId);
const data = await response.arrayBuffer();
return { data };
}
@ -95,18 +92,8 @@ async function download(
return Promise.reject(new Error('ERROR: Client is not connected'));
}
let entityType: EntityType;
const entityId: string | number = mediaMatch[2];
const sizeType = mediaMatch[3] ? mediaMatch[3].replace('?size=', '') : undefined;
let entity: (
GramJs.User | GramJs.Chat | GramJs.Channel | GramJs.Photo |
GramJs.Message | GramJs.MessageService |
GramJs.Document | GramJs.StickerSet | GramJs.TypeWebDocument | undefined
);
if (mediaMatch[1] === 'staticMap') {
const accessHash = mediaMatch[2];
const params = mediaMatch[3];
if (entityType === 'staticMap') {
const accessHash = entityId;
const parsedParams = new URLSearchParams(params);
const long = parsedParams.get('long');
const lat = parsedParams.get('lat');
@ -123,13 +110,11 @@ async function download(
};
}
if (mediaMatch[1] === 'avatar' || mediaMatch[1] === 'profile') {
entityType = getEntityTypeById(entityId);
} else {
entityType = mediaMatch[1] as (
'msg' | 'sticker' | 'wallpaper' | 'gif' | 'stickerSet' | 'photo' | 'webDocument' | 'document'
);
}
let entity: (
GramJs.User | GramJs.Chat | GramJs.Channel | GramJs.Photo |
GramJs.Message | GramJs.MessageService |
GramJs.Document | GramJs.StickerSet | GramJs.TypeWebDocument | undefined
);
switch (entityType) {
case 'channel':
@ -209,7 +194,7 @@ async function download(
return { mimeType, data };
} else {
const data = await client.downloadProfilePhoto(entity, mediaMatch[1] === 'profile');
const data = await client.downloadProfilePhoto(entity, mediaMatchType === 'profile');
const mimeType = getMimeType(data);
return { mimeType, data };
@ -310,3 +295,47 @@ function getMimeType(data: Uint8Array, fallbackMimeType = 'image/jpeg') {
return type;
}
export function parseMediaUrl(url: string) {
const mediaMatch = url.startsWith('staticMap')
? url.match(/(staticMap):([0-9-]+)(\?.+)/)
: url.startsWith('webDocument')
? url.match(/(webDocument):(.+)/)
: url.match(
/(avatar|profile|photo|msg|stickerSet|sticker|wallpaper|gif|file|document)([-\d\w./]+)(?::\d+)?(\?size=\w+)?/,
);
if (!mediaMatch) {
return undefined;
}
const mediaMatchType = mediaMatch[1];
const entityId: string | number = mediaMatch[2];
if (mediaMatchType === 'file') {
return {
mediaMatchType,
entityType: 'file',
entityId,
};
}
let entityType: EntityType;
const params = mediaMatch[3];
const sizeType = params?.replace('?size=', '') || undefined;
if (mediaMatch[1] === 'avatar' || mediaMatch[1] === 'profile') {
entityType = getEntityTypeById(entityId);
} else {
entityType = mediaMatch[1] as (
'msg' | 'sticker' | 'wallpaper' | 'gif' | 'stickerSet' | 'photo' | 'webDocument' | 'document'
);
}
return {
mediaMatchType,
entityType,
entityId,
sizeType,
params,
};
}