tweb/src/lib/appManagers/appDocsManager.ts

400 lines
12 KiB
TypeScript
Raw Normal View History

2021-04-08 15:52:31 +02:00
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
2022-08-04 08:49:54 +02:00
*
2021-04-08 15:52:31 +02:00
* Originally from:
* https://github.com/zhukov/webogram
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
2023-01-16 20:14:20 +01:00
import type {ThumbCache} from '../storages/thumbs';
2022-08-09 16:41:06 +02:00
import {AccountWallPapers, Document, DocumentAttribute, MessagesSavedGifs, PhotoSize, WallPaper} from '../../layer';
2022-08-04 08:49:54 +02:00
import {ReferenceContext} from '../mtproto/referenceDatabase';
import {getFullDate} from '../../helpers/date';
import isObject from '../../helpers/object/isObject';
import safeReplaceArrayInObject from '../../helpers/object/safeReplaceArrayInObject';
2022-08-04 08:49:54 +02:00
import {AppManager} from './manager';
2022-04-25 16:54:30 +02:00
import wrapPlainText from '../richTextProcessor/wrapPlainText';
import assumeType from '../../helpers/assumeType';
2022-08-04 08:49:54 +02:00
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';
2022-08-09 16:41:06 +02:00
import makeError from '../../helpers/makeError';
2022-08-20 22:53:19 +02:00
import {EXTENSION_MIME_TYPE_MAP} from '../../environment/mimeTypeMap';
import {THUMB_TYPE_FULL} from '../mtproto/mtproto_config';
export type MyDocument = Document.document;
2020-02-06 16:43:07 +01:00
// TODO: если залить картинку файлом, а потом перезайти в диалог - превьюшка заново скачается
2022-06-19 18:19:41 +02:00
type WallPaperId = WallPaper.wallPaper['id'];
let uploadWallPaperTempId = 0;
2022-04-25 16:54:30 +02:00
export class AppDocsManager extends AppManager {
private docs: {[docId: DocId]: MyDocument};
private stickerCachedThumbs: {[docId: DocId]: {[toneIndex: number]: {url: string, w: number, h: number}}};
2022-06-19 18:19:41 +02:00
private uploadingWallPapers: {[id: WallPaperId]: {cacheContext: ThumbCache, file: File}};
protected after() {
this.docs = {};
this.stickerCachedThumbs = {};
2022-06-19 18:19:41 +02:00
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 {
2022-06-19 18:19:41 +02:00
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);
2022-04-25 16:54:30 +02:00
this.referenceDatabase.saveContext(doc.file_reference, context);
}
2022-08-04 08:49:54 +02:00
// 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;
}
2020-10-17 00:31:58 +02:00
// * exclude from state
2022-08-04 08:49:54 +02:00
// defineNotNumerableProperties(doc, [/* 'thumbs', */'type', 'h', 'w', 'file_name',
// 'file', 'duration', 'downloaded', 'url', 'audioTitle',
// 'audioPerformer', 'sticker', 'stickerEmoji', 'stickerEmojiRaw',
// 'stickerSetInput', 'stickerThumbConverted', 'animated', 'supportsStreaming']);
2022-02-01 08:32:01 +01:00
for(let i = 0, length = doc.attributes.length; i < length; ++i) {
const attribute = doc.attributes[i];
2020-02-06 16:43:07 +01:00
switch(attribute._) {
2023-01-16 20:14:20 +01:00
case 'documentAttributeFilename': {
2022-04-25 16:54:30 +02:00
doc.file_name = wrapPlainText(attribute.file_name);
2020-02-06 16:43:07 +01:00
break;
2023-01-16 20:14:20 +01:00
}
case 'documentAttributeAudio': {
if(doc.type === 'round') {
break;
}
2020-02-06 16:43:07 +01:00
doc.duration = attribute.duration;
2023-01-16 20:14:20 +01:00
doc.type = attribute.pFlags.voice && doc.mime_type === 'audio/ogg' ? 'voice' : 'audio';
2020-02-06 16:43:07 +01:00
break;
2023-01-16 20:14:20 +01:00
}
2020-02-06 16:43:07 +01:00
2023-01-16 20:14:20 +01:00
case 'documentAttributeVideo': {
doc.duration = attribute.duration;
doc.w = attribute.w;
doc.h = attribute.h;
2022-08-04 08:49:54 +02:00
// apiDoc.supportsStreaming = attribute.pFlags?.supports_streaming/* && apiDoc.size > 524288 */;
if(/* apiDoc.thumbs && */attribute.pFlags.round_message) {
2023-01-16 20:14:20 +01:00
doc.type = 'round';
2020-04-16 02:48:41 +02:00
} else /* if(apiDoc.thumbs) */ {
2023-01-16 20:14:20 +01:00
doc.type = 'video';
2020-02-06 16:43:07 +01:00
}
break;
2023-01-16 20:14:20 +01:00
}
2020-02-06 16:43:07 +01:00
2022-08-31 06:22:16 +02:00
case 'documentAttributeCustomEmoji':
2023-01-16 20:14:20 +01:00
case 'documentAttributeSticker': {
2020-02-06 16:43:07 +01:00
if(attribute.alt !== undefined) {
doc.stickerEmojiRaw = attribute.alt;
2020-02-06 16:43:07 +01:00
}
if(attribute.stickerset) {
2021-02-04 01:30:23 +01:00
if(attribute.stickerset._ === 'inputStickerSetEmpty') {
2020-04-16 02:48:41 +02:00
delete attribute.stickerset;
2021-02-04 01:30:23 +01:00
} else if(attribute.stickerset._ === 'inputStickerSetID') {
doc.stickerSetInput = attribute.stickerset;
2020-02-06 16:43:07 +01:00
}
}
// * there can be no thumbs, then it is a document
if(/* apiDoc.thumbs && */doc.mime_type === 'image/webp' && (doc.thumbs || getEnvironment().IS_WEBP_SUPPORTED)) {
2023-01-16 20:14:20 +01:00
doc.type = 'sticker';
doc.sticker = 1;
2022-01-17 18:38:25 +01:00
} else if(doc.mime_type === 'video/webm') {
if(!getEnvironment().IS_WEBM_SUPPORTED) {
2022-08-31 06:22:16 +02:00
break;
2022-02-01 08:32:01 +01:00
}
2023-01-16 20:14:20 +01:00
doc.type = 'sticker';
2022-01-17 18:38:25 +01:00
doc.sticker = 3;
doc.animated = true;
2020-02-06 16:43:07 +01:00
}
break;
2023-01-16 20:14:20 +01:00
}
2020-02-06 16:43:07 +01:00
2023-01-16 20:14:20 +01:00
case 'documentAttributeImageSize': {
doc.type = 'photo';
doc.w = attribute.w;
doc.h = attribute.h;
2020-02-06 16:43:07 +01:00
break;
2023-01-16 20:14:20 +01:00
}
2020-02-06 16:43:07 +01:00
2023-01-16 20:14:20 +01:00
case 'documentAttributeAnimated': {
2021-02-04 01:30:23 +01:00
if((doc.mime_type === 'image/gif' || doc.mime_type === 'video/mp4')/* && apiDoc.thumbs */) {
2023-01-16 20:14:20 +01:00
doc.type = 'gif';
2020-02-06 16:43:07 +01:00
}
doc.animated = true;
2020-02-06 16:43:07 +01:00
break;
2023-01-16 20:14:20 +01:00
}
2020-02-06 16:43:07 +01:00
}
2022-02-01 08:32:01 +01:00
}
2022-08-04 08:49:54 +02:00
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;
}
2020-02-06 16:43:07 +01:00
}
} 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';
2023-01-13 22:46:36 +01:00
} 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
2022-08-09 16:41:06 +02:00
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 */) {
2023-01-15 12:51:38 +01:00
doc.supportsStreaming = true;
const cacheContext = this.thumbsStorage.getCacheContext(doc);
if(!cacheContext.url) {
this.thumbsStorage.setCacheContextURL(doc, undefined, getDocumentURL(doc), 0);
2020-08-28 17:11:25 +02:00
}
2023-01-15 12:51:38 +01:00
} else {
doc.supportsStreaming = false;
}
// for testing purposes
2020-12-21 23:38:26 +01:00
// doc.supportsStreaming = false;
// doc.url = ''; // * this will break upload urls
2022-08-04 08:49:54 +02:00
2023-01-13 22:46:36 +01:00
doc.file_name ||= '';
2020-04-14 17:46:31 +02:00
/* if(!doc.url) {
doc.url = this.getFileURL(doc);
} */
if(oldDoc) {
return Object.assign(oldDoc, doc);
}
return doc;
2020-02-06 16:43:07 +01:00
}
2022-08-04 08:49:54 +02:00
2021-10-21 15:16:43 +02:00
public getDoc(docId: DocId | MyDocument): MyDocument {
return isObject<MyDocument>(docId) ? docId : this.docs[docId];
2020-02-06 16:43:07 +01:00
}
2020-05-01 23:28:40 +02:00
public downloadDoc(doc: MyDocument, queueId?: number, onlyCache?: boolean) {
return this.apiFileManager.downloadMedia({
media: doc,
queueId,
onlyCache
});
2020-05-01 23:28:40 +02:00
}
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] ??= {};
2022-08-04 08:49:54 +02:00
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;
}
2020-06-16 22:48:08 +02:00
const thumb = doc.thumbs && doc.thumbs.find((thumb) => thumb._ === 'photoStrippedSize') as PhotoSize.photoStrippedSize;
if(!thumb) {
return;
}
doc.pFlags.stickerThumbConverted = true;
thumb.bytes = bytes;
}
2022-06-19 18:19:41 +02:00
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,
2022-08-20 22:53:19 +02:00
mime_type: file.type as MTMimeType,
size: file.size,
date: Date.now() / 1000,
pFlags: {},
thumbs: [thumb],
file_name: file.name
};
2022-02-01 20:54:00 +01:00
document = this.saveDoc(document);
const cacheContext = this.thumbsStorage.setCacheContextURL(document, undefined, URL.createObjectURL(file), file.size);
2022-06-19 18:19:41 +02:00
const wallpaper: WallPaper.wallPaper = {
_: 'wallPaper',
access_hash: '',
document: document,
id,
slug: id,
pFlags: {}
};
2022-06-19 18:19:41 +02:00
this.uploadingWallPapers[id] = {
cacheContext,
2022-08-04 08:49:54 +02:00
file
2022-06-19 18:19:41 +02:00
};
2022-06-19 18:19:41 +02:00
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: {
2023-02-01 14:44:53 +01:00
_: 'wallPaperSettings',
pFlags: {}
}
2022-06-19 18:19:41 +02:00
}).then((wallPaper) => {
assumeType<WallPaper.wallPaper>(wallPaper);
wallPaper.document = this.saveDoc(wallPaper.document);
this.thumbsStorage.setCacheContextURL(wallPaper.document, undefined, cacheContext.url, cacheContext.downloaded);
2022-06-19 18:19:41 +02:00
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);
2022-08-09 16:41:06 +02:00
if(!doc) return Promise.reject(makeError('NO_DOC'));
return this.apiFileManager.requestFilePart(dcId, getDocumentInputFileLocation(doc), offset, limit);
}
2020-02-06 16:43:07 +01:00
}