Browse Source

Payments: bubble attachment & service message

master
Eduard Kuzmenko 2 years ago
parent
commit
3568c5dbe0
  1. 79
      src/components/chat/bubbles.ts
  2. 52
      src/components/wrappers/messageActionTextNewUnsafe.ts
  3. 5
      src/components/wrappers/messageForReply.ts
  4. 12
      src/components/wrappers/photo.ts
  5. 18
      src/config/currencies.ts
  6. 9
      src/helpers/mediaSizes.ts
  7. 70
      src/helpers/paymentsWrapCurrencyAmount.ts
  8. 16
      src/helpers/setAttachmentSize.ts
  9. 8
      src/lang.ts
  10. 8
      src/layer.d.ts
  11. 2
      src/lib/appManagers/appDownloadManager.ts
  12. 19
      src/lib/appManagers/appMessagesManager.ts
  13. 23
      src/lib/appManagers/appWebDocsManager.ts
  14. 4
      src/lib/appManagers/createManagers.ts
  15. 2
      src/lib/appManagers/manager.ts
  16. 11
      src/lib/appManagers/utils/download/getDownloadMediaDetails.ts
  17. 12
      src/lib/appManagers/utils/photos/choosePhotoSize.ts
  18. 15
      src/lib/appManagers/utils/webDocs/getWebDocumentDownloadOptions.ts
  19. 5
      src/lib/appManagers/utils/webDocs/isWebDocument.ts
  20. 8
      src/lib/fileManager.ts
  21. 2
      src/lib/langPack.ts
  22. 15
      src/lib/mtproto/apiFileManager.ts
  23. 19
      src/lib/storages/thumbs.ts
  24. 12
      src/scripts/in/schema_additional_params.json
  25. 5
      src/scss/partials/_chatBubble.scss

79
src/components/chat/bubbles.ts

@ -49,7 +49,6 @@ import { getMiddleware } from "../../helpers/middleware"; @@ -49,7 +49,6 @@ import { getMiddleware } from "../../helpers/middleware";
import cancelEvent from "../../helpers/dom/cancelEvent";
import { attachClickEvent, simulateClickEvent } from "../../helpers/dom/clickEvent";
import htmlToDocumentFragment from "../../helpers/dom/htmlToDocumentFragment";
import positionElementByIndex from "../../helpers/dom/positionElementByIndex";
import reflowScrollableElement from "../../helpers/dom/reflowScrollableElement";
import replaceContent from "../../helpers/dom/replaceContent";
import setInnerHTML from "../../helpers/dom/setInnerHTML";
@ -106,10 +105,10 @@ import { cancelContextMenuOpening } from "../../helpers/dom/attachContextMenuLis @@ -106,10 +105,10 @@ import { cancelContextMenuOpening } from "../../helpers/dom/attachContextMenuLis
import contextMenuController from "../../helpers/contextMenuController";
import { AckedResult } from "../../lib/mtproto/superMessagePort";
import middlewarePromise from "../../helpers/middlewarePromise";
import findAndSplice from "../../helpers/array/findAndSplice";
import { EmoticonsDropdown } from "../emoticonsDropdown";
import indexOfAndSplice from "../../helpers/array/indexOfAndSplice";
import noop from "../../helpers/noop";
import paymentsWrapCurrencyAmount from "../../helpers/paymentsWrapCurrencyAmount";
const USE_MEDIA_TAILS = false;
const IGNORE_ACTIONS: Set<Message.messageService['action']['_']> = new Set([
@ -3580,7 +3579,7 @@ export default class ChatBubbles { @@ -3580,7 +3579,7 @@ export default class ChatBubbles {
rowDiv.classList.add('reply-markup-row');
buttons.forEach((button) => {
const text = wrapRichText(button.text, {noLinks: true, noLinebreaks: true});
let text: DocumentFragment | HTMLElement | string = wrapRichText(button.text, {noLinks: true, noLinebreaks: true});
let buttonEl: HTMLButtonElement | HTMLAnchorElement;
@ -3596,14 +3595,14 @@ export default class ChatBubbles { @@ -3596,14 +3595,14 @@ export default class ChatBubbles {
});
buttonEl = htmlToDocumentFragment(r).firstElementChild as HTMLAnchorElement;
buttonEl.classList.add('is-link', 'tgico');
buttonEl.classList.add('is-link');
break;
}
case 'keyboardButtonSwitchInline': {
buttonEl = document.createElement('button');
buttonEl.classList.add('is-switch-inline', 'tgico');
buttonEl.classList.add('is-switch-inline');
attachClickEvent(buttonEl, (e) => {
cancelEvent(e);
@ -3637,13 +3636,26 @@ export default class ChatBubbles { @@ -3637,13 +3636,26 @@ export default class ChatBubbles {
break;
}
case 'keyboardButtonBuy': {
buttonEl = document.createElement('button');
buttonEl.classList.add('is-buy');
if(messageMedia?._ === 'messageMediaInvoice') {
if(messageMedia.receipt_msg_id) {
text = i18n('Message.ReplyActionButtonShowReceipt');
}
}
break;
}
default: {
buttonEl = document.createElement('button');
break;
}
}
buttonEl.classList.add('reply-markup-button', 'rp');
buttonEl.classList.add('reply-markup-button', 'rp', 'tgico');
if(typeof(text) === 'string') {
buttonEl.insertAdjacentHTML('beforeend', text);
} else {
@ -4177,6 +4189,61 @@ export default class ChatBubbles { @@ -4177,6 +4189,61 @@ export default class ChatBubbles {
break;
}
case 'messageMediaInvoice': {
const isTest = messageMedia.pFlags.test;
const photo = messageMedia.photo;
const priceEl = document.createElement(photo ? 'span' : 'div');
const f = document.createDocumentFragment();
const l = i18n(messageMedia.receipt_msg_id ? 'PaymentReceipt' : (isTest ? 'PaymentTestInvoice' : 'PaymentInvoice'));
l.classList.add('text-uppercase');
const joiner = ' ';
const p = document.createElement('span');
p.classList.add('text-bold');
p.textContent = paymentsWrapCurrencyAmount(messageMedia.total_amount, messageMedia.currency) + joiner;
f.append(p, l);
if(isTest && messageMedia.receipt_msg_id) {
const a = document.createElement('span');
a.classList.add('text-uppercase', 'pre-wrap');
a.append(joiner + '(Test)');
f.append(a);
}
setInnerHTML(priceEl, f);
if(photo) {
const mediaSize = mediaSizes.active.invoice;
wrapPhoto({
photo,
container: attachmentDiv,
withTail: false,
isOut,
lazyLoadQueue: this.lazyLoadQueue,
middleware: this.getMiddleware(),
loadPromises,
boxWidth: mediaSize.width,
boxHeight: mediaSize.height
});
bubble.classList.add('photo');
priceEl.classList.add('video-time');
attachmentDiv.append(priceEl);
} else {
attachmentDiv = undefined;
}
const titleDiv = document.createElement('div');
titleDiv.classList.add('bubble-primary-color');
setInnerHTML(titleDiv, wrapRichText(messageMedia.title));
const richText = wrapRichText(messageMedia.description);
messageDiv.prepend(...[titleDiv, !photo && priceEl, richText].filter(Boolean));
bubble.classList.remove('is-message-empty');
break;
}
default:
attachmentDiv = undefined;

52
src/components/wrappers/messageActionTextNewUnsafe.ts

@ -9,7 +9,8 @@ import { formatTime } from "../../helpers/date"; @@ -9,7 +9,8 @@ import { formatTime } from "../../helpers/date";
import htmlToSpan from "../../helpers/dom/htmlToSpan";
import setInnerHTML from "../../helpers/dom/setInnerHTML";
import formatCallDuration from "../../helpers/formatCallDuration";
import { MessageAction } from "../../layer";
import paymentsWrapCurrencyAmount from "../../helpers/paymentsWrapCurrencyAmount";
import { Message, MessageAction } from "../../layer";
import { MyMessage } from "../../lib/appManagers/appMessagesManager";
import I18n, { FormatterArgument, FormatterArguments, i18n, join, langPack, LangPackKey, _i18n } from "../../lib/langPack";
import wrapEmojiText from "../../lib/richTextProcessor/wrapEmojiText";
@ -21,6 +22,14 @@ import getPeerTitle from "./getPeerTitle"; @@ -21,6 +22,14 @@ import getPeerTitle from "./getPeerTitle";
import wrapJoinVoiceChatAnchor from "./joinVoiceChatAnchor";
import wrapMessageForReply from "./messageForReply";
async function wrapLinkToMessage(message: Message.message | Message.messageService, plain?: boolean) {
const a = document.createElement('i');
a.dataset.savedFrom = message.peerId + '_' + message.mid;
a.dir = 'auto';
a.append(await wrapMessageForReply(message, undefined, undefined, plain as any));
return a;
}
export default async function wrapMessageActionTextNewUnsafe(message: MyMessage, plain?: boolean) {
const element: HTMLElement = plain ? undefined : document.createElement('span');
const action = 'action' in message && message.action;
@ -150,29 +159,10 @@ export default async function wrapMessageActionTextNewUnsafe(message: MyMessage, @@ -150,29 +159,10 @@ export default async function wrapMessageActionTextNewUnsafe(message: MyMessage,
langPackKey = 'ActionPinnedNoText';
if(message.reply_to_mid) { // refresh original message
managers.appMessagesManager.fetchMessageReplyTo(message).then(async(originalMessage) => {
if(originalMessage && message) {
rootScope.dispatchEvent('message_edit', {
storageKey: `${peerId}_history`,
peerId: peerId,
mid: message.mid,
message
});
if(managers.appMessagesManager.isMessageIsTopMessage(message)) {
rootScope.dispatchEvent('dialogs_multiupdate', {
[peerId]: await managers.appMessagesManager.getDialogOnly(peerId)
});
}
}
});
managers.appMessagesManager.fetchMessageReplyTo(message);
}
} else {
const a = document.createElement('i');
a.dataset.savedFrom = pinnedMessage.peerId + '_' + pinnedMessage.mid;
a.dir = 'auto';
a.append(await wrapMessageForReply(pinnedMessage, undefined, undefined, plain as any));
args.push(a);
args.push(wrapLinkToMessage(pinnedMessage, plain));
}
break;
@ -258,6 +248,24 @@ export default async function wrapMessageActionTextNewUnsafe(message: MyMessage, @@ -258,6 +248,24 @@ export default async function wrapMessageActionTextNewUnsafe(message: MyMessage,
break;
}
case 'messageActionPaymentSent': {
langPackKey = 'PaymentSuccessfullyPaidNoItem';
const price = paymentsWrapCurrencyAmount(action.total_amount, action.currency);
args = [price, getNameDivHTML(message.peerId, plain)];
if(message.reply_to_mid) {
const invoiceMessage = await managers.appMessagesManager.getMessageByPeer(message.peerId, message.reply_to_mid);
if(!invoiceMessage) {
managers.appMessagesManager.fetchMessageReplyTo(message);
} else {
langPackKey = 'PaymentSuccessfullyPaid';
args.push(wrapLinkToMessage(invoiceMessage, plain));
}
}
break;
}
default:
langPackKey = (langPack[_] || `[${action._}]`) as any;
break;

5
src/components/wrappers/messageForReply.ts

@ -158,6 +158,11 @@ export default async function wrapMessageForReply(message: MyMessage | MyDraftMe @@ -158,6 +158,11 @@ export default async function wrapMessageForReply(message: MyMessage | MyDraftMe
break;
}
case 'messageMediaInvoice': {
addPart(undefined, plain ? media.title : wrapEmojiText(media.title));
break;
}
case 'messageMediaUnsupported': {
addPart(UNSUPPORTED_LANG_PACK_KEY);
break;

12
src/components/wrappers/photo.ts

@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
import renderImageWithFadeIn from "../../helpers/dom/renderImageWithFadeIn";
import mediaSizes from "../../helpers/mediaSizes";
import { Message, PhotoSize } from "../../layer";
import { Message, PhotoSize, WebDocument } from "../../layer";
import { MyDocument } from "../../lib/appManagers/appDocsManager";
import { MyPhoto } from "../../lib/appManagers/appPhotosManager";
import rootScope from "../../lib/rootScope";
@ -19,9 +19,10 @@ import setAttachmentSize from "../../helpers/setAttachmentSize"; @@ -19,9 +19,10 @@ import setAttachmentSize from "../../helpers/setAttachmentSize";
import choosePhotoSize from "../../lib/appManagers/utils/photos/choosePhotoSize";
import type { ThumbCache } from "../../lib/storages/thumbs";
import appDownloadManager from "../../lib/appManagers/appDownloadManager";
import isWebDocument from "../../lib/appManagers/utils/webDocs/isWebDocument";
export default async function wrapPhoto({photo, message, container, boxWidth, boxHeight, withTail, isOut, lazyLoadQueue, middleware, size, withoutPreloader, loadPromises, autoDownloadSize, noBlur, noThumb, noFadeIn, blurAfter, managers = rootScope.managers}: {
photo: MyPhoto | MyDocument,
photo: MyPhoto | MyDocument | WebDocument,
message?: Message.message | Message.messageService,
container: HTMLElement,
boxWidth?: number,
@ -40,7 +41,8 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo @@ -40,7 +41,8 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
blurAfter?: boolean,
managers?: AppManagers,
}) {
if(!((photo as MyPhoto).sizes || (photo as MyDocument).thumbs)) {
const isWebDoc = isWebDocument(photo);
if(!((photo as MyPhoto).sizes || (photo as MyDocument).thumbs) && !isWebDoc) {
if(boxWidth && boxHeight && !size && photo._ === 'document') {
setAttachmentSize(photo, container, boxWidth, boxHeight, undefined, message);
}
@ -92,7 +94,7 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo @@ -92,7 +94,7 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
isFit = set.isFit;
cacheContext = await managers.thumbsStorage.getCacheContext(photo, size.type);
if(!isFit) {
if(!isFit && !isWebDoc) {
aspecter = document.createElement('div');
aspecter.classList.add('media-container-aspecter');
aspecter.style.width = set.size.width + 'px';
@ -141,7 +143,7 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo @@ -141,7 +143,7 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
cacheContext = await managers.thumbsStorage.getCacheContext(photo, size?.type);
}
if(!noThumb) {
if(!noThumb && !isWebDoc) {
const gotThumb = getStrippedThumbIfNeeded(photo, cacheContext, !noBlur);
if(gotThumb) {
loadThumbPromise = Promise.all([loadThumbPromise, gotThumb.loadPromise]);

18
src/config/currencies.ts

File diff suppressed because one or more lines are too long

9
src/helpers/mediaSizes.ts

@ -18,7 +18,8 @@ type MediaTypeSizes = { @@ -18,7 +18,8 @@ type MediaTypeSizes = {
emojiSticker: MediaSize,
poll: MediaSize,
round: MediaSize,
documentName: MediaSize
documentName: MediaSize,
invoice: MediaSize
};
export type MediaSizeType = keyof MediaTypeSizes;
@ -54,7 +55,8 @@ class MediaSizes extends EventListenerBase<{ @@ -54,7 +55,8 @@ class MediaSizes extends EventListenerBase<{
emojiSticker: makeMediaSize(112, 112),
poll: makeMediaSize(240, 0),
round: makeMediaSize(200, 200),
documentName: makeMediaSize(200, 0)
documentName: makeMediaSize(200, 0),
invoice: makeMediaSize(240, 240)
},
desktop: {
regular: makeMediaSize(420, 340),
@ -66,7 +68,8 @@ class MediaSizes extends EventListenerBase<{ @@ -66,7 +68,8 @@ class MediaSizes extends EventListenerBase<{
emojiSticker: makeMediaSize(112, 112),
poll: makeMediaSize(330, 0),
round: makeMediaSize(280, 280),
documentName: makeMediaSize(240, 0)
documentName: makeMediaSize(240, 0),
invoice: makeMediaSize(320, 260)
}
};

70
src/helpers/paymentsWrapCurrencyAmount.ts

@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
import Currencies from "../config/currencies";
// https://stackoverflow.com/a/34141813
function number_format(number: any, decimals: any, dec_point: any, thousands_sep: any) {
// Strip all characters but numerical ones.
number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
var n = !isFinite(+number) ? 0 : +number,
prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep,
dec = (typeof dec_point === 'undefined') ? '.' : dec_point,
s: any = '',
toFixedFix = function(n: number, prec: number) {
var k = Math.pow(10, prec);
return '' + Math.round(n * k) / k;
};
// Fix for IE parseFloat(0.55).toFixed(0) = 0;
s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
if (s[0].length > 3) {
s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
}
if ((s[1] || '').length < prec) {
s[1] = s[1] || '';
s[1] += new Array(prec - s[1].length + 1).join('0');
}
return s.join(dec);
}
export default function paymentsWrapCurrencyAmount($amount: number | string, $currency: string) {
$amount = +$amount;
const $currency_data = Currencies[$currency]; // вытащить из json
if(!$currency_data) {
throw new Error('CURRENCY_WRAP_INVALID');
}
const $amount_exp = $amount / Math.pow(10, $currency_data['exp']);
let $decimals = $currency_data['exp'];
if($currency == 'IRR' &&
Math.floor($amount_exp) == $amount_exp) {
$decimals = 0; // у иранцев копейки почти всегда = 0 и не показываются в UI
}
const $formatted = number_format($amount_exp, $decimals, $currency_data['decimal_sep'], $currency_data['thousands_sep']);
const $splitter = $currency_data['space_between'] ? "\xc2\xa0" : '';
let $formatted_intern: string;
if($currency_data['symbol_left']) {
$formatted_intern = $currency_data['symbol'] + $splitter + $formatted;
} else {
$formatted_intern = $formatted + $splitter + $currency_data['symbol'];
}
return $formatted_intern;
}
function paymentsGetCurrencyExp($currency: string) {
if($currency == 'CLF') {
return 4;
}
if(['BHD','IQD','JOD','KWD','LYD','OMR','TND'].includes($currency)) {
return 3;
}
if(['BIF','BYR','CLP','CVE','DJF','GNF','ISK','JPY','KMF','KRW','MGA', 'PYG','RWF','UGX','UYI','VND','VUV','XAF','XOF','XPF'].includes($currency)) {
return 0;
}
if($currency == 'MRO') {
return 1;
}
return 2;
}

16
src/helpers/setAttachmentSize.ts

@ -4,15 +4,16 @@ @@ -4,15 +4,16 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import { PhotoSize } from "../layer";
import { PhotoSize, WebDocument } from "../layer";
import { REPLIES_HIDDEN_CHANNEL_ID } from "../lib/mtproto/mtproto_config";
import { MyDocument } from "../lib/appManagers/appDocsManager";
import { MyPhoto } from "../lib/appManagers/appPhotosManager";
import choosePhotoSize from "../lib/appManagers/utils/photos/choosePhotoSize";
import { MediaSize, makeMediaSize } from "./mediaSize";
import isWebDocument from "../lib/appManagers/utils/webDocs/isWebDocument";
export default function setAttachmentSize(
photo: MyPhoto | MyDocument,
photo: MyPhoto | MyDocument | WebDocument,
element: HTMLElement | SVGForeignObjectElement,
boxWidth: number,
boxHeight: number,
@ -21,6 +22,11 @@ export default function setAttachmentSize( @@ -21,6 +22,11 @@ export default function setAttachmentSize(
pushDocumentSize?: boolean,
photoSize?: ReturnType<typeof choosePhotoSize>
) {
const _isWebDocument = isWebDocument(photo);
// if(_isWebDocument && pushDocumentSize === undefined) {
// pushDocumentSize = true;
// }
if(!photoSize) {
photoSize = choosePhotoSize(photo, boxWidth, boxHeight, undefined, pushDocumentSize);
}
@ -28,8 +34,8 @@ export default function setAttachmentSize( @@ -28,8 +34,8 @@ export default function setAttachmentSize(
let size: MediaSize;
const isDocument = photo._ === 'document';
if(isDocument) {
size = makeMediaSize((photo as MyDocument).w || (photoSize as PhotoSize.photoSize).w || 512, (photo as MyDocument).h || (photoSize as PhotoSize.photoSize).h || 512);
if(isDocument || _isWebDocument) {
size = makeMediaSize(photo.w || (photoSize as PhotoSize.photoSize).w || 512, photo.h || (photoSize as PhotoSize.photoSize).h || 512);
} else {
size = makeMediaSize((photoSize as PhotoSize.photoSize).w || 100, (photoSize as PhotoSize.photoSize).h || 100);
}
@ -40,7 +46,7 @@ export default function setAttachmentSize( @@ -40,7 +46,7 @@ export default function setAttachmentSize(
let isFit = true;
if(!isDocument || ['video', 'gif'].includes((photo as MyDocument).type)) {
if(!isDocument || ['video', 'gif'].includes(photo.type) || _isWebDocument) {
if(boxSize.width < 200 && boxSize.height < 200) { // make at least one side this big
boxSize = size = size.aspectCovered(makeMediaSize(200, 200));
}

8
src/lang.ts

@ -695,6 +695,13 @@ const lang = { @@ -695,6 +695,13 @@ const lang = {
"ScamMessage": "SCAM",
"FakeMessage": "FAKE",
"TextCopied": "Text copied to clipboard",
"PaymentInvoice": "INVOICE",
"PaymentTestInvoice": "TEST INVOICE",
"PaymentReceipt": "Receipt",
"PaymentSuccessfullyPaid": "You successfully transferred %1$s to %2$s for %3$s",
"PaymentSuccessfullyPaidNoItem": "You successfully transferred %1$s to %2$s",
// "PaymentSuccessfullyPaidRecurrent": "You successfully transferred %1$s to %2$s for %3$s and allowed future recurring payments",
// "PaymentSuccessfullyPaidNoItemRecurrent": "You successfully transferred %1$s to %2$s and allowed future recurring payments",
// * macos
"AccountSettings.Filters": "Chat Folders",
@ -913,6 +920,7 @@ const lang = { @@ -913,6 +920,7 @@ const lang = {
"Message.Context.Pin": "Pin",
"Message.Context.Unpin": "Unpin",
"Message.Context.Goto": "Show Message",
"Message.ReplyActionButtonShowReceipt": "Show Receipt",
"MessageContext.CopyMessageLink1": "Copy Message Link",
"Modal.Send": "Send",
"NewPoll.Anonymous": "Anonymous Voting",

8
src/layer.d.ts vendored

@ -5881,7 +5881,9 @@ export namespace WebDocument { @@ -5881,7 +5881,9 @@ export namespace WebDocument {
access_hash: string | number,
size: number,
mime_type: string,
attributes: Array<DocumentAttribute>
attributes: Array<DocumentAttribute>,
h?: number,
w?: number
};
export type webDocumentNoProxy = {
@ -5889,7 +5891,9 @@ export namespace WebDocument { @@ -5889,7 +5891,9 @@ export namespace WebDocument {
url: string,
size: number,
mime_type: string,
attributes: Array<DocumentAttribute>
attributes: Array<DocumentAttribute>,
h?: number,
w?: number
};
}

2
src/lib/appManagers/appDownloadManager.ts

@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
import type { ApiFileManager, DownloadMediaOptions, DownloadOptions } from "../mtproto/apiFileManager";
import deferredPromise, { CancellablePromise } from "../../helpers/cancellablePromise";
import { Document, InputFile, Photo, PhotoSize } from "../../layer";
import { Document, InputFile, Photo, PhotoSize, WebDocument } from "../../layer";
import { getFileNameByLocation } from "../../helpers/fileName";
import getFileNameForUpload from "../../helpers/getFileNameForUpload";
import { AppManagers } from "./managers";

19
src/lib/appManagers/appMessagesManager.ts

@ -2759,8 +2759,7 @@ export class AppMessagesManager extends AppManager { @@ -2759,8 +2759,7 @@ export class AppMessagesManager extends AppManager {
break; */
case 'messageMediaInvoice': {
unsupported = true;
message.media = {_: 'messageMediaUnsupported'};
message.media.photo = this.appWebDocsManager.saveWebDocument(message.media.photo);
break;
}
@ -5630,6 +5629,22 @@ export class AppMessagesManager extends AppManager { @@ -5630,6 +5629,22 @@ export class AppMessagesManager extends AppManager {
delete message.reply_to_mid; // ! WARNING!
}
if(message._ === 'messageService') {
const peerId = message.peerId;
this.rootScope.dispatchEvent('message_edit', {
storageKey: `${peerId}_history`,
peerId: peerId,
mid: message.mid,
message
});
if(this.isMessageIsTopMessage(message)) {
this.rootScope.dispatchEvent('dialogs_multiupdate', {
[peerId]: this.getDialogOnly(peerId)
});
}
}
return originalMessage;
});
}

23
src/lib/appManagers/appWebDocsManager.ts

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import { DocumentAttribute, WebDocument } from "../../layer";
export default class AppWebDocsManager {
public saveWebDocument(webDocument: WebDocument) {
if(!webDocument) {
return;
}
const attribute: DocumentAttribute.documentAttributeImageSize = webDocument.attributes.find((attribute) => attribute._ === 'documentAttributeImageSize') as any;
if(attribute) {
webDocument.w = attribute.w;
webDocument.h = attribute.h;
}
return webDocument;
}
}

4
src/lib/appManagers/createManagers.ts

@ -43,6 +43,7 @@ import { AppStoragesManager } from "./appStoragesManager"; @@ -43,6 +43,7 @@ import { AppStoragesManager } from "./appStoragesManager";
import cryptoMessagePort from "../crypto/cryptoMessagePort";
import appStateManager from "./appStateManager";
import filterUnique from "../../helpers/array/filterUnique";
import AppWebDocsManager from "./appWebDocsManager";
export default function createManagers(appStoragesManager: AppStoragesManager, userId: UserId) {
const managers = {
@ -82,7 +83,8 @@ export default function createManagers(appStoragesManager: AppStoragesManager, u @@ -82,7 +83,8 @@ export default function createManagers(appStoragesManager: AppStoragesManager, u
dcConfigurator: new DcConfigurator,
timeManager: new TimeManager,
appStoragesManager: appStoragesManager,
appStateManager: appStateManager
appStateManager: appStateManager,
appWebDocsManager: new AppWebDocsManager
};
type T = typeof managers;

2
src/lib/appManagers/manager.ts

@ -40,6 +40,7 @@ import type { AppStateManager } from "./appStateManager"; @@ -40,6 +40,7 @@ import type { AppStateManager } from "./appStateManager";
import type { AppStickersManager } from "./appStickersManager";
import type { AppStoragesManager } from "./appStoragesManager";
import type { AppUsersManager } from "./appUsersManager";
import type AppWebDocsManager from "./appWebDocsManager";
import type { AppWebPagesManager } from "./appWebPagesManager";
import type { AppManagers } from "./managers";
@ -82,6 +83,7 @@ export class AppManager { @@ -82,6 +83,7 @@ export class AppManager {
protected timeManager: TimeManager;
protected appStoragesManager: AppStoragesManager;
protected appStateManager: AppStateManager;
protected appWebDocsManager: AppWebDocsManager;
public clear: (init?: boolean) => void;

11
src/lib/appManagers/utils/download/getDownloadMediaDetails.ts

@ -4,14 +4,21 @@ @@ -4,14 +4,21 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import type { DownloadMediaOptions } from "../../../mtproto/apiFileManager";
import type { DownloadMediaOptions, DownloadOptions } from "../../../mtproto/apiFileManager";
import getDocumentDownloadOptions from "../docs/getDocumentDownloadOptions";
import getPhotoDownloadOptions from "../photos/getPhotoDownloadOptions";
import getWebDocumentDownloadOptions from "../webDocs/getWebDocumentDownloadOptions";
import isWebDocument from "../webDocs/isWebDocument";
import getDownloadFileNameFromOptions from "./getDownloadFileNameFromOptions";
export default function getDownloadMediaDetails(options: DownloadMediaOptions) {
const {media, thumb, queueId, onlyCache} = options;
const downloadOptions = media._ === 'document' ? getDocumentDownloadOptions(media, thumb as any, queueId, onlyCache) : getPhotoDownloadOptions(media as any, thumb, queueId, onlyCache);
let downloadOptions: DownloadOptions;
if(media._ === 'document') downloadOptions = getDocumentDownloadOptions(media, thumb as any, queueId, onlyCache);
else if(media._ === 'photo') downloadOptions = getPhotoDownloadOptions(media, thumb, queueId, onlyCache);
else if(isWebDocument(media)) downloadOptions = getWebDocumentDownloadOptions(media);
const fileName = getDownloadFileNameFromOptions(downloadOptions);
return {fileName, downloadOptions};
}

12
src/lib/appManagers/utils/photos/choosePhotoSize.ts

@ -6,11 +6,11 @@ @@ -6,11 +6,11 @@
import type { MyDocument } from "../../appDocsManager";
import type { MyPhoto } from "../../appPhotosManager";
import type { PhotoSize, WebDocument } from "../../../../layer";
import calcImageInBox from "../../../../helpers/calcImageInBox";
import { PhotoSize } from "../../../../layer";
export default function choosePhotoSize(
photo: MyPhoto | MyDocument,
photo: MyPhoto | MyDocument | WebDocument,
boxWidth = 0,
boxHeight = 0,
useBytes = false,
@ -34,12 +34,12 @@ export default function choosePhotoSize( @@ -34,12 +34,12 @@ export default function choosePhotoSize(
let bestPhotoSize: PhotoSize = {_: 'photoSizeEmpty', type: ''};
let sizes = (photo as MyPhoto).sizes || (photo as MyDocument).thumbs as PhotoSize[];
if(pushDocumentSize && sizes && photo._ === 'document') {
if(pushDocumentSize && sizes && photo._ !== 'photo') {
sizes = sizes.concat({
_: 'photoSize',
w: (photo as MyDocument).w,
h: (photo as MyDocument).h,
size: (photo as MyDocument).size,
w: photo.w,
h: photo.h,
size: photo.size,
type: undefined
});
}

15
src/lib/appManagers/utils/webDocs/getWebDocumentDownloadOptions.ts

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
import { WebDocument } from "../../../../layer";
import { DownloadOptions } from "../../../mtproto/apiFileManager";
export default function getWebDocumentDownloadOptions(webDocument: WebDocument): DownloadOptions {
return {
dcId: 4,
location: {
_: 'inputWebFileLocation',
access_hash: (webDocument as WebDocument.webDocument).access_hash,
url: webDocument.url
},
size: webDocument.size,
mimeType: webDocument.mime_type
};
}

5
src/lib/appManagers/utils/webDocs/isWebDocument.ts

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
import { WebDocument } from "../../../../layer";
export default function isWebDocument(webDocument: any): webDocument is WebDocument {
return !!(webDocument && (webDocument._ === 'webDocument' || webDocument._ === 'webDocumentNoProxy'));
}

8
src/lib/fileManager.ts

@ -34,6 +34,14 @@ export class FileManager { @@ -34,6 +34,14 @@ export class FileManager {
throw false;
}
// sometimes file size can be bigger than the prov
const endOffset = offset + part.byteLength;
if(endOffset > bytes.byteLength) {
const newBytes = new Uint8Array(endOffset);
newBytes.set(bytes, 0);
bytes = newBytes;
}
bytes.set(part, offset);
},
truncate: () => {

2
src/lib/langPack.ts

@ -64,7 +64,7 @@ export const langPack: {[actionType: string]: LangPackKey} = { @@ -64,7 +64,7 @@ export const langPack: {[actionType: string]: LangPackKey} = {
"messageActionGroupCall.ended_by": "Chat.Service.VoiceChatFinished",
"messageActionGroupCall.ended_byYou": "Chat.Service.VoiceChatFinishedYou",
"messageActionBotAllowed": "Chat.Service.BotPermissionAllowed"
"messageActionBotAllowed": "Chat.Service.BotPermissionAllowed",
};
export type LangPackKey = /* string | */keyof typeof lang | keyof typeof langSign;

15
src/lib/mtproto/apiFileManager.ts

@ -14,7 +14,7 @@ import Modes from "../../config/modes"; @@ -14,7 +14,7 @@ import Modes from "../../config/modes";
import deferredPromise, { CancellablePromise } from "../../helpers/cancellablePromise";
import { getFileNameByLocation } from "../../helpers/fileName";
import { randomLong } from "../../helpers/random";
import { Document, InputFile, InputFileLocation, InputWebFileLocation, Photo, PhotoSize, UploadFile, UploadWebFile } from "../../layer";
import { Document, InputFile, InputFileLocation, InputWebFileLocation, Photo, PhotoSize, UploadFile, UploadWebFile, WebDocument } from "../../layer";
import { DcId } from "../../types";
import CacheStorageController from "../cacheStorage";
import fileManager from "../fileManager";
@ -54,7 +54,7 @@ export type DownloadOptions = { @@ -54,7 +54,7 @@ export type DownloadOptions = {
};
export type DownloadMediaOptions = {
media: Photo | Document.document,
media: Photo.photo | Document.document | WebDocument,
thumb?: PhotoSize,
queueId?: number,
onlyCache?: boolean
@ -599,14 +599,15 @@ export class ApiFileManager extends AppManager { @@ -599,14 +599,15 @@ export class ApiFileManager extends AppManager {
public downloadMedia(options: DownloadMediaOptions): DownloadPromise {
let {media, thumb} = options;
const isPhoto = media._ === 'photo';
if(media._ === 'photoEmpty' || (isPhoto && !thumb)) {
if(isPhoto && !thumb) {
return Promise.reject('preloadPhoto photoEmpty!');
}
// get original instance with correct file_reference instead of using copies
const isDocument = media._ === 'document';
if(isDocument) media = this.appDocsManager.getDoc(media.id);
else if(isPhoto) media = this.appPhotosManager.getPhoto(media.id);
// const isWebDocument = media._ === 'webDocument';
if(isDocument) media = this.appDocsManager.getDoc((media as Photo.photo).id);
else if(isPhoto) media = this.appPhotosManager.getPhoto((media as Document.document).id);
const {fileName, downloadOptions} = getDownloadMediaDetails(options);
@ -615,9 +616,9 @@ export class ApiFileManager extends AppManager { @@ -615,9 +616,9 @@ export class ApiFileManager extends AppManager {
promise = this.download(downloadOptions);
if(isDocument && !thumb) {
this.rootScope.dispatchEvent('document_downloading', media.id);
this.rootScope.dispatchEvent('document_downloading', (media as Document.document).id);
promise.catch(noop).finally(() => {
this.rootScope.dispatchEvent('document_downloaded', media.id);
this.rootScope.dispatchEvent('document_downloaded', (media as Document.document).id);
});
}
}

19
src/lib/storages/thumbs.ts

@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import type { WebDocument } from "../../layer";
import type { MyDocument } from "../appManagers/appDocsManager";
import type { MyPhoto } from "../appManagers/appPhotosManager";
@ -21,29 +22,33 @@ export type ThumbsCache = { @@ -21,29 +22,33 @@ export type ThumbsCache = {
const thumbFullSize = 'full';
export type ThumbStorageMedia = MyPhoto | MyDocument | WebDocument;
export default class ThumbsStorage {
private thumbsCache: ThumbsCache = {};
public getCacheContext(media: MyPhoto | MyDocument, thumbSize: string = thumbFullSize): ThumbCache {
private getKey(media: ThumbStorageMedia) {
return media._ + ((media as MyPhoto).id ?? (media as WebDocument).url);
}
public getCacheContext(media: ThumbStorageMedia, thumbSize: string = thumbFullSize): ThumbCache {
/* if(media._ === 'photo' && thumbSize !== 'i') {
thumbSize = thumbFullSize;
} */
const key = media._ + media.id;
const cache = this.thumbsCache[key] ??= {};
const cache = this.thumbsCache[this.getKey(media)] ??= {};
return cache[thumbSize] ??= {downloaded: 0, url: '', type: thumbSize};
}
public setCacheContextURL(media: MyPhoto | MyDocument, thumbSize: string = thumbFullSize, url: string, downloaded: number = 0) {
public setCacheContextURL(media: ThumbStorageMedia, thumbSize: string = thumbFullSize, url: string, downloaded: number = 0) {
const cacheContext = this.getCacheContext(media, thumbSize);
cacheContext.url = url;
cacheContext.downloaded = downloaded;
return cacheContext;
}
public deleteCacheContext(media: MyPhoto | MyDocument, thumbSize: string = thumbFullSize) {
const key = media._ + media.id;
const cache = this.thumbsCache[key];
public deleteCacheContext(media: ThumbStorageMedia, thumbSize: string = thumbFullSize) {
const cache = this.thumbsCache[this.getKey(media)];
if(cache) {
delete cache[thumbSize];
}

12
src/scripts/in/schema_additional_params.json

@ -338,4 +338,16 @@ @@ -338,4 +338,16 @@
{"name": "file_size_max", "type": "number"},
{"name": "video_size_max", "type": "number"}
]
}, {
"predicate": "webDocument",
"params": [
{"name": "h", "type": "number"},
{"name": "w", "type": "number"}
]
}, {
"predicate": "webDocumentNoProxy",
"params": [
{"name": "h", "type": "number"},
{"name": "w", "type": "number"}
]
}]

5
src/scss/partials/_chatBubble.scss

@ -2856,3 +2856,8 @@ $bubble-beside-button-width: 38px; @@ -2856,3 +2856,8 @@ $bubble-beside-button-width: 38px;
}
}
}
.bubble-primary-color {
color: var(--message-primary-color);
font-weight: var(--font-weight-bold);
}

Loading…
Cancel
Save