Telegram Web K with changes to work inside I2P https://web.telegram.i2p/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

410 lines
13 KiB

/*
* 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 {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 getDocumentInput from './utils/docs/getDocumentInput';
import getDocumentURL from './utils/docs/getDocumentURL';
import type {ThumbCache} from '../storages/thumbs';
import makeError from '../../helpers/makeError';
export type MyDocument = Document.document;
// TODO: если залить картинку файлом, а потом перезайти в диалог - превьюшка заново скачается
const EXTENSION_MIME_TYPE_MAP = {
mov: 'video/quicktime',
gif: 'image/gif',
pdf: 'application/pdf'
};
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':
doc.duration = attribute.duration;
doc.type = attribute.pFlags.voice && doc.mime_type === 'audio/ogg' ? 'voice' : 'audio';
/* if(apiDoc.type === 'audio') {
apiDoc.supportsStreaming = true;
} */
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 '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) {
return;
}
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';
}
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()) {
if((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);
}
}
}
// for testing purposes
// doc.supportsStreaming = false;
// doc.url = ''; // * this will break upload urls
if(!doc.file_name) {
doc.file_name = '';
}
if(doc.mime_type === 'application/x-tgsticker' && doc.file_name === 'AnimatedSticker.tgs') {
doc.type = 'sticker';
doc.animated = true;
doc.sticker = 2;
}
/* 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 getWallPapers() {
return this.apiManager.invokeApiHashable({method: 'account.getWallPapers'}).then((accountWallpapers) => {
const wallPapers = (accountWallpapers as AccountWallPapers.accountWallPapers).wallpapers as WallPaper.wallPaper[];
wallPapers.forEach((wallPaper) => {
wallPaper.document = this.saveDoc(wallPaper.document);
});
return wallPapers;
});
}
public prepareWallPaperUpload(file: File) {
const id = 'wallpaper-upload-' + ++uploadWallPaperTempId;
const thumb = {
_: 'photoSize',
h: 0,
w: 0,
location: {} as any,
size: file.size,
type: 'full'
} as PhotoSize.photoSize;
let document: MyDocument = {
_: 'document',
access_hash: '',
attributes: [],
dc_id: 0,
file_reference: [],
id,
mime_type: file.type,
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'
}
}).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, getDocumentInput(doc), offset, limit);
}
}