diff --git a/src/api/gramjs/apiBuilders/appConfig.ts b/src/api/gramjs/apiBuilders/appConfig.ts index 041c06158..8ad2a3c0a 100644 --- a/src/api/gramjs/apiBuilders/appConfig.ts +++ b/src/api/gramjs/apiBuilders/appConfig.ts @@ -77,6 +77,7 @@ export interface GramJsAppConfig extends LimitsConfig { group_transcribe_level_min?: number; new_noncontact_peers_require_premium_without_ownpremium?: boolean; channel_restrict_sponsored_level_min?: number; + channel_revenue_withdrawal_enabled?: boolean; // Upload premium notifications upload_premium_speedup_notify_period?: number; upload_premium_speedup_download?: number; @@ -161,5 +162,6 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp bandwidthPremiumUploadSpeedup: appConfig.upload_premium_speedup_upload, bandwidthPremiumDownloadSpeedup: appConfig.upload_premium_speedup_download, channelRestrictAdsLevelMin: appConfig.channel_restrict_sponsored_level_min, + isChannelRevenueWithdrawalEnabled: appConfig.channel_revenue_withdrawal_enabled, }; } diff --git a/src/api/gramjs/methods/client.ts b/src/api/gramjs/methods/client.ts index 5a3effbd9..a4fd2ebf7 100644 --- a/src/api/gramjs/methods/client.ts +++ b/src/api/gramjs/methods/client.ts @@ -2,7 +2,7 @@ import { Api as GramJs, sessions, } from '../../../lib/gramjs'; -import type { TwoFaParams } from '../../../lib/gramjs/client/2fa'; +import type { TwoFaParams, TwoFaPasswordParams } from '../../../lib/gramjs/client/2fa'; import TelegramClient from '../../../lib/gramjs/client/TelegramClient'; import { Logger as GramJsLogger } from '../../../lib/gramjs/extensions/index'; @@ -369,6 +369,10 @@ export function getTmpPassword(currentPassword: string, ttl?: number) { return client.getTmpPassword(currentPassword, ttl); } +export function getCurrentPassword(params: TwoFaPasswordParams) { + return client.getCurrentPassword(params); +} + export function abortChatRequests(params: { chatId: string; threadId?: ThreadId }) { const { chatId, threadId } = params; const controller = CHAT_ABORT_CONTROLLERS.get(chatId); diff --git a/src/api/gramjs/methods/statistics.ts b/src/api/gramjs/methods/statistics.ts index 610db9cb1..e695142b7 100644 --- a/src/api/gramjs/methods/statistics.ts +++ b/src/api/gramjs/methods/statistics.ts @@ -17,6 +17,7 @@ import { } from '../apiBuilders/statistics'; import { buildInputEntity, buildInputPeer } from '../gramjsBuilders'; import { invokeRequest } from './client'; +import { getPassword, onPasswordError } from './twoFaSettings'; export async function fetchChannelStatistics({ chat, dcId, @@ -211,3 +212,32 @@ export async function fetchStoryPublicForwards({ nextOffset: result.nextOffset, }; } + +export async function loadMonetizationRevenueWithdrawalUrl({ + chat, currentPassword, +}: { chat: ApiChat; currentPassword: string }) { + try { + const password = await getPassword(currentPassword); + + if (!password || 'error' in password) { + return undefined; + } + + const result = await invokeRequest(new GramJs.stats.GetBroadcastRevenueWithdrawalUrl({ + channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel, + password, + }), { + shouldThrow: true, + }); + + if (!result) { + return undefined; + } + + return result; + } catch (err: any) { + onPasswordError(err); + } + + return undefined; +} diff --git a/src/api/gramjs/methods/twoFaSettings.ts b/src/api/gramjs/methods/twoFaSettings.ts index f4a67aa1f..3a6be255e 100644 --- a/src/api/gramjs/methods/twoFaSettings.ts +++ b/src/api/gramjs/methods/twoFaSettings.ts @@ -2,7 +2,9 @@ import { Api as GramJs, errors } from '../../../lib/gramjs'; import { DEBUG } from '../../../config'; import { sendApiUpdate } from '../updates/apiUpdateEmitter'; -import { getTmpPassword, invokeRequest, updateTwoFaSettings } from './client'; +import { + getCurrentPassword, getTmpPassword, invokeRequest, updateTwoFaSettings, +} from './client'; const ApiErrors: { [k: string]: string } = { EMAIL_UNCONFIRMED: 'Email unconfirmed', @@ -11,6 +13,7 @@ const ApiErrors: { [k: string]: string } = { NEW_SETTINGS_INVALID: 'The new password settings are invalid', CODE_INVALID: 'Invalid Code', PASSWORD_HASH_INVALID: 'Invalid Password', + PASSWORD_MISSING: 'You must enable 2FA before executing this operation', }; const emailCodeController: { @@ -45,6 +48,18 @@ export function getTemporaryPaymentPassword(password: string, ttl?: number) { return getTmpPassword(password, ttl); } +export function getPassword(password: string) { + try { + return getCurrentPassword({ + currentPassword: password, + onPasswordCodeError: onPasswordError, + }); + } catch (err: any) { + onPasswordError(err); + return undefined; + } +} + export async function checkPassword(currentPassword: string) { try { await updateTwoFaSettings({ isCheckPassword: true, currentPassword }); @@ -134,3 +149,28 @@ function onError(err: Error) { message, }); } + +export function onPasswordError(err: Error) { + let message: string; + + if (err instanceof errors.PasswordModifiedError) { + const hours = Math.ceil(Number(err.seconds) / 60 / 60); + message = `Too many attempts. Try again in ${hours > 1 ? `${hours} hours` : 'an hour'}`; + } else { + message = ApiErrors[err.message]; + } + + if (!message) { + message = 'Unexpected Error'; + + if (DEBUG) { + // eslint-disable-next-line no-console + console.error(err); + } + } + + sendApiUpdate({ + '@type': 'updatePasswordError', + error: message, + }); +} diff --git a/src/api/types/misc.ts b/src/api/types/misc.ts index d53e0c3b8..507aa6a15 100644 --- a/src/api/types/misc.ts +++ b/src/api/types/misc.ts @@ -210,6 +210,7 @@ export interface ApiAppConfig { bandwidthPremiumUploadSpeedup?: number; bandwidthPremiumDownloadSpeedup?: number; channelRestrictAdsLevelMin?: number; + isChannelRevenueWithdrawalEnabled?: boolean; } export interface ApiConfig { diff --git a/src/api/types/updates.ts b/src/api/types/updates.ts index 51bd25f96..902f54913 100644 --- a/src/api/types/updates.ts +++ b/src/api/types/updates.ts @@ -481,6 +481,11 @@ export type ApiUpdateTwoFaError = { message: string; }; +export type ApiUpdatePasswordError = { + '@type': 'updatePasswordError'; + error: string; +}; + export type ApiUpdateNotifySettings = { '@type': 'updateNotifySettings'; peerType: 'contact' | 'group' | 'broadcast'; @@ -776,7 +781,7 @@ export type ApiUpdate = ( ApiUpdateRecentStickers | ApiUpdateSavedGifs | ApiUpdateNewScheduledMessage | ApiUpdateMoveStickerSetToTop | ApiUpdateScheduledMessageSendSucceeded | ApiUpdateScheduledMessage | ApiUpdateDeleteScheduledMessages | ApiUpdateResetMessages | ApiUpdateMessageTranslations | - ApiUpdateTwoFaError | ApiUpdateTwoFaStateWaitCode | ApiUpdateWebViewResultSent | + ApiUpdateTwoFaError | ApiUpdatePasswordError | ApiUpdateTwoFaStateWaitCode | ApiUpdateWebViewResultSent | ApiUpdateNotifySettings | ApiUpdateNotifyExceptions | ApiUpdatePeerBlocked | ApiUpdatePrivacy | ApiUpdateServerTimeOffset | ApiUpdateShowInvite | ApiUpdateMessageReactions | ApiUpdateSavedReactionTags | ApiUpdateGroupCallParticipants | ApiUpdateGroupCallConnection | ApiUpdateGroupCall | ApiUpdateGroupCallStreams | diff --git a/src/bundles/extra.ts b/src/bundles/extra.ts index 1c9b1b414..3e33b44ea 100644 --- a/src/bundles/extra.ts +++ b/src/bundles/extra.ts @@ -32,6 +32,7 @@ export { default as StarsTransactionInfoModal } from '../components/modals/stars export { default as AboutAdsModal } from '../components/common/AboutAdsModal'; export { default as AboutMonetizationModal } from '../components/common/AboutMonetizationModal'; +export { default as VerificationMonetizationModal } from '../components/common/VerificationMonetizationModal'; export { default as ReportAdModal } from '../components/modals/reportAd/ReportAdModal'; export { default as CalendarModal } from '../components/common/CalendarModal'; export { default as DeleteMessageModal } from '../components/common/DeleteMessageModal'; diff --git a/src/components/common/VerificationMonetizationModal.async.tsx b/src/components/common/VerificationMonetizationModal.async.tsx new file mode 100644 index 000000000..e037427c2 --- /dev/null +++ b/src/components/common/VerificationMonetizationModal.async.tsx @@ -0,0 +1,18 @@ +import type { FC } from '../../lib/teact/teact'; +import React from '../../lib/teact/teact'; + +import type { OwnProps } from './VerificationMonetizationModal'; + +import { Bundles } from '../../util/moduleLoader'; + +import useModuleLoader from '../../hooks/useModuleLoader'; + +const VerificationMonetizationModalAsync: FC = (props) => { + const { isOpen } = props; + const VerificationMonetizationModal = useModuleLoader(Bundles.Extra, 'VerificationMonetizationModal', !isOpen); + + // eslint-disable-next-line react/jsx-props-no-spreading + return VerificationMonetizationModal ? : undefined; +}; + +export default VerificationMonetizationModalAsync; diff --git a/src/components/common/VerificationMonetizationModal.module.scss b/src/components/common/VerificationMonetizationModal.module.scss new file mode 100644 index 000000000..fca4c6641 --- /dev/null +++ b/src/components/common/VerificationMonetizationModal.module.scss @@ -0,0 +1,9 @@ +.root { + :global(.modal-dialog) { + max-width: 25rem; + } +} + +.content { + padding-top: 0.5rem; +} diff --git a/src/components/common/VerificationMonetizationModal.tsx b/src/components/common/VerificationMonetizationModal.tsx new file mode 100644 index 000000000..78e2f0790 --- /dev/null +++ b/src/components/common/VerificationMonetizationModal.tsx @@ -0,0 +1,87 @@ +import type { FC } from '../../lib/teact/teact'; +import React, { + memo, + useState, +} from '../../lib/teact/teact'; +import { getActions } from '../../global'; + +import buildClassName from '../../util/buildClassName'; + +import useLastCallback from '../../hooks/useLastCallback'; +import useOldLang from '../../hooks/useOldLang'; + +import Modal from '../ui/Modal'; +import PasswordForm from './PasswordForm'; + +import styles from './VerificationMonetizationModal.module.scss'; + +export type OwnProps = { + isOpen: boolean; + onClose: NoneToVoidFunction; + chatId: string; + passwordHint?: string; + error?: string; + isLoading?: boolean; +}; + +const VerificationMonetizationModal: FC = ({ + isOpen, + chatId, + onClose, + passwordHint, + error, + isLoading, +}) => { + const { + clearMonetizationInfo, loadMonetizationRevenueWithdrawalUrl, + } = getActions(); + + const lang = useOldLang(); + + const [shouldShowPassword, setShouldShowPassword] = useState(false); + + const handleSubmit = useLastCallback((password: string) => { + loadMonetizationRevenueWithdrawalUrl({ + chatId, + currentPassword: password, + onSuccess: () => { + onClose(); + }, + }); + }); + + const handleClearError = useLastCallback(() => { + clearMonetizationInfo(); + }); + + return ( + +
+
+ +
+
+
+ ); +}; + +export default memo(VerificationMonetizationModal); diff --git a/src/components/right/statistics/MonetizationStatistics.tsx b/src/components/right/statistics/MonetizationStatistics.tsx index 502c226b7..ded4ae75c 100644 --- a/src/components/right/statistics/MonetizationStatistics.tsx +++ b/src/components/right/statistics/MonetizationStatistics.tsx @@ -5,19 +5,22 @@ import { getActions, withGlobal } from '../../../global'; import type { ApiChannelMonetizationStatistics, StatisticsGraph } from '../../../api/types'; -import { FRAGMENT_ADS_URL } from '../../../config'; import { selectChat, selectChatFullInfo, selectTabState } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; +import renderText from '../../common/helpers/renderText'; import useFlag from '../../../hooks/useFlag'; import useForceUpdate from '../../../hooks/useForceUpdate'; import useLang from '../../../hooks/useLang'; +import useLastCallback from '../../../hooks/useLastCallback'; import useOldLang from '../../../hooks/useOldLang'; import AboutMonetizationModal from '../../common/AboutMonetizationModal.async'; import Icon from '../../common/icons/Icon'; import SafeLink from '../../common/SafeLink'; +import VerificationMonetizationModal from '../../common/VerificationMonetizationModal.async'; import Button from '../../ui/Button'; +import ConfirmDialog from '../../ui/ConfirmDialog'; import Link from '../../ui/Link'; import Loading from '../../ui/Loading'; import StatisticsOverview from './StatisticsOverview'; @@ -47,29 +50,46 @@ type StateProps = { chatId: string; dcId?: number; statistics?: ApiChannelMonetizationStatistics; - canCollect?: boolean; + isCreator?: boolean; + isChannelRevenueWithdrawalEnabled?: boolean; + hasPassword?: boolean; + passwordHint?: string; + error?: string; + isLoading?: boolean; }; const MonetizationStatistics = ({ chatId, dcId, statistics, - canCollect, + isCreator, + isChannelRevenueWithdrawalEnabled, + hasPassword, + passwordHint, + error, + isLoading, }: StateProps) => { - const { loadChannelMonetizationStatistics } = getActions(); + const { loadChannelMonetizationStatistics, loadPasswordInfo } = getActions(); const oldLang = useOldLang(); const lang = useLang(); + // eslint-disable-next-line no-null/no-null const containerRef = useRef(null); const [isReady, setIsReady] = useState(false); const loadedCharts = useRef([]); const forceUpdate = useForceUpdate(); const [isAboutMonetizationModalOpen, openAboutMonetizationModal, closeAboutMonetizationModal] = useFlag(false); - const hasAvailableBalance = Boolean(statistics?.balances?.availableBalance !== 0); + const [ + isVerificationMonetizationModalOpen, openVerificationMonetizationModal, closeVerificationMonetizationModal, + ] = useFlag(false); + const [isConfirmPasswordDialogOpen, openConfirmPasswordDialogOpen, closeConfirmPasswordDialogOpen] = useFlag(); + const availableBalance = statistics?.balances?.availableBalance; + const canWithdraw = isCreator && isChannelRevenueWithdrawalEnabled && Boolean(availableBalance); useEffect(() => { if (chatId) { loadChannelMonetizationStatistics({ chatId }); + loadPasswordInfo(); } }, [chatId, loadChannelMonetizationStatistics]); @@ -86,7 +106,7 @@ const MonetizationStatistics = ({ return; } - MONETIZATION_GRAPHS.forEach((name, index: number) => { + MONETIZATION_GRAPHS.filter(Boolean).forEach((name, index: number) => { const graph = statistics[name as keyof typeof statistics]; const isAsync = typeof graph === 'string'; @@ -115,7 +135,6 @@ const MonetizationStatistics = ({ }, [isReady, statistics, oldLang, chatId, dcId, forceUpdate]); function renderAvailableReward() { - const availableBalance = statistics?.balances?.availableBalance; const [integerTonPart, decimalTonPart] = availableBalance ? availableBalance.toFixed(4).split('.') : [0]; const [integerUsdPart, decimalUsdPart] = availableBalance && statistics?.usdRate ? (availableBalance * statistics.usdRate).toFixed(2).split('.') : [0]; @@ -172,6 +191,14 @@ const MonetizationStatistics = ({ ); }, [lang, oldLang]); + const verificationMonetizationHandler = useLastCallback(() => { + if (hasPassword) { + openVerificationMonetizationModal(); + } else { + openConfirmPasswordDialogOpen(); + } + }); + if (!isReady || !statistics) { return ; } @@ -190,29 +217,49 @@ const MonetizationStatistics = ({ {!loadedCharts.current.length && }
- {MONETIZATION_GRAPHS.map((graph) => ( + {MONETIZATION_GRAPHS.filter(Boolean).map((graph) => (
))}
- {hasAvailableBalance && ( -
- {oldLang('lng_channel_earn_balance_title')} +
+ {oldLang('lng_channel_earn_balance_title')} - {renderAvailableReward()} + {renderAvailableReward()} - + -
{rewardsText}
-
- )} +
{rewardsText}
+
+ + +

{renderText(oldLang('Monetization.Withdraw.Error.Text'), ['br'])}

+
); }; @@ -220,18 +267,38 @@ const MonetizationStatistics = ({ export default memo(withGlobal( (global): StateProps => { const tabState = selectTabState(global); + const { + settings: { + byKey: { + hasPassword, + }, + }, + twoFaSettings: { + hint: passwordHint, + }, + } = global; + const isLoading = global.monetizationInfo?.isLoading; + const error = global.monetizationInfo?.error; const monetizationStatistics = tabState.monetizationStatistics; const chatId = monetizationStatistics && monetizationStatistics.chatId; const chat = chatId ? selectChat(global, chatId) : undefined; - const canCollect = chat && chat.isCreator; const dcId = selectChatFullInfo(global, chatId!)?.statisticsDcId; + const isCreator = Boolean(chat?.isCreator); + const statistics = tabState.statistics.monetization; + const isChannelRevenueWithdrawalEnabled = global.appConfig?.isChannelRevenueWithdrawalEnabled; + return { chatId: chatId!, dcId, statistics, - canCollect, + isCreator, + isChannelRevenueWithdrawalEnabled, + hasPassword, + passwordHint, + error, + isLoading, }; }, )(MonetizationStatistics)); diff --git a/src/global/actions/all.ts b/src/global/actions/all.ts index d342311c8..132a9a5c2 100644 --- a/src/global/actions/all.ts +++ b/src/global/actions/all.ts @@ -38,5 +38,6 @@ import './apiUpdaters/symbols'; import './apiUpdaters/misc'; import './apiUpdaters/settings'; import './apiUpdaters/twoFaSettings'; +import './apiUpdaters/password'; import './apiUpdaters/calls'; import './apiUpdaters/payments'; diff --git a/src/global/actions/api/statistics.ts b/src/global/actions/api/statistics.ts index ac70412b9..b714586f2 100644 --- a/src/global/actions/api/statistics.ts +++ b/src/global/actions/api/statistics.ts @@ -1,3 +1,5 @@ +import type { ActionReturnType } from '../../types'; + import { areDeepEqual } from '../../../util/areDeepEqual'; import { getCurrentTabId } from '../../../util/establishMultitabRole'; import { callApi } from '../../../api/gramjs'; @@ -5,6 +7,7 @@ import { addActionHandler, getGlobal, setGlobal } from '../../index'; import { updateChannelMonetizationStatistics, updateMessageStatistics, + updateMonetizationInfo, updateStatistics, updateStatisticsGraph, updateStoryStatistics, @@ -224,3 +227,41 @@ addActionHandler('loadStoryPublicForwards', async (global, actions, payload): Pr }, tabId); setGlobal(global); }); + +addActionHandler('loadMonetizationRevenueWithdrawalUrl', async (global, actions, payload): Promise => { + const { + chatId, currentPassword, onSuccess, tabId = getCurrentTabId(), + } = payload; + + global = updateMonetizationInfo(global, { isLoading: true, error: undefined }); + setGlobal(global); + + const chat = selectChat(global, chatId); + if (!chat) { + return; + } + + const result = await callApi('loadMonetizationRevenueWithdrawalUrl', { chat, currentPassword }); + + if (!result) { + return; + } + + global = getGlobal(); + global = updateMonetizationInfo(global, { isLoading: false }); + setGlobal(global); + + if (result) { + onSuccess(); + actions.openUrl({ + url: result.url, + shouldSkipModal: true, + tabId, + ignoreDeepLinks: true, + }); + } +}); + +addActionHandler('clearMonetizationInfo', (global): ActionReturnType => { + return updateMonetizationInfo(global, { error: undefined }); +}); diff --git a/src/global/actions/apiUpdaters/password.ts b/src/global/actions/apiUpdaters/password.ts new file mode 100644 index 000000000..3601c5d73 --- /dev/null +++ b/src/global/actions/apiUpdaters/password.ts @@ -0,0 +1,20 @@ +import type { ActionReturnType } from '../../types'; + +import { addActionHandler } from '../../index'; + +addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { + switch (update['@type']) { + case 'updatePasswordError': { + return { + ...global, + monetizationInfo: { + ...global.monetizationInfo, + isLoading: false, + error: update.error, + }, + }; + } + } + + return undefined; +}); diff --git a/src/global/initialState.ts b/src/global/initialState.ts index 268cdad47..f4641f6b7 100644 --- a/src/global/initialState.ts +++ b/src/global/initialState.ts @@ -297,6 +297,8 @@ export const INITIAL_GLOBAL_STATE: GlobalState = { isMinimized: false, isHidden: false, }, + + monetizationInfo: {}, }; export const INITIAL_TAB_STATE: TabState = { diff --git a/src/global/reducers/index.ts b/src/global/reducers/index.ts index 45afe7d14..78f3f3b2f 100644 --- a/src/global/reducers/index.ts +++ b/src/global/reducers/index.ts @@ -12,4 +12,5 @@ export * from './payments'; export * from './statistics'; export * from './stories'; export * from './translations'; +export * from './password'; export * from './general'; diff --git a/src/global/reducers/password.ts b/src/global/reducers/password.ts new file mode 100644 index 000000000..9e9a24d77 --- /dev/null +++ b/src/global/reducers/password.ts @@ -0,0 +1,14 @@ +import type { GlobalState } from '../types'; + +export function updateMonetizationInfo( + global: T, + update: GlobalState['monetizationInfo'], +): T { + return { + ...global, + monetizationInfo: { + ...global.monetizationInfo, + ...update, + }, + }; +} diff --git a/src/global/types.ts b/src/global/types.ts index a49f56c4e..bdf130087 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -872,6 +872,11 @@ export type GlobalState = { waitingEmailCodeLength?: number; }; + monetizationInfo: { + isLoading?: boolean; + error?: string; + }; + attachmentSettings: { shouldCompress: boolean; shouldSendGrouped: boolean; @@ -1285,6 +1290,7 @@ export interface ActionPayloads { updatePerformanceSettings: Partial; loadPasswordInfo: undefined; clearTwoFaError: undefined; + clearMonetizationInfo: undefined; updatePassword: { currentPassword: string; password: string; @@ -1825,6 +1831,12 @@ export interface ActionPayloads { chatId: string; } & WithTabId; + loadMonetizationRevenueWithdrawalUrl: { + chatId: string; + currentPassword: string; + onSuccess: VoidFunction; + } & WithTabId; + // ui dismissDialog: WithTabId | undefined; setNewChatMembersDialogState: { diff --git a/src/lib/gramjs/client/2fa.ts b/src/lib/gramjs/client/2fa.ts index 6238b3858..64f7faefe 100644 --- a/src/lib/gramjs/client/2fa.ts +++ b/src/lib/gramjs/client/2fa.ts @@ -1,9 +1,11 @@ import type TelegramClient from './TelegramClient'; + +import errors from '../errors'; // eslint-disable-next-line import/no-named-default import { default as Api } from '../tl/api'; + import { generateRandomBytes } from '../Helpers'; import { computeCheck, computeDigest } from '../Password'; -import errors from '../errors'; export interface TwoFaParams { isCheckPassword?: boolean; @@ -15,7 +17,13 @@ export interface TwoFaParams { onEmailCodeError?: (err: Error) => void; } +export interface TwoFaPasswordParams { + currentPassword?: string; + onPasswordCodeError?: (err: Error) => void; +} + export type TmpPasswordResult = Api.account.TmpPassword | { error: string } | undefined; +export type PasswordResult = Api.account.Password | { error: string } | undefined; /** * Changes the 2FA settings of the logged in user. @@ -147,3 +155,29 @@ export async function getTmpPassword(client: TelegramClient, currentPassword: st throw err; } } + +export async function getCurrentPassword( + client: TelegramClient, + { + currentPassword, + onPasswordCodeError, + }: TwoFaPasswordParams, +) { + const pwd = await client.invoke(new Api.account.GetPassword()); + + if (!pwd) { + return undefined; + } + + try { + return currentPassword ? await computeCheck(pwd, currentPassword!) : new Api.InputCheckPasswordEmpty(); + } catch (err: any) { + if (err instanceof errors.PasswordModifiedError) { + return onPasswordCodeError!(err); + } else if (err.message === 'PASSWORD_HASH_INVALID') { + return { error: err.message }; + } else { + throw err; + } + } +} diff --git a/src/lib/gramjs/client/TelegramClient.d.ts b/src/lib/gramjs/client/TelegramClient.d.ts index fe0c1d5a6..42486cc18 100644 --- a/src/lib/gramjs/client/TelegramClient.d.ts +++ b/src/lib/gramjs/client/TelegramClient.d.ts @@ -1,4 +1,6 @@ -import type { TmpPasswordResult, TwoFaParams, updateTwoFaSettings } from './2fa'; +import type { + PasswordResult, TmpPasswordResult, TwoFaParams, TwoFaPasswordParams, updateTwoFaSettings, +} from './2fa'; import type { BotAuthParams, UserAuthParams } from './auth'; import type { downloadFile, DownloadFileParams } from './downloadFile'; import type { uploadFile, UploadFileParams } from './uploadFile'; @@ -24,6 +26,8 @@ declare class TelegramClient { async getTmpPassword(currentPassword: string, ttl?: number): Promise; + async getCurrentPassword(Params: TwoFaPasswordParams): Promise; + setPingCallback(callback: () => Promise); setForceHttpTransport: (forceHttpTransport: boolean) => void; diff --git a/src/lib/gramjs/client/TelegramClient.js b/src/lib/gramjs/client/TelegramClient.js index 97bc82d31..cd00db33b 100644 --- a/src/lib/gramjs/client/TelegramClient.js +++ b/src/lib/gramjs/client/TelegramClient.js @@ -26,6 +26,7 @@ const { uploadFile } = require('./uploadFile'); const { updateTwoFaSettings, getTmpPassword, + getCurrentPassword, } = require('./2fa'); const RequestState = require('../network/RequestState'); const Deferred = require('../../../util/Deferred').default; @@ -1118,6 +1119,10 @@ class TelegramClient { return getTmpPassword(this, currentPassword, ttl); } + getCurrentPassword(currentPassword) { + return getCurrentPassword(this, currentPassword); + } + // event region addEventHandler(callback, event) { this._eventBuilders.push([event, callback]); diff --git a/src/lib/gramjs/errors/RPCErrorList.js b/src/lib/gramjs/errors/RPCErrorList.js index fd980a07a..a96f3d43f 100644 --- a/src/lib/gramjs/errors/RPCErrorList.js +++ b/src/lib/gramjs/errors/RPCErrorList.js @@ -101,6 +101,16 @@ class EmailUnconfirmedError extends BadRequestError { } } +class PasswordModifiedError extends BadRequestError { + constructor(args) { + const seconds = Number(args.capture || 0); + super(`The password was modified less than 24 hours ago, try again in ${seconds} seconds.`); + // eslint-disable-next-line max-len + this.message = `The password was modified less than 24 hours ago, try again in ${seconds} seconds.`; + this.seconds = seconds; + } +} + const rpcErrorRe = [ [/FILE_MIGRATE_(\d+)/, FileMigrateError], [/FLOOD_TEST_PHONE_WAIT_(\d+)/, FloodTestPhoneWaitError], @@ -112,6 +122,7 @@ const rpcErrorRe = [ [/USER_MIGRATE_(\d+)/, UserMigrateError], [/NETWORK_MIGRATE_(\d+)/, NetworkMigrateError], [/EMAIL_UNCONFIRMED_(\d+)/, EmailUnconfirmedError], + [/PASSWORD_TOO_FRESH_(\d+)/, PasswordModifiedError], [/^Timeout$/, TimedOutError], ]; module.exports = { @@ -126,4 +137,5 @@ module.exports = { NetworkMigrateError, MsgWaitError, EmailUnconfirmedError, + PasswordModifiedError, }; diff --git a/src/lib/gramjs/tl/apiTl.js b/src/lib/gramjs/tl/apiTl.js index d203a224c..7cc22251c 100644 --- a/src/lib/gramjs/tl/apiTl.js +++ b/src/lib/gramjs/tl/apiTl.js @@ -1624,8 +1624,8 @@ bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:fla bots.canSendMessage#1359f4e6 bot:InputUser = Bool; bots.allowSendMessage#f132e3ef bot:InputUser = Updates; bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON; -bots.getPreviewMedias#a2a5594d bot:InputUser = Vector; bots.getPopularAppBots#c2510192 offset:string limit:int = bots.PopularAppBots; +bots.getPreviewMedias#a2a5594d bot:InputUser = Vector; payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm; payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt; payments.validateRequestedInfo#b6c8f12b flags:# save:flags.0?true invoice:InputInvoice info:PaymentRequestedInfo = payments.ValidatedRequestedInfo; @@ -1676,6 +1676,7 @@ stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel ms stats.getStoryStats#374fef40 flags:# dark:flags.0?true peer:InputPeer id:int = stats.StoryStats; stats.getStoryPublicForwards#a6437ef6 peer:InputPeer id:int offset:string limit:int = stats.PublicForwards; stats.getBroadcastRevenueStats#75dfb671 flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastRevenueStats; +stats.getBroadcastRevenueWithdrawalUrl#2a65ef73 channel:InputChannel password:InputCheckPasswordSRP = stats.BroadcastRevenueWithdrawalUrl; chatlists.exportChatlistInvite#8472478e chatlist:InputChatlist title:string peers:Vector = chatlists.ExportedChatlistInvite; chatlists.deleteExportedInvite#719c5c5e chatlist:InputChatlist slug:string = Bool; chatlists.editExportedInvite#653db63d flags:# chatlist:InputChatlist slug:string title:flags.1?string peers:flags.2?Vector = ExportedChatlistInvite; diff --git a/src/lib/gramjs/tl/static/api.json b/src/lib/gramjs/tl/static/api.json index 4076e6b2f..941bf479d 100644 --- a/src/lib/gramjs/tl/static/api.json +++ b/src/lib/gramjs/tl/static/api.json @@ -282,6 +282,7 @@ "stats.getStoryStats", "stats.getStoryPublicForwards", "stats.loadAsyncGraph", + "stats.getBroadcastRevenueWithdrawalUrl", "messages.getAttachMenuBots", "messages.getAttachMenuBot", "messages.toggleBotInAttachMenu", diff --git a/src/util/getReadableErrorText.ts b/src/util/getReadableErrorText.ts index b91dfa4d3..f327d608d 100644 --- a/src/util/getReadableErrorText.ts +++ b/src/util/getReadableErrorText.ts @@ -20,6 +20,7 @@ const READABLE_ERROR_MESSAGES: Record = { MEDIA_NEW_INVALID: 'Failed to replace new media', MESSAGE_NOT_MODIFIED: 'Message not modified. The new content is identical to the current one.', MEDIA_INVALID: 'Media invalid', + PASSWORD_HASH_INVALID: 'Incorrect password', PHOTO_EXT_INVALID: 'The extension of the photo is invalid', PHOTO_INVALID_DIMENSIONS: 'The photo dimensions are invalid', PHOTO_SAVE_FILE_INVALID: 'Internal issues, try again later',