diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts index 4f9752eff..dfdb68dd8 100644 --- a/src/api/gramjs/apiBuilders/messages.ts +++ b/src/api/gramjs/apiBuilders/messages.ts @@ -336,7 +336,7 @@ function buildAction( let currency: string | undefined; let giftCryptoInfo: { currency: string; - amount: string; + amount: number; } | undefined; let text: string; const translationValues: string[] = []; @@ -499,10 +499,9 @@ function buildAction( } currency = action.currency; if (action.cryptoCurrency) { - const cryptoAmountWithDecimals = action.cryptoAmount!.divide(1e7).toJSNumber() / 100; giftCryptoInfo = { currency: action.cryptoCurrency, - amount: cryptoAmountWithDecimals.toFixed(2), + amount: action.cryptoAmount!.toJSNumber(), }; } amount = action.amount.toJSNumber(); @@ -552,10 +551,9 @@ function buildAction( } currency = action.currency; if (action.cryptoCurrency) { - const cryptoAmountWithDecimals = action.cryptoAmount!.divide(1e7).toJSNumber() / 100; giftCryptoInfo = { currency: action.cryptoCurrency, - amount: cryptoAmountWithDecimals.toFixed(2), + amount: action.cryptoAmount!.toJSNumber(), }; } if (action.boostPeer) { diff --git a/src/api/gramjs/apiBuilders/misc.ts b/src/api/gramjs/apiBuilders/misc.ts index 1ab04a708..dc0755ac5 100644 --- a/src/api/gramjs/apiBuilders/misc.ts +++ b/src/api/gramjs/apiBuilders/misc.ts @@ -3,6 +3,7 @@ import { Api as GramJs } from '../../../lib/gramjs'; import type { ApiPrivacyKey } from '../../../types'; import type { ApiChatLink, + ApiCollectionInfo, ApiConfig, ApiCountry, ApiLangString, ApiPeerColors, ApiSession, ApiTimezone, ApiUrlAuthResult, ApiWallpaper, ApiWebSession, @@ -274,3 +275,23 @@ export function buildApiChatLink(data: GramJs.account.ResolvedBusinessChatLinks) text: buildMessageTextContent(data.message, data.entities), }; } + +export function buildApiCollectibleInfo(info: GramJs.fragment.TypeCollectibleInfo): ApiCollectionInfo { + const { + amount, + currency, + cryptoAmount, + cryptoCurrency, + purchaseDate, + url, + } = info; + + return { + amount: amount.toJSNumber(), + currency, + cryptoAmount: cryptoAmount.toJSNumber(), + cryptoCurrency, + purchaseDate, + url, + }; +} diff --git a/src/api/gramjs/methods/fragment.ts b/src/api/gramjs/methods/fragment.ts new file mode 100644 index 000000000..4709cb053 --- /dev/null +++ b/src/api/gramjs/methods/fragment.ts @@ -0,0 +1,26 @@ +import { Api as GramJs } from '../../../lib/gramjs'; + +import { buildApiCollectibleInfo } from '../apiBuilders/misc'; +import { invokeRequest } from './client'; + +type InputCollectible = { + phone: string; +} | { + username: string; +}; + +export async function fetchCollectionInfo(collectible: InputCollectible) { + const inputCollectible = 'username' in collectible + ? new GramJs.InputCollectibleUsername({ username: collectible.username }) + : new GramJs.InputCollectiblePhone({ phone: collectible.phone }); + + const result = await invokeRequest(new GramJs.fragment.GetCollectibleInfo({ + collectible: inputCollectible, + })); + + if (!result) { + return undefined; + } + + return buildApiCollectibleInfo(result); +} diff --git a/src/api/gramjs/methods/index.ts b/src/api/gramjs/methods/index.ts index b448d0ee7..42bec0586 100644 --- a/src/api/gramjs/methods/index.ts +++ b/src/api/gramjs/methods/index.ts @@ -102,3 +102,5 @@ export { applyBoost, fetchBoostList, fetchBoostStatus, fetchGiveawayInfo, fetchMyBoosts, applyGiftCode, checkGiftCode, getPremiumGiftCodeOptions, launchPrepaidGiveaway, } from './payments'; + +export * from './fragment'; diff --git a/src/api/types/messages.ts b/src/api/types/messages.ts index 7f3e794b2..9b9da973a 100644 --- a/src/api/types/messages.ts +++ b/src/api/types/messages.ts @@ -351,7 +351,7 @@ export interface ApiAction { currency?: string; giftCryptoInfo?: { currency: string; - amount: string; + amount: number; }; translationValues: string[]; call?: Partial; diff --git a/src/api/types/misc.ts b/src/api/types/misc.ts index 6848ef05e..bf664b562 100644 --- a/src/api/types/misc.ts +++ b/src/api/types/misc.ts @@ -267,3 +267,12 @@ type ApiUrlAuthResultDefault = { }; export type ApiUrlAuthResult = ApiUrlAuthResultRequest | ApiUrlAuthResultAccepted | ApiUrlAuthResultDefault; + +export interface ApiCollectionInfo { + amount: number; + currency: string; + cryptoAmount: number; + cryptoCurrency: string; + purchaseDate: number; + url: string; +} diff --git a/src/assets/tgs/general/Fragment.tgs b/src/assets/tgs/general/Fragment.tgs new file mode 100644 index 000000000..bcd1af2d0 Binary files /dev/null and b/src/assets/tgs/general/Fragment.tgs differ diff --git a/src/assets/tgs/general/Mention.tgs b/src/assets/tgs/general/Mention.tgs new file mode 100644 index 000000000..f93fd6d74 Binary files /dev/null and b/src/assets/tgs/general/Mention.tgs differ diff --git a/src/bundles/extra.ts b/src/bundles/extra.ts index c315f4df1..a70f74f04 100644 --- a/src/bundles/extra.ts +++ b/src/bundles/extra.ts @@ -39,6 +39,7 @@ export { default as CountryPickerModal } from '../components/common/CountryPicke export { default as ReactorListModal } from '../components/middle/ReactorListModal'; export { default as EmojiInteractionAnimation } from '../components/middle/EmojiInteractionAnimation'; export { default as ChatLanguageModal } from '../components/middle/ChatLanguageModal'; +export { default as CollectibleInfoModal } from '../components/modals/collectible/CollectibleInfoModal'; export { default as LeftSearch } from '../components/left/search/LeftSearch'; export { default as Settings } from '../components/left/settings/Settings'; diff --git a/src/components/common/helpers/animatedAssets.ts b/src/components/common/helpers/animatedAssets.ts index b87170fc0..0608051fd 100644 --- a/src/components/common/helpers/animatedAssets.ts +++ b/src/components/common/helpers/animatedAssets.ts @@ -8,6 +8,8 @@ import VoiceMini from '../../../assets/tgs/calls/VoiceMini.tgs'; import VoiceMuted from '../../../assets/tgs/calls/VoiceMuted.tgs'; import VoiceOutlined from '../../../assets/tgs/calls/VoiceOutlined.tgs'; import Flame from '../../../assets/tgs/general/Flame.tgs'; +import Fragment from '../../../assets/tgs/general/Fragment.tgs'; +import Mention from '../../../assets/tgs/general/Mention.tgs'; import PartyPopper from '../../../assets/tgs/general/PartyPopper.tgs'; import Invite from '../../../assets/tgs/invites/Invite.tgs'; import JoinRequest from '../../../assets/tgs/invites/Requests.tgs'; @@ -54,4 +56,6 @@ export const LOCAL_TGS_URLS = { ReadTime, Unlock, LastSeen, + Mention, + Fragment, }; diff --git a/src/components/common/helpers/formatUsername.ts b/src/components/common/helpers/formatUsername.ts new file mode 100644 index 000000000..4c114a7b1 --- /dev/null +++ b/src/components/common/helpers/formatUsername.ts @@ -0,0 +1,5 @@ +import { TME_LINK_PREFIX } from '../../../config'; + +export default function formatUsername(username: string, asAbsoluteLink?: boolean) { + return asAbsoluteLink ? `${TME_LINK_PREFIX}${username}` : `@${username}`; +} diff --git a/src/components/common/helpers/renderActionMessageText.tsx b/src/components/common/helpers/renderActionMessageText.tsx index 0ea61633c..1cbce3c92 100644 --- a/src/components/common/helpers/renderActionMessageText.tsx +++ b/src/components/common/helpers/renderActionMessageText.tsx @@ -138,7 +138,8 @@ export function renderActionMessageText( let priceText = price; if (giftCryptoInfo) { - priceText = `${giftCryptoInfo.amount} ${giftCryptoInfo.currency} (~${price})`; + const cryptoPrice = formatCurrency(giftCryptoInfo.amount, giftCryptoInfo.currency, lang.code); + priceText = `${cryptoPrice} (${price})`; } processed = processPlaceholder( diff --git a/src/components/common/profile/ChatExtra.tsx b/src/components/common/profile/ChatExtra.tsx index d12f5eb50..f20c35ebb 100644 --- a/src/components/common/profile/ChatExtra.tsx +++ b/src/components/common/profile/ChatExtra.tsx @@ -9,7 +9,7 @@ import type { } from '../../../api/types'; import { MAIN_THREAD_ID } from '../../../api/types'; -import { TME_LINK_PREFIX } from '../../../config'; +import { FRAGMENT_PHONE_CODE, FRAGMENT_PHONE_LENGTH } from '../../../config'; import { buildStaticMapHash, getChatLink, @@ -33,6 +33,7 @@ import { formatPhoneNumberWithCode } from '../../../util/phoneNumber'; import { debounce } from '../../../util/schedulers'; import stopEvent from '../../../util/stopEvent'; import { ChatAnimationTypes } from '../../left/main/hooks'; +import formatUsername from '../helpers/formatUsername'; import renderText from '../helpers/renderText'; import useEffectWithPrevDeps from '../../../hooks/useEffectWithPrevDeps'; @@ -102,6 +103,7 @@ const ChatExtra: FC = ({ loadPeerStories, openSavedDialog, openMapModal, + requestCollectibleInfo, } = getActions(); const { @@ -206,10 +208,6 @@ const ChatExtra: FC = ({ openSavedDialog({ chatId: chatOrUserId }); }); - if (!chat || chat.isRestricted || (isSelf && !isInSettings)) { - return undefined; - } - function copy(text: string, entity: string) { copyTextToClipboard(text); showNotification({ message: `${entity} was copied` }); @@ -217,6 +215,26 @@ const ChatExtra: FC = ({ const formattedNumber = phoneNumber && formatPhoneNumberWithCode(phoneCodeList, phoneNumber); + const handlePhoneClick = useLastCallback(() => { + if (phoneNumber?.length === FRAGMENT_PHONE_LENGTH && phoneNumber.startsWith(FRAGMENT_PHONE_CODE)) { + requestCollectibleInfo({ collectible: phoneNumber, userId: userId!, type: 'phone' }); + return; + } + copy(formattedNumber!, lang('Phone')); + }); + + const handleUsernameClick = useLastCallback((username: ApiUsername, isChat?: boolean) => { + if (!username.isEditable) { + requestCollectibleInfo({ collectible: username.username, userId: userId!, type: 'username' }); + return; + } + copy(formatUsername(username.username, isChat), lang(isChat ? 'Link' : 'Username')); + }); + + if (!chat || chat.isRestricted || (isSelf && !isInSettings)) { + return undefined; + } + function renderUsernames(usernameList: ApiUsername[], isChat?: boolean) { const [mainUsername, ...otherUsernames] = usernameList; @@ -226,21 +244,21 @@ const ChatExtra: FC = ({ .map((s) => { return (s === 'USERNAMES' ? ( <> - {otherUsernames.map(({ username: nick }, idx) => { - const textToCopy = isChat ? `${TME_LINK_PREFIX}${nick}` : `@${nick}`; + {otherUsernames.map((username, idx) => { return ( <> {idx > 0 ? ', ' : ''} { stopEvent(e); - copy(textToCopy, lang(isChat ? 'Link' : 'Username')); + handleUsernameClick(username, isChat); }} className="text-entity-link username-link" > - {`@${nick}`} + {formatUsername(username.username)} ); @@ -250,9 +268,6 @@ const ChatExtra: FC = ({ }) : undefined; - const username = isChat ? `t.me/${mainUsername.username}` : mainUsername.username; - const textToCopy = isChat ? `${TME_LINK_PREFIX}${mainUsername.username}` : `@${mainUsername.username}`; - return ( = ({ narrow ripple // eslint-disable-next-line react/jsx-no-bind - onClick={() => copy(textToCopy, lang(isChat ? 'Link' : 'Username'))} + onClick={() => { + handleUsernameClick(mainUsername, isChat); + }} > - {username} + {formatUsername(mainUsername.username, isChat)} {usernameLinks && {usernameLinks}} {lang(isChat ? 'Link' : 'Username')} @@ -289,9 +306,9 @@ const ChatExtra: FC = ({ /> )} - {formattedNumber && Boolean(formattedNumber.length) && ( + {Boolean(formattedNumber?.length) && ( // eslint-disable-next-line react/jsx-no-bind - copy(formattedNumber, lang('Phone'))}> + {formattedNumber} {lang('Phone')} diff --git a/src/components/modals/ModalContainer.tsx b/src/components/modals/ModalContainer.tsx index 3031de69e..08335c614 100644 --- a/src/components/modals/ModalContainer.tsx +++ b/src/components/modals/ModalContainer.tsx @@ -9,6 +9,7 @@ import { pick } from '../../util/iteratees'; import AttachBotInstallModal from './attachBotInstall/AttachBotInstallModal.async'; import BoostModal from './boost/BoostModal.async'; import ChatlistModal from './chatlist/ChatlistModal.async'; +import CollectibleInfoModal from './collectible/CollectibleInfoModal.async'; import GiftCodeModal from './giftcode/GiftCodeModal.async'; import InviteViaLinkModal from './inviteViaLink/InviteViaLinkModal.async'; import OneTimeMediaModal from './oneTimeMedia/OneTimeMediaModal.async'; @@ -25,6 +26,7 @@ type ModalKey = keyof Pick; @@ -51,6 +53,7 @@ const MODALS: ModalRegistry = { requestedAttachBotInstall: AttachBotInstallModal, reportAdModal: ReportAdModal, webApp: WebAppModal, + collectibleInfoModal: CollectibleInfoModal, }; const MODAL_KEYS = Object.keys(MODALS) as ModalKey[]; const MODAL_ENTRIES = Object.entries(MODALS) as Entries; diff --git a/src/components/modals/collectible/CollectibleInfoModal.async.tsx b/src/components/modals/collectible/CollectibleInfoModal.async.tsx new file mode 100644 index 000000000..0dd162896 --- /dev/null +++ b/src/components/modals/collectible/CollectibleInfoModal.async.tsx @@ -0,0 +1,18 @@ +import type { FC } from '../../../lib/teact/teact'; +import React from '../../../lib/teact/teact'; + +import type { OwnProps } from './CollectibleInfoModal'; + +import { Bundles } from '../../../util/moduleLoader'; + +import useModuleLoader from '../../../hooks/useModuleLoader'; + +const CollectibleInfoModalAsync: FC = (props) => { + const { modal } = props; + const CollectibleInfoModal = useModuleLoader(Bundles.Extra, 'CollectibleInfoModal', !modal); + + // eslint-disable-next-line react/jsx-props-no-spreading + return CollectibleInfoModal ? : undefined; +}; + +export default CollectibleInfoModalAsync; diff --git a/src/components/modals/collectible/CollectibleInfoModal.module.scss b/src/components/modals/collectible/CollectibleInfoModal.module.scss new file mode 100644 index 000000000..f9a50f4a1 --- /dev/null +++ b/src/components/modals/collectible/CollectibleInfoModal.module.scss @@ -0,0 +1,33 @@ +.content { + display: flex; + flex-direction: column; + align-items: center; +} + +.closeButton { + position: absolute; + top: 0.5rem; + left: 0.5rem; +} + +.icon { + width: 5rem; + height: 5rem; + border-radius: 50%; + + display: grid; + place-items: center; + + flex-shrink: 0; + background-color: var(--color-primary); +} + +.title, .description { + text-align: center !important; + text-wrap: pretty; + padding: 0 1rem; +} + +.title { + margin-top: 1rem; +} diff --git a/src/components/modals/collectible/CollectibleInfoModal.tsx b/src/components/modals/collectible/CollectibleInfoModal.tsx new file mode 100644 index 000000000..7724b59e1 --- /dev/null +++ b/src/components/modals/collectible/CollectibleInfoModal.tsx @@ -0,0 +1,156 @@ +import type { FC } from '../../../lib/teact/teact'; +import React, { + memo, + useMemo, +} from '../../../lib/teact/teact'; +import { getActions, withGlobal } from '../../../global'; + +import type { ApiCountryCode } from '../../../api/types'; +import type { TabState } from '../../../global/types'; + +import { copyTextToClipboard } from '../../../util/clipboard'; +import { formatDateAtTime } from '../../../util/date/dateFormat'; +import { formatCurrency } from '../../../util/formatCurrency'; +import { formatPhoneNumberWithCode } from '../../../util/phoneNumber'; +import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets'; +import formatUsername from '../../common/helpers/formatUsername'; +import renderText from '../../common/helpers/renderText'; + +import useLang from '../../../hooks/useLang'; +import useLastCallback from '../../../hooks/useLastCallback'; + +import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview'; +import Icon from '../../common/Icon'; +import PickerSelectedItem from '../../common/PickerSelectedItem'; +import Button from '../../ui/Button'; +import Modal from '../../ui/Modal'; + +import styles from './CollectibleInfoModal.module.scss'; + +export type OwnProps = { + modal: TabState['collectibleInfoModal']; +}; + +type StateProps = { + phoneCodeList: ApiCountryCode[]; +}; + +const TOP_ICON_SIZE = 60; + +const CollectibleInfoModal: FC = ({ + modal, + phoneCodeList, +}) => { + const { + closeCollectibleInfoModal, + openChat, + openUrl, + showNotification, + } = getActions(); + const lang = useLang(); + + const isUsername = modal?.type === 'username'; + + const handleClose = useLastCallback(() => { + closeCollectibleInfoModal(); + }); + + const handleOpenChat = useLastCallback(() => { + openChat({ id: modal!.userId }); + handleClose(); + }); + + const handleOpenUrl = useLastCallback(() => { + openUrl({ + url: modal!.url, + shouldSkipModal: true, + }); + handleClose(); + }); + + const handleCopy = useLastCallback(() => { + const text = isUsername ? formatUsername(modal!.collectible) + : formatPhoneNumberWithCode(phoneCodeList, modal!.collectible); + copyTextToClipboard(text); + showNotification({ + message: lang(isUsername ? 'UsernameCopied' : 'PhoneCopied'), + }); + handleClose(); + }); + + const title = useMemo(() => { + if (!modal) return undefined; + const key = isUsername ? 'FragmentUsernameTitle' : 'FragmentPhoneTitle'; + const formattedCollectible = isUsername + ? formatUsername(modal.collectible) + : formatPhoneNumberWithCode(phoneCodeList, modal.collectible); + return lang(key, formattedCollectible); + }, [modal, isUsername, phoneCodeList, lang]); + + const description = useMemo(() => { + if (!modal) return undefined; + const key = isUsername ? 'FragmentUsernameMessage' : 'FragmentPhoneMessage'; + const date = formatDateAtTime(lang, modal.purchaseDate * 1000); + const currency = formatCurrency(modal.amount, modal.currency, lang.code); + const cryptoCurrency = formatCurrency(modal.cryptoAmount, modal.cryptoCurrency, lang.code); + const paid = `${cryptoCurrency} (${currency})`; + return lang(key, [date, paid]); + }, [modal, isUsername, lang]); + + return ( + + +
+ +
+

+ {title && renderText(title, ['simple_markdown'])} +

+ +

+ {description && renderText(description, ['simple_markdown'])} +

+
+ + +
+
+ ); +}; + +export default memo(withGlobal( + (global): StateProps => { + const { countryList } = global; + + return { + phoneCodeList: countryList.phoneCodes, + }; + }, +)(CollectibleInfoModal)); diff --git a/src/config.ts b/src/config.ts index 923eb364d..bf0653b3a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -319,6 +319,8 @@ export const GIVEAWAY_MAX_ADDITIONAL_CHANNELS = 10; export const GIVEAWAY_MAX_ADDITIONAL_USERS = 10; export const GIVEAWAY_MAX_ADDITIONAL_COUNTRIES = 10; export const BOOST_PER_SENT_GIFT = 3; +export const FRAGMENT_PHONE_CODE = '888'; +export const FRAGMENT_PHONE_LENGTH = 11; export const LIGHT_THEME_BG_COLOR = '#99BA92'; export const DARK_THEME_BG_COLOR = '#0F0F0F'; diff --git a/src/global/actions/api/chats.ts b/src/global/actions/api/chats.ts index 26c6679ce..d54ba2c9a 100644 --- a/src/global/actions/api/chats.ts +++ b/src/global/actions/api/chats.ts @@ -30,6 +30,7 @@ import { TOPICS_SLICE, TOPICS_SLICE_SECOND_LOAD, } from '../../../config'; +import { copyTextToClipboard } from '../../../util/clipboard'; import { formatShareText, parseChooseParameter, processDeepLink } from '../../../util/deeplink'; import { isDeepLink } from '../../../util/deepLinkParser'; import { getCurrentTabId } from '../../../util/establishMultitabRole'; @@ -2673,6 +2674,38 @@ addActionHandler('resolveBusinessChatLink', async (global, actions, payload): Pr }); }); +addActionHandler('requestCollectibleInfo', async (global, actions, payload): Promise => { + const { + type, collectible, userId, tabId = getCurrentTabId(), + } = payload; + + let inputCollectible; + if (type === 'phone') { + inputCollectible = { phone: collectible }; + } + if (type === 'username') { + inputCollectible = { username: collectible }; + } + if (!inputCollectible) return; + + const result = await callApi('fetchCollectionInfo', inputCollectible); + if (!result) { + copyTextToClipboard(collectible); + return; + } + + global = getGlobal(); + global = updateTabState(global, { + collectibleInfoModal: { + ...result, + type, + collectible, + userId, + }, + }, tabId); + setGlobal(global); +}); + async function loadChats( listType: ChatListType, offsetId?: string, diff --git a/src/global/actions/ui/misc.ts b/src/global/actions/ui/misc.ts index 7cd44a83f..20286d368 100644 --- a/src/global/actions/ui/misc.ts +++ b/src/global/actions/ui/misc.ts @@ -744,6 +744,13 @@ addActionHandler('closeInviteViaLinkModal', (global, actions, payload): ActionRe }, tabId); }); +addActionHandler('closeCollectibleInfoModal', (global, actions, payload): ActionReturnType => { + const { tabId = getCurrentTabId() } = payload ?? {}; + return updateTabState(global, { + collectibleInfoModal: undefined, + }, tabId); +}); + addActionHandler('setShouldCloseRightColumn', (global, actions, payload): ActionReturnType => { const { value, tabId = getCurrentTabId() } = payload; return updateTabState(global, { diff --git a/src/global/types.ts b/src/global/types.ts index c82a2630f..a46e9b0e0 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -16,6 +16,7 @@ import type { ApiChatReactions, ApiChatType, ApiCheckedGiftCode, + ApiCollectionInfo, ApiConfig, ApiContact, ApiCountry, @@ -738,6 +739,12 @@ export type TabState = { oneTimeMediaModal?: { message: ApiMessage; }; + + collectibleInfoModal?: ApiCollectionInfo & { + userId: string; + type: 'phone' | 'username'; + collectible: string; + }; }; export type GlobalState = { @@ -2867,6 +2874,13 @@ export interface ActionPayloads { openOneTimeMediaModal: TabState['oneTimeMediaModal'] & WithTabId; closeOneTimeMediaModal: WithTabId | undefined; + requestCollectibleInfo: { + userId: string; + type : 'phone' | 'username'; + collectible: string; + } & WithTabId; + closeCollectibleInfoModal: WithTabId | undefined; + // Calls joinGroupCall: { chatId?: string; diff --git a/src/lib/gramjs/tl/apiTl.js b/src/lib/gramjs/tl/apiTl.js index b662ac747..6d9530d49 100644 --- a/src/lib/gramjs/tl/apiTl.js +++ b/src/lib/gramjs/tl/apiTl.js @@ -1637,4 +1637,5 @@ stories.togglePeerStoriesHidden#bd0415c4 peer:InputPeer hidden:Bool = Bool; premium.getBoostsList#60f67660 flags:# gifts:flags.0?true peer:InputPeer offset:string limit:int = premium.BoostsList; premium.getMyBoosts#be77b4a = premium.MyBoosts; premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector peer:InputPeer = premium.MyBoosts; -premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus;`; \ No newline at end of file +premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus; +fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;`; \ No newline at end of file diff --git a/src/lib/gramjs/tl/static/api.json b/src/lib/gramjs/tl/static/api.json index c50389935..676642104 100644 --- a/src/lib/gramjs/tl/static/api.json +++ b/src/lib/gramjs/tl/static/api.json @@ -349,5 +349,6 @@ "payments.applyGiftCode", "payments.getGiveawayInfo", "payments.getPremiumGiftCodeOptions", - "payments.launchPrepaidGiveaway" + "payments.launchPrepaidGiveaway", + "fragment.getCollectibleInfo" ] diff --git a/src/util/formatCurrency.ts b/src/util/formatCurrency.ts index 7bc07c478..4a7bf9468 100644 --- a/src/util/formatCurrency.ts +++ b/src/util/formatCurrency.ts @@ -24,6 +24,9 @@ export function formatCurrency( } function getCurrencyExp(currency: string) { + if (currency === 'TON') { + return 9; + } if (currency === 'CLF') { return 4; }