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.
405 lines
12 KiB
405 lines
12 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, 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'; |
|
|
|
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 |
|
doc.file_name = doc.type + '_' + getFullDate(new Date(doc.date * 1000), {monthAsNumber: true, leadingZero: true}).replace(/[:\.]/g, '-').replace(', ', '_'); |
|
} |
|
|
|
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); |
|
return this.apiFileManager.requestFilePart(dcId, getDocumentInput(doc), offset, limit); |
|
} |
|
}
|
|
|