From f1a538ed8eb0bf7df50fb3dd81b13bc7f6991715 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Wed, 4 Jun 2025 20:41:29 +0200 Subject: [PATCH] Settings Privacy: Implement account self destruction (#5939) Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com> --- src/api/gramjs/methods/account.ts | 26 ++++ src/assets/localization/fallback.strings | 4 + src/bundles/extra.ts | 1 + .../left/settings/SettingsPrivacy.tsx | 38 ++++++ src/components/modals/ModalContainer.tsx | 5 +- .../DeleteAccountModal.async.tsx | 17 +++ .../DeleteAccountModal.module.scss | 3 + .../deleteAccount/DeleteAccountModal.tsx | 117 ++++++++++++++++++ src/config.ts | 2 + src/global/actions/api/accounts.ts | 34 +++++ src/global/actions/ui/account.ts | 24 +++- src/global/cache.ts | 7 +- src/global/initialState.ts | 1 + src/global/types/actions.ts | 8 ++ src/global/types/globalState.ts | 1 + src/global/types/tabState.ts | 4 + src/lib/gramjs/tl/api.d.ts | 96 +++++++++++--- src/lib/gramjs/tl/apiTl.ts | 2 + src/lib/gramjs/tl/static/api.json | 2 + src/types/language.d.ts | 4 + src/util/getClosestEntry.ts | 5 + 21 files changed, 380 insertions(+), 21 deletions(-) create mode 100644 src/components/modals/deleteAccount/DeleteAccountModal.async.tsx create mode 100644 src/components/modals/deleteAccount/DeleteAccountModal.module.scss create mode 100644 src/components/modals/deleteAccount/DeleteAccountModal.tsx create mode 100644 src/util/getClosestEntry.ts diff --git a/src/api/gramjs/methods/account.ts b/src/api/gramjs/methods/account.ts index 140cb6c7c..19584bbf8 100644 --- a/src/api/gramjs/methods/account.ts +++ b/src/api/gramjs/methods/account.ts @@ -99,3 +99,29 @@ export function toggleSponsoredMessages({ shouldReturnTrue: true, }); } + +export function buildApiAccountDays(ttl: GramJs.AccountDaysTTL): { days: number } { + return { + days: ttl.days, + }; +} + +export function buildApiAccountDaysTTL(days: number): GramJs.AccountDaysTTL { + return new GramJs.AccountDaysTTL({ + days, + }); +} + +export async function fetchAccountTTL() { + const result = await invokeRequest(new GramJs.account.GetAccountTTL()); + if (!result) return undefined; + return buildApiAccountDays(result); +} + +export function setAccountTTL({ days }: { days: number }) { + return invokeRequest(new GramJs.account.SetAccountTTL({ + ttl: buildApiAccountDaysTTL(days), + }), { + shouldReturnTrue: true, + }); +} diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings index 0c34e2f00..1eb8ee89b 100644 --- a/src/assets/localization/fallback.strings +++ b/src/assets/localization/fallback.strings @@ -1552,6 +1552,10 @@ "CloseMiniApps" = "Close Mini Apps"; "DoNotAskAgain" = "Don't ask again"; "PaymentInfoDone" = "Proceed to checkout"; +"DeleteMyAccount" = "Delete my account"; +"DeleteAccountIfAwayFor" = "If away for"; +"SelfDestructTitle" = "Account self-destruction"; +"SelfDestructSessionsDescription" = "If you don't come online from a specific session at least once within this period, it will be terminated."; "EmojiStatusAccessText" = "**{name}** requests access to set your **emoji status**. You will be able to revoke this access in the profile page of **{name}**."; "VideoConversionTitle" = "Improving Video..."; "VideoConversionText" = "The video will be published after it's optimized for the best viewing experience."; diff --git a/src/bundles/extra.ts b/src/bundles/extra.ts index 7abed7263..3d0f9b893 100644 --- a/src/bundles/extra.ts +++ b/src/bundles/extra.ts @@ -23,6 +23,7 @@ export { default as StatusPickerMenu } from '../components/left/main/StatusPicke export { default as SuggestedStatusModal } from '../components/modals/suggestedStatus/SuggestedStatusModal'; export { default as BoostModal } from '../components/modals/boost/BoostModal'; export { default as GiftCodeModal } from '../components/modals/giftcode/GiftCodeModal'; +export { default as DeleteAccountModal } from '../components/modals/deleteAccount/DeleteAccountModal'; export { default as ChatlistModal } from '../components/modals/chatlist/ChatlistModal'; export { default as ChatInviteModal } from '../components/modals/chatInvite/ChatInviteModal'; diff --git a/src/components/left/settings/SettingsPrivacy.tsx b/src/components/left/settings/SettingsPrivacy.tsx index e8e00ae0a..96d6c4045 100644 --- a/src/components/left/settings/SettingsPrivacy.tsx +++ b/src/components/left/settings/SettingsPrivacy.tsx @@ -1,4 +1,5 @@ import type { FC } from '../../../lib/teact/teact'; +import { useMemo } from '../../../lib/teact/teact'; import React, { memo, useCallback, useEffect } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; @@ -6,14 +7,17 @@ import type { ApiPrivacySettings } from '../../../api/types'; import type { GlobalState } from '../../../global/types'; import { SettingsScreens } from '../../../types'; +import { ACCOUNT_TTL_OPTIONS } from '../../../config.ts'; import { selectCanSetPasscode, selectIsCurrentUserFrozen, selectIsCurrentUserPremium, } from '../../../global/selectors'; import { selectSharedSettings } from '../../../global/selectors/sharedState'; +import { getClosestEntry } from '../../../util/getClosestEntry.ts'; import useHistoryBack from '../../../hooks/useHistoryBack'; import useLang from '../../../hooks/useLang'; +import useLastCallback from '../../../hooks/useLastCallback.ts'; import useOldLang from '../../../hooks/useOldLang'; import StarIcon from '../../common/icons/StarIcon'; @@ -41,8 +45,11 @@ type StateProps = { canDisplayChatInTitle?: boolean; isCurrentUserFrozen?: boolean; privacy: GlobalState['settings']['privacy']; + accountDaysTtl?: number; }; +const DAYS_PER_MONTH = 30; + const SettingsPrivacy: FC = ({ isActive, isCurrentUserPremium, @@ -61,8 +68,10 @@ const SettingsPrivacy: FC = ({ privacy, onReset, isCurrentUserFrozen, + accountDaysTtl, }) => { const { + openDeleteAccountModal, loadPrivacySettings, loadBlockedUsers, loadContentSettings, @@ -72,6 +81,7 @@ const SettingsPrivacy: FC = ({ loadWebAuthorizations, setSharedSettingOption, openSettingsScreen, + loadAccountDaysTtl, } = getActions(); useEffect(() => { @@ -86,6 +96,7 @@ const SettingsPrivacy: FC = ({ useEffect(() => { if (isActive && !isCurrentUserFrozen) { loadGlobalPrivacySettings(); + loadAccountDaysTtl(); } }, [isActive, isCurrentUserFrozen, loadGlobalPrivacySettings]); @@ -113,6 +124,16 @@ const SettingsPrivacy: FC = ({ updateContentSettings({ isSensitiveEnabled: isChecked }); }, [updateContentSettings]); + const handleOpenDeleteAccountModal = useLastCallback(() => { + if (!accountDaysTtl) return; + openDeleteAccountModal({ days: accountDaysTtl }); + }); + + const dayOption = useMemo(() => { + if (!accountDaysTtl) return undefined; + return getClosestEntry(ACCOUNT_TTL_OPTIONS, accountDaysTtl / DAYS_PER_MONTH).toString(); + }, [accountDaysTtl]); + function getVisibilityValue(setting?: ApiPrivacySettings) { if (!setting) return oldLang('Loading'); @@ -402,6 +423,21 @@ const SettingsPrivacy: FC = ({ onCheck={handleChatInTitleChange} /> + +
+

+ {lang('DeleteMyAccount')} +

+ + {lang('DeleteAccountIfAwayFor')} + + {lang('Months', { count: dayOption }, { pluralValue: 1 })} + + +
); }; @@ -415,6 +451,7 @@ export default memo(withGlobal( shouldNewNonContactPeersRequirePremium, nonContactPeersPaidStars, }, privacy, + accountDaysTtl, }, blocked, passcode: { @@ -443,6 +480,7 @@ export default memo(withGlobal( canDisplayChatInTitle, canSetPasscode: selectCanSetPasscode(global), isCurrentUserFrozen, + accountDaysTtl, }; }, )(SettingsPrivacy)); diff --git a/src/components/modals/ModalContainer.tsx b/src/components/modals/ModalContainer.tsx index 4f5ae9a60..a1ed5f826 100644 --- a/src/components/modals/ModalContainer.tsx +++ b/src/components/modals/ModalContainer.tsx @@ -14,6 +14,7 @@ import BoostModal from './boost/BoostModal.async'; import ChatInviteModal from './chatInvite/ChatInviteModal.async'; import ChatlistModal from './chatlist/ChatlistModal.async'; import CollectibleInfoModal from './collectible/CollectibleInfoModal.async'; +import DeleteAccountModal from './deleteAccount/DeleteAccountModal.async'; import EmojiStatusAccessModal from './emojiStatusAccess/EmojiStatusAccessModal.async'; import FrozenAccountModal from './frozenAccount/FrozenAccountModal.async'; import PremiumGiftModal from './gift/GiftModal.async'; @@ -82,7 +83,8 @@ type ModalKey = keyof Pick; type StateProps = { @@ -135,6 +137,7 @@ const MODALS: ModalRegistry = { giftTransferModal: GiftTransferModal, chatRefundModal: ChatRefundModal, isFrozenAccountModalOpen: FrozenAccountModal, + deleteAccountModal: DeleteAccountModal, }; const MODAL_KEYS = Object.keys(MODALS) as ModalKey[]; const MODAL_ENTRIES = Object.entries(MODALS) as Entries; diff --git a/src/components/modals/deleteAccount/DeleteAccountModal.async.tsx b/src/components/modals/deleteAccount/DeleteAccountModal.async.tsx new file mode 100644 index 000000000..8f93a4fd6 --- /dev/null +++ b/src/components/modals/deleteAccount/DeleteAccountModal.async.tsx @@ -0,0 +1,17 @@ +import type { FC } from '../../../lib/teact/teact'; +import React from '../../../lib/teact/teact'; + +import type { OwnProps } from './DeleteAccountModal'; + +import { Bundles } from '../../../util/moduleLoader'; + +import useModuleLoader from '../../../hooks/useModuleLoader'; + +const DeleteAccountModalAsync: FC = (props) => { + const { modal } = props; + const DeleteAccountModal = useModuleLoader(Bundles.Extra, 'DeleteAccountModal', !modal); + + return DeleteAccountModal ? : undefined; +}; + +export default DeleteAccountModalAsync; diff --git a/src/components/modals/deleteAccount/DeleteAccountModal.module.scss b/src/components/modals/deleteAccount/DeleteAccountModal.module.scss new file mode 100644 index 000000000..ef4323f3f --- /dev/null +++ b/src/components/modals/deleteAccount/DeleteAccountModal.module.scss @@ -0,0 +1,3 @@ +.root :global(.modal-dialog) { + max-width: 23.25rem; +} diff --git a/src/components/modals/deleteAccount/DeleteAccountModal.tsx b/src/components/modals/deleteAccount/DeleteAccountModal.tsx new file mode 100644 index 000000000..4debc8e85 --- /dev/null +++ b/src/components/modals/deleteAccount/DeleteAccountModal.tsx @@ -0,0 +1,117 @@ +import React, { memo, useEffect, useMemo, useState } from '../../../lib/teact/teact'; +import { getActions, withGlobal } from '../../../global'; + +import type { TabState } from '../../../global/types'; + +import { ACCOUNT_TTL_OPTIONS } from '../../../config.ts'; +import { getClosestEntry } from '../../../util/getClosestEntry.ts'; + +import useLang from '../../../hooks/useLang'; +import useLastCallback from '../../../hooks/useLastCallback'; + +import Button from '../../ui/Button'; +import Modal from '../../ui/Modal'; +import RadioGroup from '../../ui/RadioGroup.tsx'; + +import styles from './DeleteAccountModal.module.scss'; + +export type OwnProps = { + modal: TabState['deleteAccountModal']; +}; + +export type StateProps = { + selfDestructAccountDays?: number; +}; + +const DAYS_PER_MONTH = 30; + +const DeleteAccountModal = ({ + modal, + selfDestructAccountDays, +}: OwnProps & StateProps) => { + const { + closeGiftCodeModal, closeDeleteAccountModal, setAccountTTL, + } = getActions(); + const lang = useLang(); + const isOpen = Boolean(modal); + + const [selectedOption, setSelectedOption] = useState(); + + const optionToDays = useLastCallback((value: string): number => { + return Number(value) * DAYS_PER_MONTH; + }); + + const initialSelectedOption = useMemo(() => { + if (!selfDestructAccountDays) return undefined; + return getClosestEntry(ACCOUNT_TTL_OPTIONS, selfDestructAccountDays / DAYS_PER_MONTH).toString(); + }, [selfDestructAccountDays]); + + useEffect(() => { + if (initialSelectedOption) { + setSelectedOption(initialSelectedOption); + } + }, [initialSelectedOption]); + + const options: { value: string; label: string }[] = useMemo(() => { + return ACCOUNT_TTL_OPTIONS.map((months) => ({ + value: String(months), + label: lang('Months', { count: months }, { pluralValue: 1 }), + })); + }, [lang]); + + const handleChange = useLastCallback((value: string) => { + setSelectedOption(value); + }); + + const confirmHandler = useLastCallback(() => { + if (!selectedOption) return; + setAccountTTL({ days: optionToDays(selectedOption) }); + }); + + const onCloseHandler = useLastCallback(() => { + closeDeleteAccountModal(); + }); + + return ( + +

{lang('SelfDestructSessionsDescription')}

+ +
+ + +
+
+ ); +}; + +export default memo(withGlobal( + (global, { modal }): StateProps => { + const { selfDestructAccountDays } = modal || {}; + + return { + selfDestructAccountDays, + }; + }, +)(DeleteAccountModal)); diff --git a/src/config.ts b/src/config.ts index fa78bc607..f5b942e95 100644 --- a/src/config.ts +++ b/src/config.ts @@ -471,3 +471,5 @@ export const DEFAULT_GIFT_PROFILE_FILTER_OPTIONS: GiftProfileFilterOptions = { export const DEFAULT_RESALE_GIFTS_FILTER_OPTIONS: ResaleGiftsFilterOptions = { sortType: 'byDate', }; + +export const ACCOUNT_TTL_OPTIONS = [1, 3, 6, 12, 18, 24]; diff --git a/src/global/actions/api/accounts.ts b/src/global/actions/api/accounts.ts index 34e526833..ee316b346 100644 --- a/src/global/actions/api/accounts.ts +++ b/src/global/actions/api/accounts.ts @@ -243,3 +243,37 @@ addActionHandler('terminateAllWebAuthorizations', async (global): Promise }; setGlobal(global); }); + +addActionHandler('loadAccountDaysTtl', async (global, actions, payload): Promise => { + const result = await callApi('fetchAccountTTL'); + if (!result) return; + + global = getGlobal(); + global = { + ...global, + settings: { + ...global.settings, + accountDaysTtl: result.days, + }, + }; + setGlobal(global); +}); + +addActionHandler('setAccountTTL', async (global, actions, payload): Promise => { + const { days, tabId = getCurrentTabId() } = payload || {}; + if (!days) return; + + const result = await callApi('setAccountTTL', { days }); + if (!result) return; + + global = getGlobal(); + global = { + ...global, + settings: { + ...global.settings, + accountDaysTtl: days, + }, + }; + setGlobal(global); + actions.closeDeleteAccountModal({ tabId }); +}); diff --git a/src/global/actions/ui/account.ts b/src/global/actions/ui/account.ts index 0760bcce3..5d6ac9072 100644 --- a/src/global/actions/ui/account.ts +++ b/src/global/actions/ui/account.ts @@ -1,8 +1,9 @@ import type { ActionReturnType } from '../../types'; import { getCurrentTabId } from '../../../util/establishMultitabRole'; -import { addActionHandler } from '../..'; +import { addActionHandler, setGlobal } from '../..'; import { updateTabState } from '../../reducers/tabs'; +import { selectTabState } from '../../selectors'; addActionHandler('openFrozenAccountModal', (global, actions, payload): ActionReturnType => { const { tabId = getCurrentTabId() } = payload || {}; @@ -19,3 +20,24 @@ addActionHandler('closeFrozenAccountModal', (global, actions, payload): ActionRe isFrozenAccountModalOpen: false, }, tabId); }); + +addActionHandler('openDeleteAccountModal', (global, actions, payload): ActionReturnType => { + const { days, tabId = getCurrentTabId() } = payload || {}; + if (!days) return; + + global = updateTabState(global, { + ...selectTabState(global, tabId), + deleteAccountModal: { + selfDestructAccountDays: days, + }, + }, tabId); + setGlobal(global); +}); + +addActionHandler('closeDeleteAccountModal', (global, actions, payload): ActionReturnType => { + const { tabId = getCurrentTabId() } = payload || {}; + + return updateTabState(global, { + deleteAccountModal: undefined, + }, tabId); +}); diff --git a/src/global/cache.ts b/src/global/cache.ts index 5a1fd9d66..a80df2b02 100644 --- a/src/global/cache.ts +++ b/src/global/cache.ts @@ -281,6 +281,10 @@ function unsafeMigrateCache(cached: GlobalState, initialState: GlobalState) { cached.peers = initialState.peers; } + if (!cached.settings.accountDaysTtl) { + cached.settings.accountDaysTtl = initialState.settings.accountDaysTtl; + } + if (!cached.cacheVersion) { cached.cacheVersion = initialState.cacheVersion; // Reset because of the new action message structure @@ -708,7 +712,7 @@ function omitLocalMedia(message: ApiMessage): ApiMessage { function reduceSettings(global: T): GlobalState['settings'] { const { - byKey, botVerificationShownPeerIds, notifyDefaults, lastPremiumBandwithNotificationDate, themes, + byKey, botVerificationShownPeerIds, notifyDefaults, lastPremiumBandwithNotificationDate, themes, accountDaysTtl, } = global.settings; return { @@ -718,6 +722,7 @@ function reduceSettings(global: T): GlobalState['settings lastPremiumBandwithNotificationDate, notifyDefaults, themes, + accountDaysTtl, }; } diff --git a/src/global/initialState.ts b/src/global/initialState.ts index 19e5045d1..24aa051dc 100644 --- a/src/global/initialState.ts +++ b/src/global/initialState.ts @@ -309,6 +309,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = { patternColor: DARK_THEME_PATTERN_COLOR, }, }, + accountDaysTtl: 365, }, serviceNotifications: [], diff --git a/src/global/types/actions.ts b/src/global/types/actions.ts index b7d40aa4c..9868af4f6 100644 --- a/src/global/types/actions.ts +++ b/src/global/types/actions.ts @@ -1040,6 +1040,14 @@ export interface ActionPayloads { }; openFrozenAccountModal: WithTabId | undefined; closeFrozenAccountModal: WithTabId | undefined; + openDeleteAccountModal: { + days: number; + } & WithTabId | undefined; + closeDeleteAccountModal: WithTabId | undefined; + setAccountTTL: { + days: number; + } & WithTabId | undefined; + loadAccountDaysTtl: undefined; // Chats loadPeerSettings: { diff --git a/src/global/types/globalState.ts b/src/global/types/globalState.ts index 6facd245c..c2a26cc26 100644 --- a/src/global/types/globalState.ts +++ b/src/global/types/globalState.ts @@ -415,6 +415,7 @@ export type GlobalState = { paidReactionPrivacy?: ApiPaidReactionPrivacyType; botVerificationShownPeerIds: string[]; themes: Partial>; + accountDaysTtl: number; }; push?: { diff --git a/src/global/types/tabState.ts b/src/global/types/tabState.ts index 2b6908d1b..79d3dd6bc 100644 --- a/src/global/types/tabState.ts +++ b/src/global/types/tabState.ts @@ -741,6 +741,10 @@ export type TabState = { info: ApiCheckedGiftCode; }; + deleteAccountModal?: { + selfDestructAccountDays: number; + }; + paidReactionModal?: { chatId: string; messageId: number; diff --git a/src/lib/gramjs/tl/api.d.ts b/src/lib/gramjs/tl/api.d.ts index 397150f09..4dc9d9157 100644 --- a/src/lib/gramjs/tl/api.d.ts +++ b/src/lib/gramjs/tl/api.d.ts @@ -3212,9 +3212,10 @@ namespace Api { resaleStars?: long; canTransferAt?: int; canResellAt?: int; - CONSTRUCTOR_ID: 2900347777; + CONSTRUCTOR_ID: 775611918; SUBCLASS_OF_ID: 2256589094; className: 'MessageActionStarGiftUnique'; + static fromReader(reader: Reader): MessageActionStarGiftUnique; } export class MessageActionPaidMessagesRefunded extends VirtualClass<{ @@ -3255,6 +3256,10 @@ namespace Api { callId: long; duration?: int; otherParticipants?: Api.TypePeer[]; + CONSTRUCTOR_ID: 805187450; + SUBCLASS_OF_ID: 2256589094; + className: 'MessageActionConferenceCall'; + static fromReader(reader: Reader): MessageActionConferenceCall; } export class Dialog extends VirtualClass<{ @@ -5951,6 +5956,10 @@ namespace Api { subChainId: int; blocks: bytes[]; nextOffset: int; + CONSTRUCTOR_ID: 2759272591; + SUBCLASS_OF_ID: 2676568142; + className: 'UpdateGroupCallChainBlocks'; + static fromReader(reader: Reader): UpdateGroupCallChainBlocks; } export class UpdatesTooLong extends VirtualClass { @@ -9904,6 +9913,10 @@ namespace Api { slug: string; }> { slug: string; + CONSTRUCTOR_ID: 2679894519; + SUBCLASS_OF_ID: 3634081085; + className: 'PhoneCallDiscardReasonMigrateConferenceCall'; + static fromReader(reader: Reader): PhoneCallDiscardReasonMigrateConferenceCall; } export class DataJSON extends VirtualClass<{ @@ -10250,8 +10263,7 @@ namespace Api { participantId: long; protocol: Api.TypePhoneCallProtocol; receiveDate?: int; - conferenceCall?: Api.TypeInputGroupCall; - CONSTRUCTOR_ID: 4006881368; + CONSTRUCTOR_ID: 3307368215; SUBCLASS_OF_ID: 3296664529; className: 'PhoneCallWaiting'; @@ -10277,8 +10289,7 @@ namespace Api { participantId: long; gAHash: bytes; protocol: Api.TypePhoneCallProtocol; - conferenceCall?: Api.TypeInputGroupCall; - CONSTRUCTOR_ID: 1161174115; + CONSTRUCTOR_ID: 347139340; SUBCLASS_OF_ID: 3296664529; className: 'PhoneCallRequested'; @@ -10304,8 +10315,7 @@ namespace Api { participantId: long; gB: bytes; protocol: Api.TypePhoneCallProtocol; - conferenceCall?: Api.TypeInputGroupCall; - CONSTRUCTOR_ID: 587035009; + CONSTRUCTOR_ID: 912311057; SUBCLASS_OF_ID: 3296664529; className: 'PhoneCallAccepted'; @@ -10343,8 +10353,7 @@ namespace Api { connections: Api.TypePhoneConnection[]; startDate: int; customParameters?: Api.TypeDataJSON; - conferenceCall?: Api.TypeInputGroupCall; - CONSTRUCTOR_ID: 1000707084; + CONSTRUCTOR_ID: 810769141; SUBCLASS_OF_ID: 3296664529; className: 'PhoneCall'; @@ -10366,8 +10375,7 @@ namespace Api { id: long; reason?: Api.TypePhoneCallDiscardReason; duration?: int; - conferenceCall?: Api.TypeInputGroupCall; - CONSTRUCTOR_ID: 4191311107; + CONSTRUCTOR_ID: 1355435489; SUBCLASS_OF_ID: 3296664529; className: 'PhoneCallDiscarded'; @@ -11111,6 +11119,10 @@ namespace Api { newValue: Bool; }> { newValue: Bool; + CONSTRUCTOR_ID: 3306682238; + SUBCLASS_OF_ID: 2998503411; + className: 'ChannelAdminLogEventActionToggleAutotranslation'; + static fromReader(reader: Reader): ChannelAdminLogEventActionToggleAutotranslation; } export class ChannelAdminLogEvent extends VirtualClass<{ @@ -13281,11 +13293,11 @@ namespace Api { unmutedVideoCount?: int; unmutedVideoLimit: int; version: int; - conferenceFromCall?: long; inviteLink?: string; - CONSTRUCTOR_ID: 3455636451; + CONSTRUCTOR_ID: 1429932961; SUBCLASS_OF_ID: 548729632; className: 'GroupCall'; + static fromReader(reader: Reader): GroupCall; } export class InputGroupCall extends VirtualClass<{ @@ -13304,12 +13316,20 @@ namespace Api { slug: string; }> { slug: string; + CONSTRUCTOR_ID: 4261839423; + SUBCLASS_OF_ID: 1482758833; + className: 'InputGroupCallSlug'; + static fromReader(reader: Reader): InputGroupCallSlug; } export class InputGroupCallInviteMessage extends VirtualClass<{ msgId: int; }> { msgId: int; + CONSTRUCTOR_ID: 2349883455; + SUBCLASS_OF_ID: 1482758833; + className: 'InputGroupCallInviteMessage'; + static fromReader(reader: Reader): InputGroupCallInviteMessage; } export class GroupCallParticipant extends VirtualClass<{ @@ -14052,6 +14072,10 @@ namespace Api { }> { slug: string; toId: Api.TypeInputPeer; + CONSTRUCTOR_ID: 1674298252; + SUBCLASS_OF_ID: 1919851518; + className: 'InputInvoiceStarGiftResale'; + static fromReader(reader: Reader): InputInvoiceStarGiftResale; } export class InputStorePaymentPremiumSubscription extends VirtualClass<{ @@ -16601,9 +16625,10 @@ namespace Api { upgradeStars?: long; resellMinStars?: long; title?: string; - CONSTRUCTOR_ID: 46953416; + CONSTRUCTOR_ID: 3324693032; SUBCLASS_OF_ID: 3273414923; className: 'StarGift'; + static fromReader(reader: Reader): StarGift; } export class StarGiftUnique extends VirtualClass<{ @@ -16634,9 +16659,10 @@ namespace Api { availabilityTotal: int; giftAddress?: string; resellStars?: long; - CONSTRUCTOR_ID: 1549979985; + CONSTRUCTOR_ID: 1678891913; SUBCLASS_OF_ID: 3273414923; className: 'StarGiftUnique'; + static fromReader(reader: Reader): StarGiftUnique; } export class MessageReportOption extends VirtualClass<{ @@ -16838,7 +16864,7 @@ namespace Api { patternColor: int; textColor: int; rarityPermille: int; - CONSTRUCTOR_ID: 2485589858; + CONSTRUCTOR_ID: 3644687772; SUBCLASS_OF_ID: 2276819400; className: 'StarGiftAttributeBackdrop'; @@ -16900,9 +16926,10 @@ namespace Api { transferStars?: long; canTransferAt?: int; canResellAt?: int; - CONSTRUCTOR_ID: 1616305061; + CONSTRUCTOR_ID: 3755607193; SUBCLASS_OF_ID: 2385198100; className: 'SavedStarGift'; + static fromReader(reader: Reader): SavedStarGift; } export class InputSavedStarGiftUser extends VirtualClass<{ @@ -16931,6 +16958,10 @@ namespace Api { slug: string; }> { slug: string; + CONSTRUCTOR_ID: 545636920; + SUBCLASS_OF_ID: 2406848942; + className: 'InputSavedStarGiftSlug'; + static fromReader(reader: Reader): InputSavedStarGiftSlug; } export class PaidReactionPrivacyDefault extends VirtualClass { @@ -17059,18 +17090,30 @@ namespace Api { documentId: long; }> { documentId: long; + CONSTRUCTOR_ID: 1219145276; + SUBCLASS_OF_ID: 3005295287; + className: 'StarGiftAttributeIdModel'; + static fromReader(reader: Reader): StarGiftAttributeIdModel; } export class StarGiftAttributeIdPattern extends VirtualClass<{ documentId: long; }> { documentId: long; + CONSTRUCTOR_ID: 1242965043; + SUBCLASS_OF_ID: 3005295287; + className: 'StarGiftAttributeIdPattern'; + static fromReader(reader: Reader): StarGiftAttributeIdPattern; } export class StarGiftAttributeIdBackdrop extends VirtualClass<{ backdropId: int; }> { backdropId: int; + CONSTRUCTOR_ID: 520210263; + SUBCLASS_OF_ID: 3005295287; + className: 'StarGiftAttributeIdBackdrop'; + static fromReader(reader: Reader): StarGiftAttributeIdBackdrop; } export class StarGiftAttributeCounter extends VirtualClass<{ @@ -17079,6 +17122,10 @@ namespace Api { }> { attribute: Api.TypeStarGiftAttributeId; count: int; + CONSTRUCTOR_ID: 783398488; + SUBCLASS_OF_ID: 2351477395; + className: 'StarGiftAttributeCounter'; + static fromReader(reader: Reader): StarGiftAttributeCounter; } export class PendingSuggestion extends VirtualClass<{ @@ -17091,6 +17138,10 @@ namespace Api { title: Api.TypeTextWithEntities; description: Api.TypeTextWithEntities; url: string; + CONSTRUCTOR_ID: 3890753042; + SUBCLASS_OF_ID: 3126949031; + className: 'PendingSuggestion'; + static fromReader(reader: Reader): PendingSuggestion; } export class ResPQ extends VirtualClass<{ @@ -19899,9 +19950,10 @@ namespace Api { customPendingSuggestion?: Api.TypePendingSuggestion; chats: Api.TypeChat[]; users: Api.TypeUser[]; - CONSTRUCTOR_ID: 2352576831; + CONSTRUCTOR_ID: 145021050; SUBCLASS_OF_ID: 2639877442; className: 'PromoData'; + static fromReader(reader: Reader): PromoData; } export class CountryCode extends VirtualClass<{ @@ -21081,6 +21133,10 @@ namespace Api { chats: Api.TypeChat[]; counters?: Api.TypeStarGiftAttributeCounter[]; users: Api.TypeUser[]; + CONSTRUCTOR_ID: 2491028191; + SUBCLASS_OF_ID: 3000743907; + className: 'ResaleStarGifts'; + static fromReader(reader: Reader): ResaleStarGifts; } } @@ -21696,6 +21752,10 @@ namespace Api { countRemains: int; }> { countRemains: int; + CONSTRUCTOR_ID: 3280453710; + SUBCLASS_OF_ID: 3411255960; + className: 'CanSendStoryCount'; + static fromReader(reader: Reader): CanSendStoryCount; } } diff --git a/src/lib/gramjs/tl/apiTl.ts b/src/lib/gramjs/tl/apiTl.ts index 6ff916c1b..e05ab8b36 100644 --- a/src/lib/gramjs/tl/apiTl.ts +++ b/src/lib/gramjs/tl/apiTl.ts @@ -1466,6 +1466,8 @@ account.checkUsername#2714d86c username:string = Bool; account.updateUsername#3e0bdd7c username:string = User; account.getPrivacy#dadbc950 key:InputPrivacyKey = account.PrivacyRules; account.setPrivacy#c9f81ce8 key:InputPrivacyKey rules:Vector = account.PrivacyRules; +account.getAccountTTL#8fc711d = AccountDaysTTL; +account.setAccountTTL#2442485e ttl:AccountDaysTTL = Bool; account.getAuthorizations#e320c158 = account.Authorizations; account.resetAuthorization#df77f3bc hash:long = Bool; account.getPassword#548a30f5 = account.Password; diff --git a/src/lib/gramjs/tl/static/api.json b/src/lib/gramjs/tl/static/api.json index ec6145828..7e6d306de 100644 --- a/src/lib/gramjs/tl/static/api.json +++ b/src/lib/gramjs/tl/static/api.json @@ -63,6 +63,8 @@ "account.getCollectibleEmojiStatuses", "account.addNoPaidMessagesException", "account.getPaidMessagesRevenue", + "account.getAccountTTL", + "account.setAccountTTL", "users.getUsers", "users.getFullUser", "contacts.getContacts", diff --git a/src/types/language.d.ts b/src/types/language.d.ts index 67e147d34..1f079bdd9 100644 --- a/src/types/language.d.ts +++ b/src/types/language.d.ts @@ -1272,6 +1272,10 @@ export interface LangPair { 'CloseMiniApps': undefined; 'DoNotAskAgain': undefined; 'PaymentInfoDone': undefined; + 'DeleteMyAccount': undefined; + 'DeleteAccountIfAwayFor': undefined; + 'SelfDestructTitle': undefined; + 'SelfDestructSessionsDescription': undefined; 'VideoConversionTitle': undefined; 'VideoConversionText': undefined; 'VideoConversionDone': undefined; diff --git a/src/util/getClosestEntry.ts b/src/util/getClosestEntry.ts new file mode 100644 index 000000000..47a591c84 --- /dev/null +++ b/src/util/getClosestEntry.ts @@ -0,0 +1,5 @@ +export function getClosestEntry(arr: number[], value: number): number { + return arr.reduce((prev, curr) => { + return Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev; + }); +}