From 3568c5dbe0ca8445bede9fc2301c2f1ae07e3557 Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Fri, 1 Jul 2022 15:43:33 +0200 Subject: [PATCH] Payments: bubble attachment & service message --- src/components/chat/bubbles.ts | 79 +++++++++++++++++-- .../wrappers/messageActionTextNewUnsafe.ts | 52 ++++++------ src/components/wrappers/messageForReply.ts | 5 ++ src/components/wrappers/photo.ts | 12 +-- src/config/currencies.ts | 18 +++++ src/helpers/mediaSizes.ts | 9 ++- src/helpers/paymentsWrapCurrencyAmount.ts | 70 ++++++++++++++++ src/helpers/setAttachmentSize.ts | 16 ++-- src/lang.ts | 8 ++ src/layer.d.ts | 8 +- src/lib/appManagers/appDownloadManager.ts | 2 +- src/lib/appManagers/appMessagesManager.ts | 19 ++++- src/lib/appManagers/appWebDocsManager.ts | 23 ++++++ src/lib/appManagers/createManagers.ts | 4 +- src/lib/appManagers/manager.ts | 2 + .../utils/download/getDownloadMediaDetails.ts | 11 ++- .../utils/photos/choosePhotoSize.ts | 12 +-- .../webDocs/getWebDocumentDownloadOptions.ts | 15 ++++ .../utils/webDocs/isWebDocument.ts | 5 ++ src/lib/fileManager.ts | 8 ++ src/lib/langPack.ts | 2 +- src/lib/mtproto/apiFileManager.ts | 15 ++-- src/lib/storages/thumbs.ts | 19 +++-- src/scripts/in/schema_additional_params.json | 12 +++ src/scss/partials/_chatBubble.scss | 5 ++ 25 files changed, 361 insertions(+), 70 deletions(-) create mode 100644 src/config/currencies.ts create mode 100644 src/helpers/paymentsWrapCurrencyAmount.ts create mode 100644 src/lib/appManagers/appWebDocsManager.ts create mode 100644 src/lib/appManagers/utils/webDocs/getWebDocumentDownloadOptions.ts create mode 100644 src/lib/appManagers/utils/webDocs/isWebDocument.ts diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index c70924e2..9aaead28 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -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 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 = new Set([ @@ -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 { }); 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 { 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 { 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; diff --git a/src/components/wrappers/messageActionTextNewUnsafe.ts b/src/components/wrappers/messageActionTextNewUnsafe.ts index f1400042..4ee1c032 100644 --- a/src/components/wrappers/messageActionTextNewUnsafe.ts +++ b/src/components/wrappers/messageActionTextNewUnsafe.ts @@ -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"; 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, 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, 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; diff --git a/src/components/wrappers/messageForReply.ts b/src/components/wrappers/messageForReply.ts index 5ead3c82..8a0c8c01 100644 --- a/src/components/wrappers/messageForReply.ts +++ b/src/components/wrappers/messageForReply.ts @@ -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; diff --git a/src/components/wrappers/photo.ts b/src/components/wrappers/photo.ts index a213535e..3812cb11 100644 --- a/src/components/wrappers/photo.ts +++ b/src/components/wrappers/photo.ts @@ -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"; 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 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 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 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]); diff --git a/src/config/currencies.ts b/src/config/currencies.ts new file mode 100644 index 00000000..b5d594f8 --- /dev/null +++ b/src/config/currencies.ts @@ -0,0 +1,18 @@ +// Taken from https://core.telegram.org/bots/payments/currencies.json +export type Currency = { + code: string, + title: string, + symbol: string, + native: string, + thousands_sep: string, + decimal_sep: string, + symbol_left: boolean, + space_between: boolean, + exp: number, + min_amount: string | number, + max_amount: string | number +}; + +const Currencies: {[currency: string]: Currency} = {"AED":{"code":"AED","title":"United Arab Emirates Dirham","symbol":"AED","native":"د.إ.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"367","max_amount":"3672990"},"AFN":{"code":"AFN","title":"Afghan Afghani","symbol":"AFN","native":"؋","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"8893","max_amount":"88930176"},"ALL":{"code":"ALL","title":"Albanian Lek","symbol":"ALL","native":"Lek","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":false,"exp":2,"min_amount":"11408","max_amount":"114088432"},"AMD":{"code":"AMD","title":"Armenian Dram","symbol":"AMD","native":"դր.","thousands_sep":",","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"41129","max_amount":"411293180"},"ARS":{"code":"ARS","title":"Argentine Peso","symbol":"ARS","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"12503","max_amount":"125036607"},"AUD":{"code":"AUD","title":"Australian Dollar","symbol":"AU$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"145","max_amount":"1451625"},"AZN":{"code":"AZN","title":"Azerbaijani Manat","symbol":"AZN","native":"ман.","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"171","max_amount":"1711164"},"BAM":{"code":"BAM","title":"Bosnia & Herzegovina Convertible Mark","symbol":"BAM","native":"KM","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"187","max_amount":"1873042"},"BDT":{"code":"BDT","title":"Bangladeshi Taka","symbol":"BDT","native":"৳","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"9415","max_amount":"94154281"},"BGN":{"code":"BGN","title":"Bulgarian Lev","symbol":"BGN","native":"лв.","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"186","max_amount":"1869710"},"BND":{"code":"BND","title":"Brunei Dollar","symbol":"BND","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"139","max_amount":"1399458"},"BOB":{"code":"BOB","title":"Bolivian Boliviano","symbol":"BOB","native":"Bs","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"692","max_amount":"6926834"},"BRL":{"code":"BRL","title":"Brazilian Real","symbol":"R$","native":"R$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"518","max_amount":"5182986"},"CAD":{"code":"CAD","title":"Canadian Dollar","symbol":"CA$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"129","max_amount":"1290450"},"CHF":{"code":"CHF","title":"Swiss Franc","symbol":"CHF","native":"CHF","thousands_sep":"'","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"95","max_amount":"954630"},"CLP":{"code":"CLP","title":"Chilean Peso","symbol":"CLP","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":0,"min_amount":"926","max_amount":"9268013"},"CNY":{"code":"CNY","title":"Chinese Renminbi Yuan","symbol":"CN¥","native":"CN¥","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"669","max_amount":"6692902"},"COP":{"code":"COP","title":"Colombian Peso","symbol":"COP","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"410927","max_amount":"4109270000"},"CRC":{"code":"CRC","title":"Costa Rican Colón","symbol":"CRC","native":"₡","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"69166","max_amount":"691668622"},"CZK":{"code":"CZK","title":"Czech Koruna","symbol":"CZK","native":"Kč","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"2367","max_amount":"23674601"},"DKK":{"code":"DKK","title":"Danish Krone","symbol":"DKK","native":"kr","thousands_sep":"","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"712","max_amount":"7120540"},"DOP":{"code":"DOP","title":"Dominican Peso","symbol":"DOP","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"5497","max_amount":"54971796"},"DZD":{"code":"DZD","title":"Algerian Dinar","symbol":"DZD","native":"د.ج.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"14655","max_amount":"146557782"},"EGP":{"code":"EGP","title":"Egyptian Pound","symbol":"EGP","native":"ج.م.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"1879","max_amount":"18794601"},"EUR":{"code":"EUR","title":"Euro","symbol":"€","native":"€","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"95","max_amount":"957150"},"GBP":{"code":"GBP","title":"British Pound","symbol":"£","native":"£","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"82","max_amount":"822531"},"GEL":{"code":"GEL","title":"Georgian Lari","symbol":"GEL","native":"GEL","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"293","max_amount":"2939866"},"GTQ":{"code":"GTQ","title":"Guatemalan Quetzal","symbol":"GTQ","native":"Q","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"781","max_amount":"7811180"},"HKD":{"code":"HKD","title":"Hong Kong Dollar","symbol":"HK$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"784","max_amount":"7845675"},"HNL":{"code":"HNL","title":"Honduran Lempira","symbol":"HNL","native":"L","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"2476","max_amount":"24763692"},"HRK":{"code":"HRK","title":"Croatian Kuna","symbol":"HRK","native":"kn","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"721","max_amount":"7210988"},"HUF":{"code":"HUF","title":"Hungarian Forint","symbol":"HUF","native":"Ft","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"37819","max_amount":"378197939"},"IDR":{"code":"IDR","title":"Indonesian Rupiah","symbol":"IDR","native":"Rp","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"1490695","max_amount":"14906950000"},"ILS":{"code":"ILS","title":"Israeli New Sheqel","symbol":"₪","native":"₪","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"348","max_amount":"3480155"},"INR":{"code":"INR","title":"Indian Rupee","symbol":"₹","native":"₹","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"7894","max_amount":"78945050"},"ISK":{"code":"ISK","title":"Icelandic Króna","symbol":"ISK","native":"kr","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":0,"min_amount":"133","max_amount":"1336303"},"JMD":{"code":"JMD","title":"Jamaican Dollar","symbol":"JMD","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"15175","max_amount":"151753529"},"JPY":{"code":"JPY","title":"Japanese Yen","symbol":"¥","native":"¥","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":0,"min_amount":"136","max_amount":"1362010"},"KES":{"code":"KES","title":"Kenyan Shilling","symbol":"KES","native":"Ksh","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"11787","max_amount":"117879251"},"KGS":{"code":"KGS","title":"Kyrgyzstani Som","symbol":"KGS","native":"KGS","thousands_sep":" ","decimal_sep":"-","symbol_left":false,"space_between":true,"exp":2,"min_amount":"7950","max_amount":"79509472"},"KRW":{"code":"KRW","title":"South Korean Won","symbol":"₩","native":"₩","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":0,"min_amount":"1297","max_amount":"12971249"},"KZT":{"code":"KZT","title":"Kazakhstani Tenge","symbol":"KZT","native":"₸","thousands_sep":" ","decimal_sep":"-","symbol_left":true,"space_between":false,"exp":2,"min_amount":"47177","max_amount":"471777437"},"LBP":{"code":"LBP","title":"Lebanese Pound","symbol":"LBP","native":"ل.ل.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"152338","max_amount":"1523381760"},"LKR":{"code":"LKR","title":"Sri Lankan Rupee","symbol":"LKR","native":"රු.","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"36271","max_amount":"362713465"},"MAD":{"code":"MAD","title":"Moroccan Dirham","symbol":"MAD","native":"د.م.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"1018","max_amount":"10188182"},"MDL":{"code":"MDL","title":"Moldovan Leu","symbol":"MDL","native":"MDL","thousands_sep":",","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1928","max_amount":"19284237"},"MNT":{"code":"MNT","title":"Mongolian Tögrög","symbol":"MNT","native":"MNT","thousands_sep":" ","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"312408","max_amount":"3124087599"},"MUR":{"code":"MUR","title":"Mauritian Rupee","symbol":"MUR","native":"MUR","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"4614","max_amount":"46144273"},"MVR":{"code":"MVR","title":"Maldivian Rufiyaa","symbol":"MVR","native":"MVR","thousands_sep":",","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1534","max_amount":"15349670"},"MXN":{"code":"MXN","title":"Mexican Peso","symbol":"MX$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"2015","max_amount":"20158770"},"MYR":{"code":"MYR","title":"Malaysian Ringgit","symbol":"MYR","native":"RM","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"440","max_amount":"4406499"},"MZN":{"code":"MZN","title":"Mozambican Metical","symbol":"MZN","native":"MTn","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"6383","max_amount":"63830365"},"NGN":{"code":"NGN","title":"Nigerian Naira","symbol":"NGN","native":"₦","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"41513","max_amount":"415132815"},"NIO":{"code":"NIO","title":"Nicaraguan Córdoba","symbol":"NIO","native":"C$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"3612","max_amount":"36125609"},"NOK":{"code":"NOK","title":"Norwegian Krone","symbol":"NOK","native":"kr","thousands_sep":" ","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"990","max_amount":"9902585"},"NPR":{"code":"NPR","title":"Nepalese Rupee","symbol":"NPR","native":"नेरू","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"12731","max_amount":"127318435"},"NZD":{"code":"NZD","title":"New Zealand Dollar","symbol":"NZ$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"160","max_amount":"1603695"},"PAB":{"code":"PAB","title":"Panamanian Balboa","symbol":"PAB","native":"B\/.","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"100","max_amount":"1007566"},"PEN":{"code":"PEN","title":"Peruvian Nuevo Sol","symbol":"PEN","native":"S\/.","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"381","max_amount":"3818809"},"PHP":{"code":"PHP","title":"Philippine Peso","symbol":"PHP","native":"₱","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"5499","max_amount":"54994501"},"PKR":{"code":"PKR","title":"Pakistani Rupee","symbol":"PKR","native":"₨","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"20651","max_amount":"206515440"},"PLN":{"code":"PLN","title":"Polish Złoty","symbol":"PLN","native":"zł","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"446","max_amount":"4466920"},"PYG":{"code":"PYG","title":"Paraguayan Guaraní","symbol":"PYG","native":"₲","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":0,"min_amount":"6909","max_amount":"69095662"},"QAR":{"code":"QAR","title":"Qatari Riyal","symbol":"QAR","native":"ر.ق.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"364","max_amount":"3640988"},"RON":{"code":"RON","title":"Romanian Leu","symbol":"RON","native":"RON","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"473","max_amount":"4736501"},"RSD":{"code":"RSD","title":"Serbian Dinar","symbol":"RSD","native":"дин.","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"11252","max_amount":"112520089"},"RUB":{"code":"RUB","title":"Russian Ruble","symbol":"RUB","native":"руб.","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"5282","max_amount":"52825030"},"SAR":{"code":"SAR","title":"Saudi Riyal","symbol":"SAR","native":"ر.س.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"375","max_amount":"3752099"},"SEK":{"code":"SEK","title":"Swedish Krona","symbol":"SEK","native":"kr","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1022","max_amount":"10224070"},"SGD":{"code":"SGD","title":"Singapore Dollar","symbol":"SGD","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"139","max_amount":"1390698"},"THB":{"code":"THB","title":"Thai Baht","symbol":"฿","native":"฿","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"3529","max_amount":"35290499"},"TJS":{"code":"TJS","title":"Tajikistani Somoni","symbol":"TJS","native":"TJS","thousands_sep":" ","decimal_sep":";","symbol_left":false,"space_between":true,"exp":2,"min_amount":"977","max_amount":"9773409"},"TRY":{"code":"TRY","title":"Turkish Lira","symbol":"TRY","native":"TL","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1667","max_amount":"16673549"},"TTD":{"code":"TTD","title":"Trinidad and Tobago Dollar","symbol":"TTD","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"684","max_amount":"6847347"},"TWD":{"code":"TWD","title":"New Taiwan Dollar","symbol":"NT$","native":"NT$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"2973","max_amount":"29735499"},"TZS":{"code":"TZS","title":"Tanzanian Shilling","symbol":"TZS","native":"TSh","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"233200","max_amount":"2332000087"},"UAH":{"code":"UAH","title":"Ukrainian Hryvnia","symbol":"UAH","native":"₴","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":false,"exp":2,"min_amount":"2974","max_amount":"29741945"},"UGX":{"code":"UGX","title":"Ugandan Shilling","symbol":"UGX","native":"USh","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":0,"min_amount":"3788","max_amount":"37883728"},"USD":{"code":"USD","title":"United States Dollar","symbol":"$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"100","max_amount":1000000},"UYU":{"code":"UYU","title":"Uruguayan Peso","symbol":"UYU","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"3979","max_amount":"39794286"},"UZS":{"code":"UZS","title":"Uzbekistani Som","symbol":"UZS","native":"UZS","thousands_sep":" ","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1094209","max_amount":"10942099215"},"VND":{"code":"VND","title":"Vietnamese Đồng","symbol":"₫","native":"₫","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":0,"min_amount":"23270","max_amount":"232700000"},"YER":{"code":"YER","title":"Yemeni Rial","symbol":"YER","native":"ر.ي.‏","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"25024","max_amount":"250249914"},"ZAR":{"code":"ZAR","title":"South African Rand","symbol":"ZAR","native":"R","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"1624","max_amount":"16246189"}}; + +export default Currencies; diff --git a/src/helpers/mediaSizes.ts b/src/helpers/mediaSizes.ts index ad7449b0..807e3c2b 100644 --- a/src/helpers/mediaSizes.ts +++ b/src/helpers/mediaSizes.ts @@ -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<{ 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<{ emojiSticker: makeMediaSize(112, 112), poll: makeMediaSize(330, 0), round: makeMediaSize(280, 280), - documentName: makeMediaSize(240, 0) + documentName: makeMediaSize(240, 0), + invoice: makeMediaSize(320, 260) } }; diff --git a/src/helpers/paymentsWrapCurrencyAmount.ts b/src/helpers/paymentsWrapCurrencyAmount.ts new file mode 100644 index 00000000..e6620ec9 --- /dev/null +++ b/src/helpers/paymentsWrapCurrencyAmount.ts @@ -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; +} diff --git a/src/helpers/setAttachmentSize.ts b/src/helpers/setAttachmentSize.ts index 52878956..403d10cc 100644 --- a/src/helpers/setAttachmentSize.ts +++ b/src/helpers/setAttachmentSize.ts @@ -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( pushDocumentSize?: boolean, photoSize?: ReturnType ) { + 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( 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( 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)); } diff --git a/src/lang.ts b/src/lang.ts index c16232a5..48b641bb 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -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 = { "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", diff --git a/src/layer.d.ts b/src/layer.d.ts index bb9f06c5..1a906ef6 100644 --- a/src/layer.d.ts +++ b/src/layer.d.ts @@ -5881,7 +5881,9 @@ export namespace WebDocument { access_hash: string | number, size: number, mime_type: string, - attributes: Array + attributes: Array, + h?: number, + w?: number }; export type webDocumentNoProxy = { @@ -5889,7 +5891,9 @@ export namespace WebDocument { url: string, size: number, mime_type: string, - attributes: Array + attributes: Array, + h?: number, + w?: number }; } diff --git a/src/lib/appManagers/appDownloadManager.ts b/src/lib/appManagers/appDownloadManager.ts index 79ef1679..9d64a073 100644 --- a/src/lib/appManagers/appDownloadManager.ts +++ b/src/lib/appManagers/appDownloadManager.ts @@ -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"; diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 47762baf..35978725 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -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 { 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; }); } diff --git a/src/lib/appManagers/appWebDocsManager.ts b/src/lib/appManagers/appWebDocsManager.ts new file mode 100644 index 00000000..2313596d --- /dev/null +++ b/src/lib/appManagers/appWebDocsManager.ts @@ -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; + } +} diff --git a/src/lib/appManagers/createManagers.ts b/src/lib/appManagers/createManagers.ts index 471cc5ca..2bad18db 100644 --- a/src/lib/appManagers/createManagers.ts +++ b/src/lib/appManagers/createManagers.ts @@ -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 dcConfigurator: new DcConfigurator, timeManager: new TimeManager, appStoragesManager: appStoragesManager, - appStateManager: appStateManager + appStateManager: appStateManager, + appWebDocsManager: new AppWebDocsManager }; type T = typeof managers; diff --git a/src/lib/appManagers/manager.ts b/src/lib/appManagers/manager.ts index a9a177fb..9ad0b706 100644 --- a/src/lib/appManagers/manager.ts +++ b/src/lib/appManagers/manager.ts @@ -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 { protected timeManager: TimeManager; protected appStoragesManager: AppStoragesManager; protected appStateManager: AppStateManager; + protected appWebDocsManager: AppWebDocsManager; public clear: (init?: boolean) => void; diff --git a/src/lib/appManagers/utils/download/getDownloadMediaDetails.ts b/src/lib/appManagers/utils/download/getDownloadMediaDetails.ts index 70c5976c..180c9a56 100644 --- a/src/lib/appManagers/utils/download/getDownloadMediaDetails.ts +++ b/src/lib/appManagers/utils/download/getDownloadMediaDetails.ts @@ -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}; } diff --git a/src/lib/appManagers/utils/photos/choosePhotoSize.ts b/src/lib/appManagers/utils/photos/choosePhotoSize.ts index 7a11eddd..977c0dae 100644 --- a/src/lib/appManagers/utils/photos/choosePhotoSize.ts +++ b/src/lib/appManagers/utils/photos/choosePhotoSize.ts @@ -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( 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 }); } diff --git a/src/lib/appManagers/utils/webDocs/getWebDocumentDownloadOptions.ts b/src/lib/appManagers/utils/webDocs/getWebDocumentDownloadOptions.ts new file mode 100644 index 00000000..16c729f3 --- /dev/null +++ b/src/lib/appManagers/utils/webDocs/getWebDocumentDownloadOptions.ts @@ -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 + }; +} diff --git a/src/lib/appManagers/utils/webDocs/isWebDocument.ts b/src/lib/appManagers/utils/webDocs/isWebDocument.ts new file mode 100644 index 00000000..f64c443c --- /dev/null +++ b/src/lib/appManagers/utils/webDocs/isWebDocument.ts @@ -0,0 +1,5 @@ +import { WebDocument } from "../../../../layer"; + +export default function isWebDocument(webDocument: any): webDocument is WebDocument { + return !!(webDocument && (webDocument._ === 'webDocument' || webDocument._ === 'webDocumentNoProxy')); +} diff --git a/src/lib/fileManager.ts b/src/lib/fileManager.ts index 811d1521..990f7d82 100644 --- a/src/lib/fileManager.ts +++ b/src/lib/fileManager.ts @@ -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: () => { diff --git a/src/lib/langPack.ts b/src/lib/langPack.ts index 8724ac14..549789d1 100644 --- a/src/lib/langPack.ts +++ b/src/lib/langPack.ts @@ -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; diff --git a/src/lib/mtproto/apiFileManager.ts b/src/lib/mtproto/apiFileManager.ts index c1d42aa2..b999612c 100644 --- a/src/lib/mtproto/apiFileManager.ts +++ b/src/lib/mtproto/apiFileManager.ts @@ -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 = { }; 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 { 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 { 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); }); } } diff --git a/src/lib/storages/thumbs.ts b/src/lib/storages/thumbs.ts index b40b10b8..550939dc 100644 --- a/src/lib/storages/thumbs.ts +++ b/src/lib/storages/thumbs.ts @@ -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 = { 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]; } diff --git a/src/scripts/in/schema_additional_params.json b/src/scripts/in/schema_additional_params.json index 49053967..3606b279 100644 --- a/src/scripts/in/schema_additional_params.json +++ b/src/scripts/in/schema_additional_params.json @@ -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"} + ] }] \ No newline at end of file diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index 4afed5d0..ca4a9f79 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -2856,3 +2856,8 @@ $bubble-beside-button-width: 38px; } } } + +.bubble-primary-color { + color: var(--message-primary-color); + font-weight: var(--font-weight-bold); +}