tweb/src/lib/appManagers/appDocsManager.ts

400 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*
* Originally from:
* https://github.com/zhukov/webogram
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
import type {ThumbCache} from '../storages/thumbs';
import {AccountWallPapers, Document, DocumentAttribute, MessagesSavedGifs, PhotoSize, WallPaper} from '../../layer';
import {ReferenceContext} from '../mtproto/referenceDatabase';
import {getFullDate} from '../../helpers/date';
import isObject from '../../helpers/object/isObject';
import safeReplaceArrayInObject from '../../helpers/object/safeReplaceArrayInObject';
import {AppManager} from './manager';
import wrapPlainText from '../richTextProcessor/wrapPlainText';
import assumeType from '../../helpers/assumeType';
import {getEnvironment} from '../../environment/utils';
import {isServiceWorkerOnline} from '../mtproto/mtproto.worker';
import MTProtoMessagePort from '../mtproto/mtprotoMessagePort';
import getDocumentInputFileLocation from './utils/docs/getDocumentInputFileLocation';
import getDocumentURL from './utils/docs/getDocumentURL';
import makeError from '../../helpers/makeError';
import {EXTENSION_MIME_TYPE_MAP} from '../../environment/mimeTypeMap';
import {THUMB_TYPE_FULL} from '../mtproto/mtproto_config';
export type MyDocument = Document.document;
// TODO: если залить картинку файлом, а потом перезайти в диалог - превьюшка заново скачается
type WallPaperId = WallPaper.wallPaper['id'];
let uploadWallPaperTempId = 0;
export class AppDocsManager extends AppManager {
private docs: {[docId: DocId]: MyDocument};
private stickerCachedThumbs: {[docId: DocId]: {[toneIndex: number]: {url: string, w: number, h: number}}};
private uploadingWallPapers: {[id: WallPaperId]: {cacheContext: ThumbCache, file: File}};
protected after() {
this.docs = {};
this.stickerCachedThumbs = {};
this.uploadingWallPapers = {};
MTProtoMessagePort.getInstance<false>().addEventListener('serviceWorkerOnline', (online) => {
if(!online) {
this.onServiceWorkerFail();
}
});
}
private onServiceWorkerFail = () => {
for(const id in this.docs) {
const doc = this.docs[id];
if(doc.supportsStreaming) {
delete doc.supportsStreaming;
this.thumbsStorage.deleteCacheContext(doc);
}
}
};
public saveDoc(doc: Document, context?: ReferenceContext): MyDocument {
if(!doc || doc._ === 'documentEmpty') {
return;
}
const oldDoc = this.docs[doc.id];
if(doc.file_reference) { // * because we can have a new object w/o the file_reference while sending
safeReplaceArrayInObject('file_reference', oldDoc, doc);
this.referenceDatabase.saveContext(doc.file_reference, context);
}
// console.log('saveDoc', apiDoc, this.docs[apiDoc.id]);
// if(oldDoc) {
// //if(doc._ !== 'documentEmpty' && doc._ === d._) {
// if(doc.thumbs) {
// if(!oldDoc.thumbs) oldDoc.thumbs = doc.thumbs;
// /* else if(apiDoc.thumbs[0].bytes && !d.thumbs[0].bytes) {
// d.thumbs.unshift(apiDoc.thumbs[0]);
// } else if(d.thumbs[0].url) { // fix for converted thumb in safari
// apiDoc.thumbs[0] = d.thumbs[0];
// } */
// }
// //}
// return oldDoc;
// //return Object.assign(d, apiDoc, context);
// //return context ? Object.assign(d, context) : d;
// }
if(!oldDoc) {
this.docs[doc.id] = doc;
}
// * exclude from state
// defineNotNumerableProperties(doc, [/* 'thumbs', */'type', 'h', 'w', 'file_name',
// 'file', 'duration', 'downloaded', 'url', 'audioTitle',
// 'audioPerformer', 'sticker', 'stickerEmoji', 'stickerEmojiRaw',
// 'stickerSetInput', 'stickerThumbConverted', 'animated', 'supportsStreaming']);
for(let i = 0, length = doc.attributes.length; i < length; ++i) {
const attribute = doc.attributes[i];
switch(attribute._) {
case 'documentAttributeFilename': {
doc.file_name = wrapPlainText(attribute.file_name);
break;
}
case 'documentAttributeAudio': {
if(doc.type === 'round') {
break;
}
doc.duration = attribute.duration;
doc.type = attribute.pFlags.voice && doc.mime_type === 'audio/ogg' ? 'voice' : 'audio';
break;
}
case 'documentAttributeVideo': {
doc.duration = attribute.duration;
doc.w = attribute.w;
doc.h = attribute.h;
// apiDoc.supportsStreaming = attribute.pFlags?.supports_streaming/* && apiDoc.size > 524288 */;
if(/* apiDoc.thumbs && */attribute.pFlags.round_message) {
doc.type = 'round';
} else /* if(apiDoc.thumbs) */ {
doc.type = 'video';
}
break;
}
case 'documentAttributeCustomEmoji':
case 'documentAttributeSticker': {
if(attribute.alt !== undefined) {
doc.stickerEmojiRaw = attribute.alt;
}
if(attribute.stickerset) {
if(attribute.stickerset._ === 'inputStickerSetEmpty') {
delete attribute.stickerset;
} else if(attribute.stickerset._ === 'inputStickerSetID') {
doc.stickerSetInput = attribute.stickerset;
}
}
// * there can be no thumbs, then it is a document
if(/* apiDoc.thumbs && */doc.mime_type === 'image/webp' && (doc.thumbs || getEnvironment().IS_WEBP_SUPPORTED)) {
doc.type = 'sticker';
doc.sticker = 1;
} else if(doc.mime_type === 'video/webm') {
if(!getEnvironment().IS_WEBM_SUPPORTED) {
break;
}
doc.type = 'sticker';
doc.sticker = 3;
doc.animated = true;
}
break;
}
case 'documentAttributeImageSize': {
doc.type = 'photo';
doc.w = attribute.w;
doc.h = attribute.h;
break;
}
case 'documentAttributeAnimated': {
if((doc.mime_type === 'image/gif' || doc.mime_type === 'video/mp4')/* && apiDoc.thumbs */) {
doc.type = 'gif';
}
doc.animated = true;
break;
}
}
}
if(!doc.mime_type) {
const ext = (doc.file_name || '').split('.').pop();
// @ts-ignore
const mappedMimeType = ext && EXTENSION_MIME_TYPE_MAP[ext.toLowerCase()];
if(mappedMimeType) {
doc.mime_type = mappedMimeType;
} else {
switch(doc.type) {
case 'gif':
case 'video':
case 'round':
doc.mime_type = 'video/mp4';
break;
case 'sticker':
doc.mime_type = 'image/webp';
break;
case 'audio':
doc.mime_type = 'audio/mpeg';
break;
case 'voice':
doc.mime_type = 'audio/ogg';
break;
default:
doc.mime_type = 'application/octet-stream';
break;
}
}
} else if(doc.mime_type === EXTENSION_MIME_TYPE_MAP.pdf) {
doc.type = 'pdf';
} else if(doc.mime_type === EXTENSION_MIME_TYPE_MAP.gif) {
doc.type = 'gif';
} else if(doc.mime_type === 'application/x-tgsticker' && doc.file_name === 'AnimatedSticker.tgs') {
doc.type = 'sticker';
doc.animated = true;
doc.sticker = 2;
}
if(doc.type === 'voice' || doc.type === 'round') {
// browser will identify extension
const attribute = doc.attributes.find((attribute) => attribute._ === 'documentAttributeFilename') as DocumentAttribute.documentAttributeFilename;
const ext = attribute && attribute.file_name.split('.').pop();
const date = getFullDate(new Date(doc.date * 1000), {monthAsNumber: true, leadingZero: true}).replace(/[:\.]/g, '-').replace(', ', '_');
doc.file_name = `${doc.type}_${date}${ext ? '.' + ext : ''}`;
}
if(isServiceWorkerOnline() && ((doc.type === 'gif' && doc.size > 8e6) || doc.type === 'audio' || doc.type === 'video')/* || doc.mime_type.indexOf('video/') === 0 */) {
doc.supportsStreaming = true;
const cacheContext = this.thumbsStorage.getCacheContext(doc);
if(!cacheContext.url) {
this.thumbsStorage.setCacheContextURL(doc, undefined, getDocumentURL(doc), 0);
}
} else {
doc.supportsStreaming = false;
}
// for testing purposes
// doc.supportsStreaming = false;
// doc.url = ''; // * this will break upload urls
doc.file_name ||= '';
/* if(!doc.url) {
doc.url = this.getFileURL(doc);
} */
if(oldDoc) {
return Object.assign(oldDoc, doc);
}
return doc;
}
public getDoc(docId: DocId | MyDocument): MyDocument {
return isObject<MyDocument>(docId) ? docId : this.docs[docId];
}
public downloadDoc(doc: MyDocument, queueId?: number, onlyCache?: boolean) {
return this.apiFileManager.downloadMedia({
media: doc,
queueId,
onlyCache
});
}
public getLottieCachedThumb(docId: DocId, toneIndex: number) {
const cached = this.stickerCachedThumbs[docId];
return cached && cached[toneIndex];
}
public saveLottiePreview(docId: DocId, blob: Blob, width: number, height: number, toneIndex: number) {
const doc = this.getDoc(docId);
if(!doc) {
return;
}
const cached = this.stickerCachedThumbs[doc.id] ??= {};
const thumb = cached[toneIndex];
if(thumb && thumb.w >= width && thumb.h >= height) {
return;
}
cached[toneIndex] = {
url: URL.createObjectURL(blob),
w: width,
h: height
};
}
public saveWebPConvertedStrippedThumb(docId: DocId, bytes: Uint8Array) {
const doc = this.getDoc(docId);
if(!doc) {
return;
}
const thumb = doc.thumbs && doc.thumbs.find((thumb) => thumb._ === 'photoStrippedSize') as PhotoSize.photoStrippedSize;
if(!thumb) {
return;
}
doc.pFlags.stickerThumbConverted = true;
thumb.bytes = bytes;
}
public prepareWallPaperUpload(file: File) {
const id = 'wallpaper-upload-' + ++uploadWallPaperTempId;
const thumb = {
_: 'photoSize',
h: 0,
w: 0,
location: {} as any,
size: file.size,
type: THUMB_TYPE_FULL
} as PhotoSize.photoSize;
let document: MyDocument = {
_: 'document',
access_hash: '',
attributes: [],
dc_id: 0,
file_reference: [],
id,
mime_type: file.type as MTMimeType,
size: file.size,
date: Date.now() / 1000,
pFlags: {},
thumbs: [thumb],
file_name: file.name
};
document = this.saveDoc(document);
const cacheContext = this.thumbsStorage.setCacheContextURL(document, undefined, URL.createObjectURL(file), file.size);
const wallpaper: WallPaper.wallPaper = {
_: 'wallPaper',
access_hash: '',
document: document,
id,
slug: id,
pFlags: {}
};
this.uploadingWallPapers[id] = {
cacheContext,
file
};
return wallpaper;
}
public uploadWallPaper(id: WallPaperId) {
const {cacheContext, file} = this.uploadingWallPapers[id];
delete this.uploadingWallPapers[id];
const upload = this.apiFileManager.upload({file, fileName: file.name});
return upload.then((inputFile) => {
return this.apiManager.invokeApi('account.uploadWallPaper', {
file: inputFile,
mime_type: file.type,
settings: {
_: 'wallPaperSettings',
pFlags: {}
}
}).then((wallPaper) => {
assumeType<WallPaper.wallPaper>(wallPaper);
wallPaper.document = this.saveDoc(wallPaper.document);
this.thumbsStorage.setCacheContextURL(wallPaper.document, undefined, cacheContext.url, cacheContext.downloaded);
return wallPaper;
});
});
}
public getGifs() {
return this.apiManager.invokeApiHashable({
method: 'messages.getSavedGifs',
processResult: (res) => {
assumeType<MessagesSavedGifs.messagesSavedGifs>(res);
return res.gifs.map((doc) => this.saveDoc(doc));
}
});
}
public requestDocPart(docId: DocId, dcId: number, offset: number, limit: number) {
const doc = this.getDoc(docId);
if(!doc) return Promise.reject(makeError('NO_DOC'));
return this.apiFileManager.requestFilePart(dcId, getDocumentInputFileLocation(doc), offset, limit);
}
}