From 1e12d4f6283dcad6e98fe358cab835a9a247f22e Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Fri, 25 Jul 2025 19:34:40 +0200 Subject: [PATCH] Layer: Support layer 206 and 207 (#6038) --- CLAUDE.md | 399 ++++++++++++++++++ src/api/gramjs/apiBuilders/appConfig.ts | 14 + src/api/gramjs/apiBuilders/gifts.ts | 6 +- src/api/gramjs/apiBuilders/messageActions.ts | 31 ++ src/api/gramjs/apiBuilders/messages.ts | 27 +- src/api/gramjs/apiBuilders/payments.ts | 34 +- src/api/gramjs/apiBuilders/statistics.ts | 8 +- src/api/gramjs/gramjsBuilders/index.ts | 12 + src/api/gramjs/methods/chats.ts | 2 + src/api/gramjs/methods/messages.ts | 81 +++- src/api/gramjs/methods/stars.ts | 42 +- src/api/gramjs/methods/statistics.ts | 4 +- src/api/gramjs/updates/mtpUpdateHandler.ts | 7 +- src/api/types/chats.ts | 3 +- src/api/types/messageActions.ts | 24 +- src/api/types/messages.ts | 25 +- src/api/types/misc.ts | 7 + src/api/types/stars.ts | 2 + src/assets/localization/fallback.strings | 67 ++- src/bundles/extra.ts | 3 + src/components/common/CalendarModal.scss | 9 + src/components/common/CalendarModal.tsx | 16 +- src/components/common/Composer.tsx | 36 +- .../common/embedded/EmbeddedMessage.scss | 5 + .../common/embedded/EmbeddedMessage.tsx | 43 ++ src/components/middle/MessageListContent.tsx | 59 ++- .../composer/ComposerEmbeddedMessage.tsx | 23 +- .../middle/composer/hooks/useDraft.ts | 17 +- .../middle/helpers/groupMessages.ts | 1 + .../middle/message/ActionMessage.module.scss | 81 ++++ .../middle/message/ActionMessage.tsx | 61 ++- .../middle/message/ActionMessageText.tsx | 99 ++++- .../middle/message/InlineButtons.scss | 4 + .../middle/message/InlineButtons.tsx | 15 +- src/components/middle/message/Message.tsx | 136 +++++- .../middle/message/_message-content.scss | 4 + .../message/actions/SuggestedPostApproval.tsx | 148 +++++++ .../actions/SuggestedPostBalanceTooLow.tsx | 86 ++++ .../message/actions/SuggestedPostRejected.tsx | 74 ++++ src/components/modals/ModalContainer.tsx | 6 + .../modals/gift/UniqueGiftHeader.module.scss | 17 + .../modals/gift/UniqueGiftHeader.tsx | 28 +- .../modals/gift/info/GiftInfoModal.tsx | 39 +- .../stars/chatRefund/ChatRefundModal.tsx | 1 + .../SuggestMessageModal.async.tsx | 18 + .../SuggestMessageModal.module.scss | 55 +++ .../suggestMessage/SuggestMessageModal.tsx | 256 +++++++++++ .../SuggestedPostApprovalModal.async.tsx | 18 + .../SuggestedPostApprovalModal.module.scss | 3 + .../SuggestedPostApprovalModal.tsx | 234 ++++++++++ src/config.ts | 9 + src/global/actions/api/bots.ts | 17 + src/global/actions/api/messages.ts | 195 ++++++++- src/global/actions/api/stars.ts | 5 +- src/global/actions/ui/chats.ts | 1 + src/global/actions/ui/messages.ts | 28 ++ src/global/helpers/messages.ts | 85 ++++ src/global/types/actions.ts | 41 ++ src/global/types/tabState.ts | 10 + src/lib/gramjs/tl/AllTLObjects.ts | 3 +- src/lib/gramjs/tl/api.d.ts | 363 ++++++++-------- src/lib/gramjs/tl/apiTl.ts | 50 +-- src/lib/gramjs/tl/static/api.json | 2 + src/lib/gramjs/tl/static/api.tl | 62 ++- src/types/index.ts | 4 + src/types/language.d.ts | 162 +++++++ src/util/dates/dateFormat.ts | 19 +- 67 files changed, 3136 insertions(+), 310 deletions(-) create mode 100644 CLAUDE.md create mode 100644 src/components/middle/message/actions/SuggestedPostApproval.tsx create mode 100644 src/components/middle/message/actions/SuggestedPostBalanceTooLow.tsx create mode 100644 src/components/middle/message/actions/SuggestedPostRejected.tsx create mode 100644 src/components/modals/suggestMessage/SuggestMessageModal.async.tsx create mode 100644 src/components/modals/suggestMessage/SuggestMessageModal.module.scss create mode 100644 src/components/modals/suggestMessage/SuggestMessageModal.tsx create mode 100644 src/components/modals/suggestedPostApproval/SuggestedPostApprovalModal.async.tsx create mode 100644 src/components/modals/suggestedPostApproval/SuggestedPostApprovalModal.module.scss create mode 100644 src/components/modals/suggestedPostApproval/SuggestedPostApprovalModal.tsx diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..6669542f6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,399 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +# Instructions + +You are an expert in TypeScript, JavaScript, HTML, SCSS and Teact with deep experience in our project's simplified React-like API. You are working on a modern web app for Telegram. + +- **Be concise.** Only change code directly related to the current task; leave unrelated parts untouched. +- **Reuse** existing types, functions and components. Search before creating a new one. +- **No new libraries.** Use existing dependencies only. If a task truly can't be done without a new library, stop and explain why. +- **Do not** write tests. + +- **SCSS modules:** + - Name classes in camelCase. + - Import as `styles` in your component: + ```scss + /* Component.module.scss */ + .myWrapper { /*…*/ } + ``` + ```tsx + /* Component.tsx */ + import styles from "./Component.module.scss"; +
+ ``` + - Use [buildClassName.ts](mdc:src/util/buildClassName.ts) to merge multiple class names. + - **Always extract styles to files** - avoid inline styles unless absolutely necessary. + - **If file already imports styles**, check where they come from and add new styles there - don't create new style files. + - Use rem units for all measurements. + +- **Code Style:** + - Early returns. + - Prefix boolean variables with primary or modal auxiliaries (e.q. `isOpen`, `willUpdate`, `shouldRender`). + - Functions should start with a verb (e.q. `openModal`, `closeDialog`, `handleClick`). + - Prefer checking required parameter before calling a function, avoid making it optinal and checking at the beginning of the function. + - Only leave comments for complex logic. + - **IMPORTANT: Avoid conditional spread operators** - TypeScript doesn't check if spread fields match the target type. + ```typescript + // ❌ BAD - No type checking + { ...condition && { field: value } } + + // ✅ GOOD - Full type checking + { field: condition ? value : undefined } + ``` + +- **Localization & Text Rules:** + - **ALWAYS** use `lang()` for all text content - never hardcode strings. + - `lang()` can accept parameters: `lang('Key', { param: value })`. + - Add new translations to end of `src/assets/localization/fallback.strings`. + +- **After your solution:** + 1. Critique it—identify any shortcomings. + 2. Fix those issues, do more planning. + 3. Present the improved result. + +- **When deeper debugging is needed:** + 1. Outline clear, step-by-step debugging instructions for the operator. + 2. Remove any temporary debug code once the issue is resolved. + +- **Lint errors you can't fix manually:** + Suggest running `eslint --fix `. + +# Telegram Web API Guide + +## 1. API Definition +- The master file is `src/lib/gramjs/tl/static/api.tl` (TL syntax). +- **Don't edit** this autogenerated file. TypeScript types live in `api.d.ts`. +- We use GramJS inside a web worker; UI code uses plain objects (`Api*` types) in `src/api/types`. + +## 2. Generating Code +1. Make sure to include the method name in `api.json`. +2. Run: +```bash + npm run gramjs:tl +``` +to regenerate `api.d.ts`. +3. In `src/api/gramjs/methods/`, pick a file for your method, then: +* Name fetchers `fetch*` if the TL method starts with `get`. +* Use a destructured parameter object. +* Call the API via: + ```ts + const result = await invokeRequest( + new GramJs.namespace.MethodName({ /* params */ }) + ); + ``` +* If `result` is `undefined`, return `undefined` to signal an error. +* Convert any returned GramJS classes into plain `Api*` objects. + +Convesion from and to Api* objects is done by `apiBuilders` (function name starts with `buildApi*`) and `gramjsBuilders` (function name `buildInput*`). + +## 3. Using the API + +* In your actions, call: + + ```ts + const result = await callApi('methodName', { /* params */ }); + ``` +* Always check for `undefined` before proceeding. + +## 4. Example + +```ts +// src/api/gramjs/methods/users.ts +export async function fetchUsers({ users }: { users: ApiUser[] }) { + const result = await invokeRequest(new GramJs.users.GetUsers({ + id: users.map(({ id, accessHash }) => buildInputUser(id, accessHash)), + })); + if (!result || !result.length) { + return undefined; + } + + const apiUsers = result.map(buildApiUser).filter(Boolean); + const userStatusesById = buildApiUserStatuses(result); + + return { + users: apiUsers, + userStatusesById, + }; +} + +// src/global/actions/api/users.ts +addActionHandler('loadUser', async (global, actions, { userId }) => { + const user = selectUser(global, userId); + if (!user) return; + const res = await callApi('fetchUsers', { users: [user] }); + if (!res) return; + // update global state... +}); +``` + +## 5. Handling Updates + +* Updates come in via `mtpUpdateHandler.ts`. +* They're routed through `src/global/actions/apiUpdaters` to merge into global state. +* Types are defined in `src/api/types/updates.ts`. + +## Component Style Guide + +### 1. Basics & Imports + +* All components use JSX and render with Teact. +* **Always** import React from teact library, for JSX compatibility reasons. Only import from `'react'` when you need React **types** that are not provided in Teact. +* Built-in hooks live in `src/lib/teact/teact`. Import them from there. + +### 2. Props & Types + +* Split your props into two types: + * **OwnProps**: data passed in by the parent + * **StateProps**: data injected by `withGlobal` HOC +* Merge them as `OwnProps & StateProps` when defining your component. +* You can skip one or both if they are not used. +* **Order rule**: list any function types *last* in your props definitions. + +### 3. Hooks +* **useLastCallback** is your go-to for callbacks, since it won't trigger re-renders and always uses the latest scope. +* Only use **useCallback** when you really need to memoize a render function. +* Prefer **useFlag()** over `useState()` for simple boolean toggles. +* Check the `hooks/` folders for additional utilities. + +### 4. Component Signature +> **Migrate** any old `FC` syntax to the new form. + +```ts +// Before +const OldComp: FC = ({ … }) => { … } + +// After +const NewComp = (props: OwnProps & StateProps) => { … } +``` + +### 5. Memoization +* Wrap most components with `memo()` to avoid unnecessary updates. +* Don't pass freshly created objects or arrays as props to memoized components. +* **Exceptions** (no memo): `ListItem`, `Button`, `MenuItem`, etc. + +### 6. Localization + +* Call `const lang = useLang()` at the top of your component. +* Look up the localization guide for how to add new language keys. + +--- + +### Example + +```ts +import React, { useFlag } from '../../lib/teact/teact'; + +import useLang from '../../hooks/useLang'; +import useLastCallback from '../../hooks/useLastCallback'; + +import styles from './Component.module.scss'; + +type OwnProps = { + id: string; + className?: string; + onClick?: NoneToVoidFunction; +}; + +type StateProps = { + stateValue?: string; +}; + +// Constants first +const MAX_ITEMS = 10 + +const Component = ({ id, className, stateValue, onClick }: OwnProps & StateProps) => { + const { someAction } = getActions(); // Should always be first, if actions are used + + const ref = useRef(null); + + const [color, setColor] = useState('#FF00FF'); + const [isOpen, open, close] = useFlag(); + + const lang = useLang(); + + const handleClick = useLastCallback(() => { + if (!ref.current) return; + const el = ref.current; + setColor(el.value); + close(); + onClick?.(); + someAction(el.value); + }); + + return ( +
+ +

{stateValue}

+
+ ); +} + +export default memo(withGlobal((global, { id }): StateProps => { + + const stateValue = selectValue(global, id); + return { + stateValue, + }; + })(Component); +) +``` + +## Global State Overview + +Global State is our single, app-wide store, similar to Redux or Zustand. All its code lives under `src/global/`, with subfolders grouping related functionality (for example, `selectors/users.ts` holds all user-related selectors). + +### 1. Folder Structure + +* **`actions/`**: Actions that are used to update global from any point in the app +* **`selectors/`**: Pure functions that read data (e.g. `selectors/users.ts`). +* **`reducers/`**: Functions that update global state. +* **`types/`**: All TypeScript types live in `src/global/types`. +* **`cache.ts`**: Manages saving a slimmed-down copy of global to IndexedDB. + +### 2. Actions + +1. **Preffered** way to update global. When inside action, use `setGlobal`, or simple `return` if sync. +2. **Sync actions** return type should be `ActionReturnType`. +3. **Async actions** return type should be `Promise`. +4. If you add or remove an action, update `actions.ts` accordingly. +5. Actions in `ui` folder should be only sync. + +### 3. Multi-Tab Support + +* Actions and selectors can accept a `tabId` parameter, so we don't lose tab context when working with multiple tabs. +* **`tabId` is required** if calling an action or selector that can accept it. +* **Exception**: UI components may call without `tabId` (they receive it automatically). + +### 4. Selectors & Reducers + +* If logic takes more than one line, create a new selector or reducer in the appropriate folder and file. +* **Selectors must be pure**: only use their inputs and global. Don't allocate new objects or arrays, as that breaks memoization. + +### 5. Data Constraints + +* Global may only store serializable primitives (strings, numbers, booleans). +* When you change a type that's cached in `cache.ts`, add a migration to avoid errors from new selectors. + +--- + +## Component Guidelines + +### 1. Accessing Global in Components + +* **Use** `withGlobal` (a `mapStateToProps` helper) to pull in state. +* **Avoid** the experimental `useSelector` API. +* **Use** `getGlobal` **only** inside hooks for one-off reads (it's non-reactive). + +### 2. Performance + +* Wrap `withGlobal` in `memo` so the component re-renders only on real data changes. +* **Don't** return new arrays or objects inside `withGlobal`; that defeats memoization. +* If you need to filter or map a list, **pass IDs as props** and do the heavy work in a `useMemo` hook. + +### 3. Example Component + +```ts +type OwnProps = { id: string }; +type StateProps = { + someValue?: string; + otherValue?: number; + thirdValue: boolean; +}; + +const Component = ({ + id, + someValue, + otherValue, + thirdValue, +}: OwnProps & StateProps) => { + // component logic... +}; + +export default memo( + withGlobal((global, { id }) => { + const { otherValue } = selectTabState(global); + const someValue = selectSomeValue(global, id); + const thirdValue = Boolean(global.rawValue); + + return { + someValue, + otherValue, + thirdValue, + }; + })(Component); +); +``` + +# Localization Guide + +**1. Setup & Fallback** + +* Translations live on [Translation Platform](https://translations.telegram.org/). +* Fallback file: `src/assets/localization/fallback.strings`. + +**2. Getting Strings** + +```ts +const lang = useLang(); + +// Simple +lang('SimpleKey'); + +// Plurals +lang('PluralKey', undefined, { pluralValue: 3 }); + +// String replacements +lang('ReplKey', { name: 'Amy' }); + +// JSX nodes (e.g. links) +lang('LinkKey', { link: }, { withNodes: true }); + +// Markdown +lang('MarkdownKey', undefined, { withNodes: true, withMarkdown: true }); +``` + +**3. Adding a New Key** + +1. Search Translation Platform for similar strings to get the correct wording. +2. Add it to `fallback.strings`. +3. If it's plural, include `_one` and `_other`. +4. Run `npm run lang:ts`. + +**4. Naming Rules** + +* **PascalCase** (no dots). +* Use short, clear prefixes for context (e.g. `Acc` for accessibility). +* Keep names under ~30 chars, shorten consistently if needed. + +**5. API & Options** + +* **Basic**: `lang(key, vars?, options?) → string` + +* **Advanced** (`withNodes`): returns `TeactNode[]` so you can inject JSX. + +* **Other options**: + + * `withMarkdown` (for simple markdown + emojis) + * `renderTextFilters` (custom filters) + * `specialReplacement` (for replacing substrings, e.g. icons) + +* **Object syntax**: + Simple form that returns string can be used in some actions. + ```ts + actions.showNotification({ key: 'LangKey' }); + + lang.with({ key: 'hello', vars: { name }, options: { withNodes: true } }); + ``` + +**6. Handy Extensions** + +* `lang.region(code)` → country name +* `lang.conjunction(['a','b','c'])` → "a, b, and c" +* `lang.disjunction(['x','y'])` → "x or y" +* `lang.number(1234)` → locale-formatted number +* Flags: `lang.isRtl`, `lang.code`, `lang.rawCode` + +**7. Beyond React** +Use `getTranslationFn()` to grab the same `lang` function in non-component code. Discouraged, use object syntax. \ No newline at end of file diff --git a/src/api/gramjs/apiBuilders/appConfig.ts b/src/api/gramjs/apiBuilders/appConfig.ts index 9c049bd42..158ad2a0c 100644 --- a/src/api/gramjs/apiBuilders/appConfig.ts +++ b/src/api/gramjs/apiBuilders/appConfig.ts @@ -101,6 +101,13 @@ export interface GramJsAppConfig extends LimitsConfig { stars_stargift_resale_amount_max?: number; stars_stargift_resale_amount_min?: number; stars_stargift_resale_commission_permille?: number; + stars_suggested_post_amount_max?: number; + stars_suggested_post_amount_min?: number; + stars_suggested_post_commission_permille?: number; + stars_suggested_post_age_min?: number; + stars_suggested_post_future_max?: number; + stars_suggested_post_future_min?: number; + ton_suggested_post_commission_permille?: number; poll_answers_max?: number; todo_items_max?: number; todo_title_length_max?: number; @@ -205,6 +212,13 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp starsStargiftResaleAmountMin: appConfig.stars_stargift_resale_amount_min, starsStargiftResaleAmountMax: appConfig.stars_stargift_resale_amount_max, starsStargiftResaleCommissionPermille: appConfig.stars_stargift_resale_commission_permille, + starsSuggestedPostAmountMax: appConfig.stars_suggested_post_amount_max, + starsSuggestedPostAmountMin: appConfig.stars_suggested_post_amount_min, + starsSuggestedPostCommissionPermille: appConfig.stars_suggested_post_commission_permille, + starsSuggestedPostAgeMin: appConfig.stars_suggested_post_age_min, + starsSuggestedPostFutureMax: appConfig.stars_suggested_post_future_max, + starsSuggestedPostFutureMin: appConfig.stars_suggested_post_future_min, + tonSuggestedPostCommissionPermille: appConfig.ton_suggested_post_commission_permille, pollMaxAnswers: appConfig.poll_answers_max, todoItemsMax: appConfig.todo_items_max ?? TODO_ITEMS_LIMIT, todoTitleLengthMax: appConfig.todo_title_length_max ?? TODO_TITLE_LENGTH_LIMIT, diff --git a/src/api/gramjs/apiBuilders/gifts.ts b/src/api/gramjs/apiBuilders/gifts.ts index 57274a8af..35ddb8106 100644 --- a/src/api/gramjs/apiBuilders/gifts.ts +++ b/src/api/gramjs/apiBuilders/gifts.ts @@ -24,7 +24,7 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift { if (starGift instanceof GramJs.StarGiftUnique) { const { id, num, ownerId, ownerName, title, attributes, availabilityIssued, availabilityTotal, slug, ownerAddress, - giftAddress, resellStars, + giftAddress, resellStars, releasedBy, } = starGift; return { @@ -41,12 +41,13 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift { slug, giftAddress, resellPriceInStars: resellStars?.toJSNumber(), + releasedByPeerId: releasedBy && getApiChatIdFromMtpPeer(releasedBy), }; } const { id, limited, stars, availabilityRemains, availabilityTotal, convertStars, firstSaleDate, lastSaleDate, soldOut, - birthday, upgradeStars, resellMinStars, title, availabilityResale, + birthday, upgradeStars, resellMinStars, title, availabilityResale, releasedBy, } = starGift; addDocumentToLocalDb(starGift.sticker); @@ -69,6 +70,7 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift { upgradeStars: upgradeStars?.toJSNumber(), title, resellMinStars: resellMinStars?.toJSNumber(), + releasedByPeerId: releasedBy && getApiChatIdFromMtpPeer(releasedBy), availabilityResale: availabilityResale?.toJSNumber(), }; } diff --git a/src/api/gramjs/apiBuilders/messageActions.ts b/src/api/gramjs/apiBuilders/messageActions.ts index bea31f515..d91fc535c 100644 --- a/src/api/gramjs/apiBuilders/messageActions.ts +++ b/src/api/gramjs/apiBuilders/messageActions.ts @@ -7,6 +7,7 @@ import { buildApiBotApp } from './bots'; import { buildApiFormattedText, buildApiPhoto } from './common'; import { buildApiStarGift } from './gifts'; import { buildTodoItem } from './messageContent'; +import { buildApiStarsAmount } from './payments'; import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers'; const UNSUPPORTED_ACTION: ApiMessageAction = { @@ -447,6 +448,36 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess count, }; } + if (action instanceof GramJs.MessageActionSuggestedPostApproval) { + const { + rejected, balanceTooLow, rejectComment, scheduleDate, price, + } = action; + return { + mediaType: 'action', + type: 'suggestedPostApproval', + isRejected: Boolean(rejected), + isBalanceTooLow: Boolean(balanceTooLow), + rejectComment, + scheduleDate, + amount: price ? buildApiStarsAmount(price) : undefined, + }; + } + if (action instanceof GramJs.MessageActionSuggestedPostSuccess) { + const { price } = action; + return { + mediaType: 'action', + type: 'suggestedPostSuccess', + amount: buildApiStarsAmount(price), + }; + } + if (action instanceof GramJs.MessageActionSuggestedPostRefund) { + const { payerInitiated } = action; + return { + mediaType: 'action', + type: 'suggestedPostRefund', + payerInitiated: Boolean(payerInitiated), + }; + } if (action instanceof GramJs.MessageActionTodoCompletions) { const { completed, incompleted, diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts index 47ba60b78..a3f38a73e 100644 --- a/src/api/gramjs/apiBuilders/messages.ts +++ b/src/api/gramjs/apiBuilders/messages.ts @@ -8,6 +8,7 @@ import type { ApiFactCheck, ApiInputMessageReplyInfo, ApiInputReplyInfo, + ApiInputSuggestedPostInfo, ApiMediaTodo, ApiMessage, ApiMessageEntity, @@ -25,6 +26,7 @@ import type { ApiSticker, ApiStory, ApiStorySkipped, + ApiSuggestedPost, ApiThreadInfo, ApiVideo, MediaContent, @@ -44,6 +46,9 @@ import { addTimestampEntities } from '../../../util/dates/timestamp'; import { omitUndefined, pick } from '../../../util/iteratees'; import { getServerTime, getServerTimeOffset } from '../../../util/serverTime'; import { interpolateArray } from '../../../util/waveform'; +import { + buildApiStarsAmount, +} from '../apiBuilders/payments'; import { buildPeer } from '../gramjsBuilders'; import { addDocumentToLocalDb, @@ -241,6 +246,7 @@ export function buildApiMessageWithChatId( reactions: mtpMessage.reactions && buildMessageReactions(mtpMessage.reactions), emojiOnlyCount, ...(mtpMessage.replyTo && { replyInfo: buildApiReplyInfo(mtpMessage.replyTo, mtpMessage) }), + ...(mtpMessage.suggestedPost && { suggestedPostInfo: buildApiSuggestedPost(mtpMessage.suggestedPost) }), forwardInfo, isEdited, editDate: mtpMessage.editDate, @@ -280,7 +286,7 @@ export function buildMessageDraft(draft: GramJs.TypeDraftMessage): ApiDraft | un } const { - message, entities, replyTo, date, effect, + message, entities, replyTo, date, effect, suggestedPost, } = draft; const replyInfo = replyTo instanceof GramJs.InputReplyToMessage ? { @@ -293,14 +299,31 @@ export function buildMessageDraft(draft: GramJs.TypeDraftMessage): ApiDraft | un quoteOffset: replyTo.quoteOffset, } satisfies ApiInputMessageReplyInfo : undefined; + const suggestedPostInfo = suggestedPost instanceof GramJs.SuggestedPost ? { + isAccepted: suggestedPost.accepted, + isRejected: suggestedPost.rejected, + price: suggestedPost.price ? buildApiStarsAmount(suggestedPost.price) : undefined, + scheduleDate: suggestedPost.scheduleDate, + } satisfies ApiInputSuggestedPostInfo : undefined; + return { text: message ? buildMessageTextContent(message, entities) : undefined, replyInfo, + suggestedPostInfo, date, effectId: effect?.toString(), }; } +function buildApiSuggestedPost(suggestedPost: GramJs.SuggestedPost): ApiSuggestedPost { + return { + isAccepted: suggestedPost.accepted, + isRejected: suggestedPost.rejected, + price: suggestedPost.price ? buildApiStarsAmount(suggestedPost.price) : undefined, + scheduleDate: suggestedPost.scheduleDate, + }; +} + function buildApiMessageForwardInfo(fwdFrom: GramJs.MessageFwdHeader, isChatWithSelf = false): ApiMessageForwardInfo { const savedFromPeerId = fwdFrom.savedFromPeer && getApiChatIdFromMtpPeer(fwdFrom.savedFromPeer); const fromId = fwdFrom.fromId && getApiChatIdFromMtpPeer(fwdFrom.fromId); @@ -396,6 +419,7 @@ export function buildLocalMessage( text?: string, entities?: ApiMessageEntity[], replyInfo?: ApiInputReplyInfo, + suggestedPostInfo?: ApiInputSuggestedPostInfo, attachment?: ApiAttachment, sticker?: ApiSticker, gif?: ApiVideo, @@ -439,6 +463,7 @@ export function buildLocalMessage( isOutgoing: !isChannel, senderId: chat.type !== 'chatTypePrivate' ? (sendAs?.id || currentUserId) : undefined, replyInfo: resultReplyInfo, + suggestedPostInfo, ...(groupedId && { groupedId, ...(media && (media.photo || media.video) && { isInAlbum: true }), diff --git a/src/api/gramjs/apiBuilders/payments.ts b/src/api/gramjs/apiBuilders/payments.ts index f11319852..fb578a2ad 100644 --- a/src/api/gramjs/apiBuilders/payments.ts +++ b/src/api/gramjs/apiBuilders/payments.ts @@ -461,11 +461,26 @@ export function buildApiStarsGiftOptions(option: GramJs.StarsGiftOption): ApiSta }; } -export function buildApiStarsAmount(amount: GramJs.StarsAmount): ApiStarsAmount { - return { - amount: amount.amount.toJSNumber(), +export function buildApiStarsAmount(amount: GramJs.TypeStarsAmount): ApiStarsAmount | undefined { + if (amount instanceof GramJs.StarsAmount) { + return { + amount: amount.amount.toJSNumber(), + nanos: amount.nanos, + }; + } + + if (amount instanceof GramJs.StarsTonAmount) { + return undefined; + } + + return undefined; +} + +export function buildInputStarsAmount(amount: ApiStarsAmount): GramJs.TypeStarsAmount { + return new GramJs.StarsAmount({ + amount: bigInt(amount.amount), nanos: amount.nanos, - }; + }); } export function buildApiStarsGiveawayWinnersOption( @@ -532,9 +547,9 @@ export function buildApiStarsTransactionPeer(peer: GramJs.TypeStarsTransactionPe return { type: 'unsupported' }; } -export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction): ApiStarsTransaction { +export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction): ApiStarsTransaction | undefined { const { - date, id, peer, stars, description, photo, title, refund, extendedMedia, failed, msgId, pending, gift, reaction, + date, id, peer, amount, description, photo, title, refund, extendedMedia, failed, msgId, pending, gift, reaction, subscriptionPeriod, stargift, giveawayPostId, starrefCommissionPermille, stargiftUpgrade, paidMessages, stargiftResale, } = transaction; @@ -548,11 +563,16 @@ export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction): const starRefCommision = starrefCommissionPermille ? starrefCommissionPermille / 10 : undefined; + const starsAmount = buildApiStarsAmount(amount); + if (!starsAmount) { + return undefined; + } + return { id, date, peer: buildApiStarsTransactionPeer(peer), - stars: buildApiStarsAmount(stars), + stars: starsAmount, title, description, photo: photo && buildApiWebDocument(photo), diff --git a/src/api/gramjs/apiBuilders/statistics.ts b/src/api/gramjs/apiBuilders/statistics.ts index 0260b9c6c..281340746 100644 --- a/src/api/gramjs/apiBuilders/statistics.ts +++ b/src/api/gramjs/apiBuilders/statistics.ts @@ -54,15 +54,15 @@ export function buildChannelStatistics(stats: GramJs.stats.BroadcastStats): ApiC } export function buildChannelMonetizationStatistics( - stats: GramJs.stats.BroadcastRevenueStats, + stats: GramJs.payments.StarsRevenueStats, ): ApiChannelMonetizationStatistics { return { // Graphs - topHoursGraph: buildGraph(stats.topHoursGraph), + topHoursGraph: stats.topHoursGraph ? buildGraph(stats.topHoursGraph) : undefined, revenueGraph: buildGraph(stats.revenueGraph, undefined, true, stats.usdRate), // Statistics overview - balances: buildChannelMonetizationBalances(stats.balances), + balances: buildChannelMonetizationBalances(stats.status), usdRate: stats.usdRate, }; } @@ -269,7 +269,7 @@ function buildChannelMonetizationBalances({ availableBalance, overallRevenue, withdrawalEnabled, -}: GramJs.BroadcastRevenueBalances): ChannelMonetizationBalances { +}: GramJs.StarsRevenueStatus): ChannelMonetizationBalances { return { currentBalance: Number(currentBalance) / DECIMALS, availableBalance: Number(availableBalance) / DECIMALS, diff --git a/src/api/gramjs/gramjsBuilders/index.ts b/src/api/gramjs/gramjsBuilders/index.ts index 0f6603e3f..e8e2900b0 100644 --- a/src/api/gramjs/gramjsBuilders/index.ts +++ b/src/api/gramjs/gramjsBuilders/index.ts @@ -15,6 +15,7 @@ import type { ApiInputPrivacyRules, ApiInputReplyInfo, ApiInputStorePaymentPurpose, + ApiInputSuggestedPostInfo, ApiMessageEntity, ApiNewMediaTodo, ApiNewPoll, @@ -40,6 +41,7 @@ import { import { CHANNEL_ID_BASE, DEFAULT_STATUS_ICON_ID } from '../../../config'; import { pick } from '../../../util/iteratees'; +import { buildInputStarsAmount } from '../apiBuilders/payments'; import { deserializeBytes } from '../helpers/misc'; import localDb from '../localDb'; @@ -888,6 +890,16 @@ export function buildInputReplyTo(replyInfo: ApiInputReplyInfo) { return undefined; } +export function buildInputSuggestedPost(suggestedPostInfo: ApiInputSuggestedPostInfo): GramJs.SuggestedPost { + const isPaid = Boolean(suggestedPostInfo.price) + && Boolean((suggestedPostInfo.price.amount || suggestedPostInfo.price.nanos)); + + return new GramJs.SuggestedPost({ + price: isPaid ? buildInputStarsAmount(suggestedPostInfo.price!) : undefined, + scheduleDate: suggestedPostInfo.scheduleDate, + }); +} + export function buildInputPrivacyRules( rules: ApiInputPrivacyRules, ) { diff --git a/src/api/gramjs/methods/chats.ts b/src/api/gramjs/methods/chats.ts index 1e39190b8..1b98753d7 100644 --- a/src/api/gramjs/methods/chats.ts +++ b/src/api/gramjs/methods/chats.ts @@ -68,6 +68,7 @@ import { buildInputPeer, buildInputPhoto, buildInputReplyTo, + buildInputSuggestedPost, buildInputUser, buildMtpMessageEntity, DEFAULT_PRIMITIVES, @@ -526,6 +527,7 @@ export function saveDraft({ message: draft?.text?.text || DEFAULT_PRIMITIVES.STRING, entities: draft?.text?.entities?.map(buildMtpMessageEntity), replyTo: draft?.replyInfo && buildInputReplyTo(draft.replyInfo), + suggestedPost: draft?.suggestedPostInfo && buildInputSuggestedPost(draft.suggestedPostInfo), })); } diff --git a/src/api/gramjs/methods/messages.ts b/src/api/gramjs/methods/messages.ts index dac234a3b..76a29de4c 100644 --- a/src/api/gramjs/methods/messages.ts +++ b/src/api/gramjs/methods/messages.ts @@ -14,6 +14,7 @@ import type { ApiFormattedText, ApiGlobalMessageSearchType, ApiInputReplyInfo, + ApiInputSuggestedPostInfo, ApiMessage, ApiMessageEntity, ApiMessageSearchContext, @@ -76,13 +77,16 @@ import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers'; import { buildApiUser, buildApiUserStatuses } from '../apiBuilders/users'; import { buildInputChannel, + buildInputDocument, buildInputMediaDocument, buildInputPeer, + buildInputPhoto, buildInputPoll, buildInputPollFromExisting, buildInputReaction, buildInputReplyTo, buildInputStory, + buildInputSuggestedPost, buildInputTextWithEntities, buildInputTodo, buildInputUser, @@ -98,6 +102,7 @@ import { deserializeBytes, resolveMessageApiChatId, } from '../helpers/misc'; +import localDb from '../localDb'; import { sendApiUpdate } from '../updates/apiUpdateEmitter'; import { processMessageAndUpdateThreadInfo } from '../updates/entityProcessor'; import { processAffectedHistory, updateChannelState } from '../updates/updateManager'; @@ -266,8 +271,8 @@ export function sendMessageLocal( params: SendMessageParams, ) { const { - chat, lastMessageId, text, entities, replyInfo, attachment, sticker, story, gif, poll, todo, contact, - scheduledAt, groupedId, sendAs, wasDrafted, isInvertedMedia, effectId, isPending, messagePriceInStars, + chat, lastMessageId, text, entities, replyInfo, suggestedPostInfo, attachment, sticker, story, gif, poll, todo, + contact, scheduledAt, groupedId, sendAs, wasDrafted, isInvertedMedia, effectId, isPending, messagePriceInStars, } = params; if (!chat) return undefined; @@ -281,6 +286,7 @@ export function sendMessageLocal( text, entities, replyInfo, + suggestedPostInfo, attachment, sticker, gif, @@ -315,7 +321,9 @@ export function sendApiMessage( onProgress?: ApiOnProgress, ) { const { - chat, text, entities, replyInfo, attachment, sticker, story, gif, poll, todo, contact, + chat, text, entities, replyInfo, suggestedPostInfo, suggestedMedia, + attachment, sticker, story, gif, poll, todo, contact, + isSilent, scheduledAt, groupedId, noWebPage, sendAs, shouldUpdateStickerSetOrder, isInvertedMedia, effectId, webPageMediaSize, webPageUrl, messagePriceInStars, } = params; @@ -343,6 +351,7 @@ export function sendApiMessage( text, entities, replyInfo, + suggestedPostInfo, attachment: attachment!, groupedId, isSilent, @@ -353,7 +362,43 @@ export function sendApiMessage( const messagePromise = (async () => { let media: GramJs.TypeInputMedia | undefined; - if (attachment) { + + if (suggestedPostInfo && suggestedMedia && !attachment) { + if (suggestedMedia.photo) { + const inputPhoto = buildInputPhoto(suggestedMedia.photo); + if (inputPhoto) { + media = new GramJs.InputMediaPhoto({ + id: inputPhoto, + spoiler: suggestedMedia.photo.isSpoiler || undefined, + }); + } + } else if (suggestedMedia.video) { + const inputDocument = buildInputDocument(suggestedMedia.video); + if (inputDocument) { + media = new GramJs.InputMediaDocument({ + id: inputDocument, + spoiler: suggestedMedia.video.isSpoiler || undefined, + }); + } + } else if (suggestedMedia.document) { + const document = suggestedMedia.document; + if (document.id) { + const localDocument = localDb.documents[document.id]; + if (localDocument) { + const inputDocument = new GramJs.InputDocument({ + id: localDocument.id, + accessHash: localDocument.accessHash, + fileReference: localDocument.fileReference, + }); + media = new GramJs.InputMediaDocument({ + id: inputDocument, + }); + } + } + } + } + + if (!media && attachment) { try { media = await uploadMedia(localMessage, attachment, onProgress!); } catch (err) { @@ -416,6 +461,7 @@ export function sendApiMessage( invertMedia: isInvertedMedia || undefined, effect: effectId ? BigInt(effectId) : undefined, allowPaidStars: messagePriceInStars ? BigInt(messagePriceInStars) : undefined, + suggestedPost: suggestedPostInfo && buildInputSuggestedPost(suggestedPostInfo), }; try { @@ -477,6 +523,7 @@ function sendGroupedMedia( text = DEFAULT_PRIMITIVES.STRING, entities, replyInfo, + suggestedPostInfo, attachment, groupedId, isSilent, @@ -488,6 +535,7 @@ function sendGroupedMedia( text?: string; entities?: ApiMessageEntity[]; replyInfo?: ApiInputReplyInfo; + suggestedPostInfo?: ApiInputSuggestedPostInfo; attachment: ApiAttachment; groupedId: string; isSilent?: boolean; @@ -571,6 +619,7 @@ function sendGroupedMedia( ...(scheduledAt && { scheduleDate: scheduledAt }), ...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }), ...(messagePriceInStars && { allowPaidStars: BigInt(messagePriceInStars * count) }), + ...(suggestedPostInfo && { suggestedPost: buildInputSuggestedPost(suggestedPostInfo) }), }), { shouldIgnoreUpdates: true, }); @@ -1075,6 +1124,30 @@ export async function deleteSavedHistory({ }); } +export async function toggleSuggestedPostApproval({ + chat, + messageId, + reject, + scheduleDate, + rejectComment, +}: { + chat: ApiChat; + messageId: number; + reject?: boolean; + scheduleDate?: number; + rejectComment?: string; +}) { + const result = await invokeRequest(new GramJs.messages.ToggleSuggestedPostApproval({ + peer: buildInputPeer(chat.id, chat.accessHash), + msgId: messageId, + reject: reject || undefined, + scheduleDate, + rejectComment, + })); + + return result; +} + export async function reportMessages({ peer, messageIds, description, option, }: { diff --git a/src/api/gramjs/methods/stars.ts b/src/api/gramjs/methods/stars.ts index bc8e31c98..d58a279a4 100644 --- a/src/api/gramjs/methods/stars.ts +++ b/src/api/gramjs/methods/stars.ts @@ -10,6 +10,7 @@ import type { ApiStarGiftRegular, } from '../../types'; +import { buildApiChatFromPreview } from '../apiBuilders/chats'; import { buildApiResaleGifts, buildApiSavedStarGift, buildApiStarGift, buildApiStarGiftAttribute, buildInputResaleGiftsAttributes } from '../apiBuilders/gifts'; import { @@ -20,6 +21,7 @@ import { buildApiStarsTransaction, buildApiStarTopupOption, } from '../apiBuilders/payments'; +import { buildApiUser } from '../apiBuilders/users'; import { buildInputPeer, buildInputSavedStarGift, buildInputUser, DEFAULT_PRIMITIVES } from '../gramjsBuilders'; import { checkErrorType, wrapError } from '../helpers/misc'; import { invokeRequest } from './client'; @@ -44,8 +46,18 @@ export async function fetchStarGifts() { return undefined; } + const chats = result.chats?.map((chat) => buildApiChatFromPreview(chat)).filter(Boolean); + const users = result.users?.map(buildApiUser).filter(Boolean); + // Right now, only regular star gifts can be bought, but API are not specific - return result.gifts.map(buildApiStarGift).filter((gift): gift is ApiStarGiftRegular => gift.type === 'starGift'); + const gifts + = result.gifts.map(buildApiStarGift).filter((gift): gift is ApiStarGiftRegular => gift.type === 'starGift'); + + return { + gifts, + chats, + users, + }; } export async function fetchResaleGifts({ @@ -179,12 +191,18 @@ export async function fetchStarsStatus() { return undefined; } + const balance = buildApiStarsAmount(result.balance); + if (!balance) { + // For now, skip if balance is in TON + return undefined; + } + return { nextHistoryOffset: result.nextOffset, - history: result.history?.map(buildApiStarsTransaction), + history: result.history?.map(buildApiStarsTransaction).filter(Boolean), nextSubscriptionOffset: result.subscriptionsNextOffset, subscriptions: result.subscriptions?.map(buildApiStarsSubscription), - balance: buildApiStarsAmount(result.balance), + balance, }; } @@ -214,10 +232,16 @@ export async function fetchStarsTransactions({ return undefined; } + const balance = buildApiStarsAmount(result.balance); + if (!balance) { + // For now, skip if balance is in TON + return undefined; + } + return { nextOffset: result.nextOffset, - history: result.history?.map(buildApiStarsTransaction), - balance: buildApiStarsAmount(result.balance), + history: result.history?.map(buildApiStarsTransaction).filter(Boolean), + balance, }; } @@ -261,10 +285,16 @@ export async function fetchStarsSubscriptions({ return undefined; } + const balance = buildApiStarsAmount(result.balance); + if (!balance) { + // For now, skip if balance is in TON + return undefined; + } + return { nextOffset: result.subscriptionsNextOffset, subscriptions: result.subscriptions.map(buildApiStarsSubscription), - balance: buildApiStarsAmount(result.balance), + balance, }; } diff --git a/src/api/gramjs/methods/statistics.ts b/src/api/gramjs/methods/statistics.ts index 1a3cbff76..eaa769f90 100644 --- a/src/api/gramjs/methods/statistics.ts +++ b/src/api/gramjs/methods/statistics.ts @@ -45,7 +45,7 @@ export async function fetchChannelMonetizationStatistics({ peer: ApiPeer; dcId?: number; }) { - const result = await invokeRequest(new GramJs.stats.GetBroadcastRevenueStats({ + const result = await invokeRequest(new GramJs.payments.GetStarsRevenueStats({ peer: buildInputPeer(peer.id, peer.accessHash), }), { dcId, @@ -234,7 +234,7 @@ export async function fetchMonetizationRevenueWithdrawalUrl({ return password; } - const result = await invokeRequest(new GramJs.stats.GetBroadcastRevenueWithdrawalUrl({ + const result = await invokeRequest(new GramJs.payments.GetStarsRevenueWithdrawalUrl({ peer: buildInputPeer(peer.id, peer.accessHash), password, }), { diff --git a/src/api/gramjs/updates/mtpUpdateHandler.ts b/src/api/gramjs/updates/mtpUpdateHandler.ts index 0f691f3cb..bcf523f80 100644 --- a/src/api/gramjs/updates/mtpUpdateHandler.ts +++ b/src/api/gramjs/updates/mtpUpdateHandler.ts @@ -1042,9 +1042,14 @@ export function updater(update: Update) { isEnabled: update.enabled ? true : undefined, }); } else if (update instanceof GramJs.UpdateStarsBalance) { + const balance = buildApiStarsAmount(update.balance); + if (!balance) { + // Skip TON balance updates for now + return; + } sendApiUpdate({ '@type': 'updateStarsBalance', - balance: buildApiStarsAmount(update.balance), + balance, }); } else if (update instanceof GramJs.UpdatePaidReactionPrivacy) { sendApiUpdate({ diff --git a/src/api/types/chats.ts b/src/api/types/chats.ts index 315ece68e..e8cc18851 100644 --- a/src/api/types/chats.ts +++ b/src/api/types/chats.ts @@ -1,6 +1,6 @@ import type { ApiBotCommand } from './bots'; import type { - ApiChatReactions, ApiFormattedText, ApiInputMessageReplyInfo, ApiPhoto, ApiStickerSet, + ApiChatReactions, ApiFormattedText, ApiInputMessageReplyInfo, ApiInputSuggestedPostInfo, ApiPhoto, ApiStickerSet, } from './messages'; import type { ApiBotVerification, ApiChatInviteImporter, ApiPeerNotifySettings } from './misc'; import type { @@ -318,6 +318,7 @@ export interface ApiChatLink { export type ApiDraft = { text?: ApiFormattedText; replyInfo?: ApiInputMessageReplyInfo; + suggestedPostInfo?: ApiInputSuggestedPostInfo; date?: number; effectId?: string; isLocal?: boolean; diff --git a/src/api/types/messageActions.ts b/src/api/types/messageActions.ts index 23d18bf58..76806c2a7 100644 --- a/src/api/types/messageActions.ts +++ b/src/api/types/messageActions.ts @@ -1,7 +1,7 @@ import type { ApiGroupCall, ApiPhoneCallDiscardReason } from './calls'; import type { ApiBotApp, ApiFormattedText, ApiPhoto } from './messages'; import type { ApiTodoItem } from './messages'; -import type { ApiStarGiftRegular, ApiStarGiftUnique } from './stars'; +import type { ApiStarGiftRegular, ApiStarGiftUnique, ApiStarsAmount } from './stars'; interface ActionMediaType { mediaType: 'action'; @@ -282,6 +282,25 @@ export interface ApiMessageActionPaidMessagesPrice extends ActionMediaType { isAllowedInChannel?: boolean; } +export interface ApiMessageActionSuggestedPostApproval extends ActionMediaType { + type: 'suggestedPostApproval'; + isRejected?: boolean; + isBalanceTooLow?: boolean; + rejectComment?: string; + scheduleDate?: number; + amount?: ApiStarsAmount; +} + +export interface ApiMessageActionSuggestedPostSuccess extends ActionMediaType { + type: 'suggestedPostSuccess'; + amount?: ApiStarsAmount; +} + +export interface ApiMessageActionSuggestedPostRefund extends ActionMediaType { + type: 'suggestedPostRefund'; + payerInitiated: boolean; +} + export interface ApiMessageActionTodoCompletions extends ActionMediaType { type: 'todoCompletions'; completedIds: number[]; @@ -310,5 +329,6 @@ export type ApiMessageAction = ApiMessageActionUnsupported | ApiMessageActionCha | ApiMessageActionChannelJoined | ApiMessageActionGiftCode | ApiMessageActionGiveawayLaunch | ApiMessageActionGiveawayResults | ApiMessageActionPaymentRefunded | ApiMessageActionGiftStars | ApiMessageActionPrizeStars | ApiMessageActionStarGift | ApiMessageActionStarGiftUnique - | ApiMessageActionPaidMessagesRefunded | ApiMessageActionPaidMessagesPrice | ApiMessageActionTodoCompletions + | ApiMessageActionPaidMessagesRefunded | ApiMessageActionPaidMessagesPrice | ApiMessageActionSuggestedPostApproval + | ApiMessageActionSuggestedPostSuccess | ApiMessageActionSuggestedPostRefund | ApiMessageActionTodoCompletions | ApiMessageActionTodoAppendTasks; diff --git a/src/api/types/messages.ts b/src/api/types/messages.ts index a606691c8..c675b901d 100644 --- a/src/api/types/messages.ts +++ b/src/api/types/messages.ts @@ -9,7 +9,7 @@ import type { ApiMessageAction } from './messageActions'; import type { ApiLabeledPrice, } from './payments'; -import type { ApiStarGiftUnique } from './stars'; +import type { ApiStarGiftUnique, ApiStarsAmount } from './stars'; import type { ApiMessageStoryData, ApiStory, ApiWebPageStickerData, ApiWebPageStoryData, } from './stories'; @@ -412,12 +412,26 @@ export interface ApiInputMessageReplyInfo { quoteOffset?: number; } +export interface ApiSuggestedPost { + isAccepted?: true; + isRejected?: true; + price?: ApiStarsAmount; + scheduleDate?: number; +} + export interface ApiInputStoryReplyInfo { type: 'story'; peerId: string; storyId: number; } +export interface ApiInputSuggestedPostInfo { + price?: ApiStarsAmount; + scheduleDate?: number; + isAccepted?: true; + isRejected?: true; +} + export type ApiInputReplyInfo = ApiInputMessageReplyInfo | ApiInputStoryReplyInfo; export interface ApiMessageForwardInfo { @@ -577,6 +591,7 @@ export interface ApiMessage { isOutgoing: boolean; senderId?: string; replyInfo?: ApiReplyInfo; + suggestedPostInfo?: ApiInputSuggestedPostInfo; sendingState?: 'messageSendingStatePending' | 'messageSendingStateFailed'; forwardInfo?: ApiMessageForwardInfo; isDeleting?: boolean; @@ -866,6 +881,13 @@ interface ApiKeyboardButtonCopy { copyText: string; } +export interface ApiKeyboardButtonSuggestedMessage { + type: 'suggestedMessage'; + text: string; + buttonType: 'approve' | 'decline' | 'suggestChanges'; + disabled?: boolean; +} + export type ApiKeyboardButton = ( ApiKeyboardButtonSimple | ApiKeyboardButtonReceipt @@ -878,6 +900,7 @@ export type ApiKeyboardButton = ( | ApiKeyboardButtonSimpleWebView | ApiKeyboardButtonUrlAuth | ApiKeyboardButtonCopy + | ApiKeyboardButtonSuggestedMessage ); export type ApiKeyboardButtons = ApiKeyboardButton[][]; diff --git a/src/api/types/misc.ts b/src/api/types/misc.ts index 7e83f37c8..502c60ee2 100644 --- a/src/api/types/misc.ts +++ b/src/api/types/misc.ts @@ -252,6 +252,13 @@ export interface ApiAppConfig { starsStargiftResaleAmountMin?: number; starsStargiftResaleAmountMax?: number; starsStargiftResaleCommissionPermille?: number; + starsSuggestedPostAmountMax?: number; + starsSuggestedPostAmountMin?: number; + starsSuggestedPostCommissionPermille?: number; + starsSuggestedPostAgeMin?: number; + starsSuggestedPostFutureMax?: number; + starsSuggestedPostFutureMin?: number; + tonSuggestedPostCommissionPermille?: number; pollMaxAnswers?: number; todoItemsMax?: number; todoTitleLengthMax?: number; diff --git a/src/api/types/stars.ts b/src/api/types/stars.ts index f2588a756..72e7571bc 100644 --- a/src/api/types/stars.ts +++ b/src/api/types/stars.ts @@ -19,6 +19,7 @@ export interface ApiStarGiftRegular { isBirthday?: true; upgradeStars?: number; resellMinStars?: number; + releasedByPeerId?: string; title?: string; } @@ -36,6 +37,7 @@ export interface ApiStarGiftUnique { slug: string; giftAddress?: string; resellPriceInStars?: number; + releasedByPeerId?: string; } export type ApiStarGift = ApiStarGiftRegular | ApiStarGiftUnique; diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings index 5e66c9666..fca2cc1e7 100644 --- a/src/assets/localization/fallback.strings +++ b/src/assets/localization/fallback.strings @@ -2025,6 +2025,70 @@ "MonoforumComposerPlaceholder" = "Choose a message to reply"; "ChannelSendMessage" = "Direct Messages"; "AutomaticTranslation" = "Automatic Translation"; +"ComposerEmbeddedMessageSuggestedPostTitle" = "Suggest a Post Below"; +"ComposerEmbeddedMessageSuggestedPostDescription" = "Tap to offer a price for publishing"; +"TitleSuggestedPostAmountForAnyTime" = "{amount} for publishing anytime"; +"ActionSuggestedPostOutgoing" = "**You** suggest to post this message."; +"ActionSuggestedPostIncoming" = "**{user}** suggest to post this message."; +"ActionSuggestedChangesPrice" = "price"; +"ActionSuggestedChangesText" = "text"; +"ActionSuggestedChangesTime" = "time"; +"ActionSuggestedChangesMedia" = "media"; +"ActionSuggestedChangesOutgoing" = "**You** suggested a new {changes} for this post."; +"ActionSuggestedChangesIncoming" = "**{user}** suggested a new {changes} for this post."; +"TitlePrice" = "Price"; +"TitleTime" = "Time"; +"TitleSuggestMessage" = "Suggest a Message"; +"TitleSuggestedChanges" = "Suggest Changes"; +"EnterPriceInStars" = "Enter Price In Stars"; +"SuggestMessagePriceDescription" = "Choose how many {currency} to pay to publish this post."; +"SuggestMessageDateTimeHint" = "Select the date and time you want the post to be published."; +"SuggestMessageTimeDescription" = "{hint} The post will remain available for at least {duration} from this date."; +"TitleAnytime" = "Anytime"; +"ButtonOfferAmount" = "Offer {amount}"; +"ButtonOfferFree" = "Offer Free"; +"ButtonUpdateTerms" = "Update Terms"; +"InputPlaceholderPrice" = "Enter Price"; +"SuggestedPostApprove" = "Approve"; +"SuggestedPostDecline" = "Decline"; +"SuggestedPostSuggestChanges" = "Suggest Changes"; +"InputTitleSuggestMessageTime" = "Publishing Time"; +"SuggestedPostApproved" = "Suggested post approved"; +"SuggestedPostRejectedNotification" = "Suggested post rejected"; +"SuggestedPostAgreementReached" = "🤝 Agreement reached!"; +"SuggestedPostPublishSchedule" = "📅 This post will be automatically published on {peer} **{date}**."; +"SuggestedPostPublishScheduleYou" = "📅 Your post will be automatically published on {peer} **{date}**."; +"SuggestedPostPublished" = "📅 This post was automatically published on {peer} **{date}**."; +"SuggestedPostPublishedYou" = "📅 Your post was automatically published on {peer} **{date}**."; +"SuggestedPostCharged" = "💰 {user} has been charged {amount}."; +"SuggestedPostChargedYou" = "💰 You have been charged {amount}."; +"SuggestedPostReceiveAmount" = "⏳ {peer} will receive the {currency} once the post has been live for {duration}."; +"SuggestedPostReceiveAmountYou" = "⏳ {peer} will receive your {currency} once the post has been live for {duration}."; +"SuggestedPostRefund" = "🔄 If {peer} removes the post before it has been live for {duration}, payment will be refunded."; +"SuggestedPostRefundYou" = "🔄 If {peer} removes the post before it has been live for {duration}, your {currency} will be refunded."; +"SuggestedPostBalanceTooLow" = "⚠️ **Transaction failed** because {peer} didn't have enough {currency}."; +"SuggestedPostRefundedByUser" = "{channel} will not receive {amount} because {user} requested a refund."; +"SuggestedPostRefundedByChannel" = "{amount} was returned to {peer} because {channel} deleted the message."; +"CurrencyStars" = "Stars"; +"DeclineReasonPlaceholder" = "Add a reason (optional)"; +"DeclinePostDialogQuestion" = "Do you want to decline this post from **{sender}**?"; +"SuggestedPostRejected" = "**{peer}** rejected this message."; +"SuggestedPostRejectedYou" = "You rejected this message."; +"SuggestedPostRejectedWithReason" = "**{peer}** rejected this message with the comment."; +"SuggestedPostRejectedWithReasonYou" = "You rejected this message with the comment."; +"SuggestedPostRejectedComment" = "\"{comment}\""; +"ActionSuggestedPostSuccess" = "{channel} has received {amount} for publishing post"; +"ComposerPlaceholderCaption" = "Caption"; +"DescriptionSuggestedPostMinimumOffer" = "Minimum offer is **{amount}**."; +"SuggestedPostConfirmTitle" = "Accept Terms"; +"SuggestedPostConfirmMessage" = "Do you want to publish this post from **{peer}**?"; +"SuggestedPostConfirmDetailsAdmin" = "You will receive **{amount}** ({commission}%) for publishing this post. It must remain visible for at least {duration} after publication."; +"SuggestedPostConfirmDetailsUser" = "You will pay **{amount}** ({commission}%) for publishing this post. It must remain visible for at least {duration} after publication."; +"SuggestedPostConfirmDetailsWithTimeAdmin" = "You will receive **{amount}** ({commission}%) for publishing this post **{time}**. It must remain visible for at least {duration} after publication."; +"SuggestedPostConfirmDetailsWithTimeUser" = "You will pay **{amount}** ({commission}%) for publishing this post **{time}**. It must remain visible for at least {duration} after publication."; +"ButtonPublish" = "Publish"; +"ButtonPublishAtTime" = "Publish {time}"; +"PublishNow" = "Publish Now"; "TitleNewToDoList" = "New Checklist"; "TitleEditToDoList" = "Edit Checklist"; "TitleAppendToDoList" = "Append Checklist"; @@ -2061,4 +2125,5 @@ "HintTodoListTasksCount" = "You can add {count} more tasks"; "ToDoListErrorChooseTitle" = "Please enter a title."; "ToDoListErrorChooseTasks" = "Please enter at least one task."; -"PremiumPreviewTodo" = "Checklists"; +"GiftInfoCollectibleBy" = "Collectible #{number} by **{owner}**"; +"PremiumPreviewTodo" = "Checklists"; \ No newline at end of file diff --git a/src/bundles/extra.ts b/src/bundles/extra.ts index f6a6f4f44..5c2a8d801 100644 --- a/src/bundles/extra.ts +++ b/src/bundles/extra.ts @@ -37,6 +37,9 @@ export { default as ReportModal } from '../components/modals/reportModal/ReportM export { default as PreparedMessageModal } from '../components/modals/preparedMessage/PreparedMessageModal'; export { default as SharePreparedMessageModal } from '../components/modals/sharePreparedMessage/SharePreparedMessageModal'; +export { default as SuggestMessageModal } from '../components/modals/suggestMessage/SuggestMessageModal'; +export { default as SuggestedPostApprovalModal } + from '../components/modals/suggestedPostApproval/SuggestedPostApprovalModal'; export { default as CalendarModal } from '../components/common/CalendarModal'; export { default as DeleteMessageModal } from '../components/common/DeleteMessageModal'; export { default as PinMessageModal } from '../components/common/PinMessageModal'; diff --git a/src/components/common/CalendarModal.scss b/src/components/common/CalendarModal.scss index e996a5512..1ce1caa2e 100644 --- a/src/components/common/CalendarModal.scss +++ b/src/components/common/CalendarModal.scss @@ -30,6 +30,15 @@ flex-direction: column; justify-content: flex-end; + .description { + min-height: 2.75rem; + margin-bottom: 1rem; + margin-left: 1rem; + + font-size: 0.875rem; + color: var(--color-text-secondary); + } + .Button { text-transform: none; diff --git a/src/components/common/CalendarModal.tsx b/src/components/common/CalendarModal.tsx index 8523e5b88..4c98ad40c 100644 --- a/src/components/common/CalendarModal.tsx +++ b/src/components/common/CalendarModal.tsx @@ -33,8 +33,10 @@ export type OwnProps = { withTimePicker?: boolean; submitButtonLabel?: string; secondButtonLabel?: string; + description?: string; onClose: () => void; onSubmit: (date: Date) => void; + onDateChange?: (date: Date) => void; onSecondButtonClick?: NoneToVoidFunction; }; @@ -58,8 +60,10 @@ const CalendarModal: FC = ({ withTimePicker, submitButtonLabel, secondButtonLabel, + description, onClose, onSubmit, + onDateChange, onSecondButtonClick, }) => { const lang = useOldLang(); @@ -169,6 +173,7 @@ const CalendarModal: FC = ({ dateCopy.setMonth(currentMonth); dateCopy.setFullYear(currentYear); + onDateChange?.(dateCopy); return dateCopy; }); } @@ -196,11 +201,12 @@ const CalendarModal: FC = ({ const date = new Date(selectedDate.getTime()); date.setHours(hours); setSelectedDate(date); + onDateChange?.(date); const hoursStr = formatInputTime(hours); setSelectedHours(hoursStr); e.target.value = hoursStr; - }, [selectedDate]); + }, [selectedDate, onDateChange]); const handleChangeMinutes = useCallback((e: React.ChangeEvent) => { const value = e.target.value.replace(/[^\d]+/g, ''); @@ -215,11 +221,12 @@ const CalendarModal: FC = ({ const date = new Date(selectedDate.getTime()); date.setMinutes(minutes); setSelectedDate(date); + onDateChange?.(date); const minutesStr = formatInputTime(minutes); setSelectedMinutes(minutesStr); e.target.value = minutesStr; - }, [selectedDate]); + }, [selectedDate, onDateChange]); function renderTimePicker() { return ( @@ -331,6 +338,11 @@ const CalendarModal: FC = ({ {withTimePicker && renderTimePicker()}
+ {description && ( +
+ {description} +
+ )}
)} + {shouldShowSuggestedPostButton && ( + + )} {Boolean(botKeyboardMessageId) && !activeVoiceRecording && !editingMessage && ( ( const maxMessageLength = global.config?.maxMessageLength || DEFAULT_MAX_MESSAGE_LENGTH; const isForwarding = chatId === tabState.forwardMessages.toChatId; + const isReplying = Boolean(draft?.replyInfo); + const hasSuggestedPost = Boolean(draft?.suggestedPostInfo); const starsBalance = global.stars?.balance.amount || 0; const isStarsBalanceModalOpen = Boolean(tabState.starsBalanceModal); const isAccountFrozen = selectIsCurrentUserFrozen(global); @@ -2507,6 +2537,8 @@ export default memo(withGlobal( botKeyboardMessageId, botKeyboardPlaceholder: keyboardMessage?.keyboardPlaceholder, isForwarding, + isReplying, + hasSuggestedPost, forwardedMessagesCount: isForwarding ? forwardMessageIds!.length : undefined, pollModal: tabState.pollModal, todoListModal: tabState.todoListModal, diff --git a/src/components/common/embedded/EmbeddedMessage.scss b/src/components/common/embedded/EmbeddedMessage.scss index 44f8f3751..b6eb945fd 100644 --- a/src/components/common/embedded/EmbeddedMessage.scss +++ b/src/components/common/embedded/EmbeddedMessage.scss @@ -63,6 +63,11 @@ } } + .suggested-price-star-icon { + margin-left: 0rem; + text-indent: 0rem; + } + &--background-icons { margin: -0.1875rem -0.375rem -0.1875rem -0.1875rem; } diff --git a/src/components/common/embedded/EmbeddedMessage.tsx b/src/components/common/embedded/EmbeddedMessage.tsx index f47173be4..4b00ae139 100644 --- a/src/components/common/embedded/EmbeddedMessage.tsx +++ b/src/components/common/embedded/EmbeddedMessage.tsx @@ -4,6 +4,7 @@ import { useMemo, useRef } from '../../../lib/teact/teact'; import type { ApiChat, + ApiInputSuggestedPostInfo, ApiMessage, ApiPeer, ApiReplyInfo, MediaContainer, } from '../../../api/types'; import type { ObserveFn } from '../../../hooks/useIntersectionObserver'; @@ -22,8 +23,10 @@ import { import { getMediaContentTypeDescription } from '../../../global/helpers/messageSummary'; import { getPeerTitle } from '../../../global/helpers/peers'; import buildClassName from '../../../util/buildClassName'; +import { formatScheduledDateTime } from '../../../util/dates/dateFormat'; import { isUserId } from '../../../util/entities/ids'; import freezeWhenClosed from '../../../util/hoc/freezeWhenClosed'; +import { formatStarsAsIcon } from '../../../util/localization/format'; import { getPictogramDimensions } from '../helpers/mediaDimensions'; import renderText from '../helpers/renderText'; import { renderTextWithEntities } from '../helpers/renderTextWithEntities'; @@ -47,6 +50,7 @@ import './EmbeddedMessage.scss'; type OwnProps = { className?: string; replyInfo?: ApiReplyInfo; + suggestedPostInfo?: ApiInputSuggestedPostInfo; message?: ApiMessage; sender?: ApiPeer; senderChat?: ApiChat; @@ -72,6 +76,7 @@ const EmbeddedMessage: FC = ({ className, message, replyInfo, + suggestedPostInfo, sender, senderChat, forwardSender, @@ -139,6 +144,35 @@ const EmbeddedMessage: FC = ({ const { handleClick, handleMouseDown } = useFastClick(onClick); function renderTextContent() { + const isFree = !(suggestedPostInfo?.price?.amount); + if (suggestedPostInfo) { + if (isFree && !suggestedPostInfo.scheduleDate) { + return lang('ComposerEmbeddedMessageSuggestedPostDescription'); + } + const priceText = suggestedPostInfo.price + ? formatStarsAsIcon(lang, suggestedPostInfo.price.amount, { + className: 'suggested-price-star-icon', + }) + : ''; + const scheduleText = suggestedPostInfo.scheduleDate + ? formatScheduledDateTime(suggestedPostInfo.scheduleDate, lang, oldLang) + : ''; + if (priceText && !scheduleText) { + return lang('TitleSuggestedPostAmountForAnyTime', + { amount: priceText }, + { + withNodes: true, + withMarkdown: true, + }); + } + return ( + + {priceText} + {scheduleText ? ` • ${scheduleText}` : ''} + + ); + } + if (replyInfo?.type === 'message' && replyInfo.quoteText) { return renderTextWithEntities({ text: replyInfo.quoteText.text, @@ -186,6 +220,14 @@ const EmbeddedMessage: FC = ({ return renderText(title); } + if (suggestedPostInfo && replyInfo) { + return lang('TitleSuggestedChanges'); + } + + if (suggestedPostInfo) { + return lang('ComposerEmbeddedMessageSuggestedPostTitle'); + } + if (!senderTitle && !forwardSendersTitle) { return NBSP; } @@ -252,6 +294,7 @@ const EmbeddedMessage: FC = ({ mediaThumbnail && 'with-thumb', 'no-selection', composerForwardSenders && 'is-input-forward', + suggestedPostInfo && 'is-suggested-post', )} dir={lang.isRtl ? 'rtl' : undefined} onClick={handleClick} diff --git a/src/components/middle/MessageListContent.tsx b/src/components/middle/MessageListContent.tsx index 5d435c95a..5707e37a2 100644 --- a/src/components/middle/MessageListContent.tsx +++ b/src/components/middle/MessageListContent.tsx @@ -13,18 +13,21 @@ import { SCHEDULED_WHEN_ONLINE } from '../../config'; import { getMessageHtmlId, getMessageOriginalId, + getSuggestedChangesActionText, + getSuggestedChangesInfo, isActionMessage, isOwnMessage, isServiceNotificationMessage, } from '../../global/helpers'; import { getPeerTitle } from '../../global/helpers/peers'; -import { selectSender } from '../../global/selectors'; +import { selectChatMessage, selectSender } from '../../global/selectors'; import buildClassName from '../../util/buildClassName'; -import { formatHumanDate } from '../../util/dates/dateFormat'; +import { formatHumanDate, formatScheduledDateTime } from '../../util/dates/dateFormat'; import { compact } from '../../util/iteratees'; import { formatStarsAsText } from '../../util/localization/format'; import { isAlbum } from './helpers/groupMessages'; import { preventMessageInputBlur } from './helpers/preventMessageInputBlur'; +import { renderPeerLink } from './message/helpers/messageActions'; import useDerivedSignal from '../../hooks/useDerivedSignal'; import useLang from '../../hooks/useLang'; @@ -33,12 +36,15 @@ import usePreviousDeprecated from '../../hooks/usePreviousDeprecated'; import useMessageObservers from './hooks/useMessageObservers'; import useScrollHooks from './hooks/useScrollHooks'; +import MiniTable, { type TableEntry } from '../common/MiniTable'; import ActionMessage from './message/ActionMessage'; import Message from './message/Message'; import SenderGroupContainer from './message/SenderGroupContainer'; import SponsoredMessage from './message/SponsoredMessage'; import MessageListAccountInfo from './MessageListAccountInfo'; +import actionMessageStyles from './message/ActionMessage.module.scss'; + interface OwnProps { canShowAds?: boolean; chatId: string; @@ -176,6 +182,54 @@ const MessageListContent: FC = ({ } return undefined; }; + + const renderSuggestedPostInfoAction = (message: ApiMessage) => { + if (message.suggestedPostInfo) { + const { price, scheduleDate } = message.suggestedPostInfo; + const sender = selectSender(getGlobal(), message); + const userTitle = sender ? getPeerTitle(lang, sender) : ''; + const userLink = renderPeerLink(sender?.id, userTitle || lang('ActionFallbackUser')); + + const originalMessage = message.replyInfo?.type === 'message' && message.replyInfo.replyToMsgId + ? selectChatMessage(getGlobal(), message.chatId, message.replyInfo.replyToMsgId) + : undefined; + const changesInfo = getSuggestedChangesInfo(message, originalMessage); + + const titleText = changesInfo + ? getSuggestedChangesActionText(lang, message, originalMessage, message.isOutgoing, userLink) + : message.isOutgoing + ? lang('ActionSuggestedPostOutgoing', undefined, { withNodes: true, withMarkdown: true }) + : lang('ActionSuggestedPostIncoming', { user: userLink }, { withNodes: true, withMarkdown: true }); + + const tableData: TableEntry[] = compact([ + price && [lang('TitlePrice'), formatStarsAsText(lang, price.amount)], + Boolean(scheduleDate) && [lang('TitleTime'), formatScheduledDateTime(scheduleDate, lang, oldLang)], + ]); + + return ( +
+ +
+ {titleText} +
+ {Boolean(tableData.length) && ( + + )} +
+
+ ); + } + return undefined; + }; + const messageCountToAnimate = noAppearanceAnimation ? 0 : messageGroups.reduce((acc, messageGroup) => { return acc + messageGroup.senderGroups.flat().length; }, 0); @@ -268,6 +322,7 @@ const MessageListContent: FC = ({ return compact([ message.id === memoUnreadDividerBeforeIdRef.current && unreadDivider, message.paidMessageStars && !withUsers && renderPaidMessageAction(message, album), + message.suggestedPostInfo && renderSuggestedPostInfoAction(message), = ({ replyInfo, + suggestedPostInfo, editingId, message, sender, @@ -104,6 +106,7 @@ const ComposerEmbeddedMessage: FC = ({ }) => { const { resetDraftReplyInfo, + resetDraftSuggestedPostInfo, updateDraftReplyInfo, setEditingId, focusMessage, @@ -113,6 +116,7 @@ const ComposerEmbeddedMessage: FC = ({ setForwardNoCaptions, exitForwardMode, setShouldPreventComposerAnimation, + openSuggestMessageModal, } = getActions(); const ref = useRef(); const oldLang = useOldLang(); @@ -121,6 +125,7 @@ const ComposerEmbeddedMessage: FC = ({ const isReplyToTopicStart = message?.content.action?.type === 'topicCreate'; const isShowingReply = replyInfo && !shouldForceShowEditing; const isReplyWithQuote = Boolean(replyInfo?.quoteText); + const isShowingSuggestedPost = Boolean(suggestedPostInfo) && !shouldForceShowEditing; const isForwarding = Boolean(forwardedMessagesCount); @@ -145,6 +150,7 @@ const ComposerEmbeddedMessage: FC = ({ if (isInChangingRecipientMode) return false; if (message && (replyInfo || editingId)) return true; if (forwardSenders && isForwarding) return true; + if (isShowingSuggestedPost) return true; return false; })(); @@ -170,6 +176,9 @@ const ComposerEmbeddedMessage: FC = ({ setEditingId({ messageId: undefined }); } else if (forwardedMessagesCount) { exitForwardMode(); + } else if (isShowingSuggestedPost) { + resetDraftSuggestedPostInfo(); + resetDraftReplyInfo(); } else if (replyInfo && !shouldForceShowEditing) { resetDraftReplyInfo(); } @@ -187,6 +196,10 @@ const ComposerEmbeddedMessage: FC = ({ focusMessage({ chatId: message!.chatId, messageId: message!.id, noForumTopicPanel: true }); }; const handleMessageClick = useLastCallback((e: React.MouseEvent): void => { + if (suggestedPostInfo) { + openSuggestMessageModal({ chatId }); + return; + } handleContextMenu(e); }); @@ -236,6 +249,9 @@ const ComposerEmbeddedMessage: FC = ({ if (editingId) { return 'edit'; } + if (isShowingSuggestedPost) { + return 'cash-circle'; + } if (isForwarding) { return 'forward'; } @@ -244,7 +260,7 @@ const ComposerEmbeddedMessage: FC = ({ } return undefined; - }, [editingId, isForwarding, isShowingReply]); + }, [editingId, isForwarding, isShowingReply, isShowingSuggestedPost]); const customText = forwardedMessagesCount && forwardedMessagesCount > 1 ? oldLang('ForwardedMessageCount', forwardedMessagesCount) @@ -284,6 +300,7 @@ const ComposerEmbeddedMessage: FC = ({ isOpen={isShown} className="inside-input" replyInfo={replyInfo} + suggestedPostInfo={suggestedPostInfo} isInComposer message={strippedMessage} sender={!noAuthors ? sender : undefined} @@ -425,6 +442,7 @@ export default memo(withGlobal( const draft = selectDraft(global, chatId, threadId); const replyInfo = draft?.replyInfo; + const suggestedPostInfo = draft?.suggestedPostInfo; const replyToPeerId = replyInfo?.replyToPeerId; const senderChat = replyToPeerId ? selectChat(global, replyToPeerId) : undefined; @@ -479,6 +497,7 @@ export default memo(withGlobal( return { replyInfo, + suggestedPostInfo, editingId, message, sender, diff --git a/src/components/middle/composer/hooks/useDraft.ts b/src/components/middle/composer/hooks/useDraft.ts index cab93ad81..9703a3fe5 100644 --- a/src/components/middle/composer/hooks/useDraft.ts +++ b/src/components/middle/composer/hooks/useDraft.ts @@ -6,10 +6,11 @@ import type { ThreadId } from '../../../../types'; import type { Signal } from '../../../../util/signals'; import { ApiMessageEntityTypes } from '../../../../api/types'; -import { DRAFT_DEBOUNCE } from '../../../../config'; +import { DRAFT_DEBOUNCE, EDITABLE_INPUT_CSS_SELECTOR } from '../../../../config'; import { - requestMeasure, + requestMeasure, requestNextMutation, } from '../../../../lib/fasterdom/fasterdom'; +import focusEditableElement from '../../../../util/focusEditableElement'; import parseHtmlAsFormattedText from '../../../../util/parseHtmlAsFormattedText'; import { getTextWithEntitiesAsHtml } from '../../../common/helpers/renderTextWithEntities'; @@ -84,6 +85,7 @@ const useDraft = ({ chatId: prevState.chatId ?? chatId, threadId: prevState.threadId ?? threadId, shouldKeepReply: true, + shouldKeepSuggestedPost: true, }); } }); @@ -96,6 +98,7 @@ const useDraft = ({ return; } const isTouched = isTouchedRef.current; + const shouldUpdateSuggestedPost = draft?.suggestedPostInfo && !prevDraft?.suggestedPostInfo; if (chatId === prevChatId && threadId === prevThreadId) { if (isTouched && !draft) return; // Prevent reset from other client if we have local edits @@ -103,7 +106,7 @@ const useDraft = ({ setHtml(''); } - if (isTouched) return; + if (isTouched && !shouldUpdateSuggestedPost) return; } if (editedMessage || !draft) { @@ -111,6 +114,14 @@ const useDraft = ({ } setHtml(getTextWithEntitiesAsHtml(draft.text)); + if (shouldUpdateSuggestedPost) { + requestNextMutation(() => { + const messageInput = document.querySelector(EDITABLE_INPUT_CSS_SELECTOR); + if (messageInput) { + focusEditableElement(messageInput, true); + } + }); + } const customEmojiIds = draft.text?.entities ?.map((entity) => entity.type === ApiMessageEntityTypes.CustomEmoji && entity.documentId) diff --git a/src/components/middle/helpers/groupMessages.ts b/src/components/middle/helpers/groupMessages.ts index 56473a09d..f016843ff 100644 --- a/src/components/middle/helpers/groupMessages.ts +++ b/src/components/middle/helpers/groupMessages.ts @@ -91,6 +91,7 @@ export function groupMessages( nextMessage.id === firstUnreadId || message.senderId !== nextMessage.senderId || (!withUsers && message.paidMessageStars) + || (nextMessage.suggestedPostInfo) || message.isOutgoing !== nextMessage.isOutgoing || message.postAuthorTitle !== nextMessage.postAuthorTitle || (isActionMessage(message) && message.content.action?.type !== 'phoneCall') diff --git a/src/components/middle/message/ActionMessage.module.scss b/src/components/middle/message/ActionMessage.module.scss index 9d4ced97b..09268183b 100644 --- a/src/components/middle/message/ActionMessage.module.scss +++ b/src/components/middle/message/ActionMessage.module.scss @@ -201,3 +201,84 @@ .uniqueValue { color: white; } + +.suggestedPostContainer { + padding: 0.5rem 0.75rem !important; +} + +.suggestedPostTitle { + width: 100%; + max-width: 10rem; + font-weight: var(--font-weight-normal); + text-align: center; +} + +.suggestedPostInfo { + width: fit-content; + margin: 0.75rem auto 0; +} + +.suggestedPostBalanceTooLowBox, +.suggestedPostRejectedContentBox, +.suggestedPostContentBox, +.hoverable .textContent { + cursor: var(--custom-cursor, pointer); + transition: opacity 0.15s; + + &:hover { + opacity: 0.8; + } +} + +.suggestedPostContentBox { + max-width: 20rem !important; +} + +.suggestedPostBalanceTooLowBox { + max-width: 20rem !important; + text-align: center; +} + +.suggestedPostBalanceTooLowTitle { + margin-bottom: 0.25rem; +} + +.suggestedPostRejectedContentBox { + max-width: 15rem !important; +} + +.suggestedPostApprovalTitle { + display: flex; + justify-content: center; + + width: 100%; + margin-bottom: 0.375rem; + + font-weight: var(--font-weight-medium); +} + +.suggestedPostApprovalSection { + width: 100%; + margin-bottom: 0.375rem; + text-align: left; + + &:last-child { + margin-bottom: 0; + } +} + +.suggestedPostRejectedComment { + width: 100%; + text-align: center; +} + +.suggestedPostRejectedTitle { + display: inline-block; + text-align: center; +} + +.rejectedIcon { + margin-right: 0.125rem; + font-size: 1rem; + vertical-align: middle; +} diff --git a/src/components/middle/message/ActionMessage.tsx b/src/components/middle/message/ActionMessage.tsx index e68338f38..cbdc96d1c 100644 --- a/src/components/middle/message/ActionMessage.tsx +++ b/src/components/middle/message/ActionMessage.tsx @@ -1,4 +1,3 @@ -import type React from '../../../lib/teact/teact'; import { memo, useEffect, useMemo, useRef, useUnmountCleanup, } from '../../../lib/teact/teact'; @@ -53,6 +52,9 @@ import GiveawayPrize from './actions/GiveawayPrize'; import StarGift from './actions/StarGift'; import StarGiftUnique from './actions/StarGiftUnique'; import SuggestedPhoto from './actions/SuggestedPhoto'; +import SuggestedPostApproval from './actions/SuggestedPostApproval'; +import SuggestedPostBalanceTooLow from './actions/SuggestedPostBalanceTooLow'; +import SuggestedPostRejected from './actions/SuggestedPostRejected'; import ContextMenuContainer from './ContextMenuContainer'; import Reactions from './reactions/Reactions'; import SimilarChannels from './SimilarChannels'; @@ -98,7 +100,8 @@ const SINGLE_LINE_ACTIONS = new Set([ 'todoAppendTasks', 'unsupported', ]); -const HIDDEN_TEXT_ACTIONS = new Set(['giftCode', 'prizeStars', 'suggestProfilePhoto']); +const HIDDEN_TEXT_ACTIONS = new Set(['giftCode', 'prizeStars', + 'suggestProfilePhoto', 'suggestedPostApproval']); const ActionMessage = ({ message, @@ -139,6 +142,7 @@ const ActionMessage = ({ toggleChannelRecommendations, animateUnreadReaction, markMentionsRead, + focusMessage, } = getActions(); const ref = useRef(); @@ -150,6 +154,7 @@ const ActionMessage = ({ const isTextHidden = HIDDEN_TEXT_ACTIONS.has(action.type); const isSingleLine = SINGLE_LINE_ACTIONS.has(action.type); const isFluidMultiline = IS_FLUID_BACKGROUND_SUPPORTED && !isSingleLine; + const isClickableText = action.type === 'suggestedPostSuccess'; const messageReplyInfo = getMessageReplyInfo(message); const { replyToMsgId, replyToPeerId } = messageReplyInfo || {}; @@ -310,6 +315,30 @@ const ActionMessage = ({ toggleChannelRecommendations({ chatId }); break; } + + case 'suggestedPostApproval': { + const replyInfo = getMessageReplyInfo(message); + if (replyInfo?.type === 'message' && replyInfo.replyToMsgId) { + focusMessage({ + chatId: message.chatId, + threadId, + messageId: replyInfo.replyToMsgId, + }); + } + break; + } + + case 'suggestedPostSuccess': { + const replyInfo = getMessageReplyInfo(message); + if (replyInfo?.type === 'message' && replyInfo.replyToMsgId) { + focusMessage({ + chatId: message.chatId, + threadId, + messageId: replyInfo.replyToMsgId, + }); + } + break; + } } }); @@ -387,6 +416,30 @@ const ActionMessage = ({ /> ); + case 'suggestedPostApproval': + if (action.isBalanceTooLow) { + return ( + + ); + } + return action.isRejected ? ( + + ) : ( + + ); + default: return undefined; } @@ -421,13 +474,13 @@ const ActionMessage = ({ {!isTextHidden && ( <> {isFluidMultiline && ( -
+
)} -
+
diff --git a/src/components/middle/message/ActionMessageText.tsx b/src/components/middle/message/ActionMessageText.tsx index 6fa7276d6..3d57985bf 100644 --- a/src/components/middle/message/ActionMessageText.tsx +++ b/src/components/middle/message/ActionMessageText.tsx @@ -18,16 +18,18 @@ import { getMessageReplyInfo } from '../../../global/helpers/replies'; import { selectChat, selectChatMessage, + selectMonoforumChannel, selectPeer, selectSender, selectThreadIdFromMessage, selectTopic, } from '../../../global/selectors'; import { ensureProtocol } from '../../../util/browser/url'; -import { formatDateTimeToString, formatShortDuration } from '../../../util/dates/dateFormat'; +import { formatDateTimeToString, formatScheduledDateTime, formatShortDuration } from '../../../util/dates/dateFormat'; import { formatCurrency } from '../../../util/formatCurrency'; import { formatStarsAsText } from '../../../util/localization/format'; import { conjuctionWithNodes } from '../../../util/localization/utils'; +import { getServerTime } from '../../../util/serverTime'; import renderText from '../../common/helpers/renderText'; import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities'; import { @@ -40,6 +42,7 @@ import { import useLang from '../../../hooks/useLang'; import useLastCallback from '../../../hooks/useLastCallback'; +import useOldLang from '../../../hooks/useOldLang'; import CustomEmoji from '../../common/CustomEmoji'; import TopicDefaultIcon from '../../common/TopicDefaultIcon'; @@ -83,6 +86,7 @@ const ActionMessageText = ({ const action = message.content.action!; const lang = useLang(); + const oldLang = useOldLang(); function renderStrong(text: TeactNode) { if (asPreview) return text; @@ -92,10 +96,10 @@ const ActionMessageText = ({ const renderActionText = useLastCallback(() => { const global = getGlobal(); - const isChannel = chat && isChatChannel(chat); const isServiceNotificationsChat = chatId === SERVICE_NOTIFICATIONS_USER_ID; const isSavedMessages = chatId === currentUserId; + const isChannel = chat && isChatChannel(chat); const senderTitle = sender && getPeerTitle(lang, sender); const chatTitle = chat && getPeerTitle(lang, chat); @@ -750,6 +754,97 @@ const ActionMessageText = ({ }, { withNodes: true, withMarkdown: true }); } + case 'suggestedPostSuccess': { + const { amount: stars } = action; + const channel = chat?.isMonoforum ? selectMonoforumChannel(global, chatId) : chat; + const channelTitle = channel && getPeerTitle(lang, channel); + const channelLink = renderPeerLink(channel?.id, channelTitle || channelFallbackText, asPreview); + return lang('ActionSuggestedPostSuccess', { + channel: channelLink, + amount: formatStarsAsText(lang, stars?.amount || 0), + }, { withNodes: true }); + } + case 'suggestedPostRefund': { + const { payerInitiated } = action; + + const replyMessage = message.replyInfo?.type === 'message' && message.replyInfo.replyToMsgId + ? selectChatMessage(global, chatId, message.replyInfo.replyToMsgId) + : undefined; + + const postSender = replyMessage ? selectSender(global, replyMessage) : sender; + const postSenderTitle = postSender && getPeerTitle(lang, postSender); + const postSenderLink = renderPeerLink(postSender?.id, postSenderTitle || userFallbackText, asPreview); + + const starsAmount = replyMessage?.suggestedPostInfo?.price?.amount || 0; + + const channel = chat?.isMonoforum ? selectMonoforumChannel(global, chatId) : chat; + const channelTitle = channel && getPeerTitle(lang, channel); + const channelLink = renderPeerLink(channel?.id, channelTitle || channelFallbackText, asPreview); + + if (payerInitiated) { + return lang('SuggestedPostRefundedByUser', { + amount: formatStarsAsText(lang, starsAmount), + user: postSenderLink, + channel: channelLink, + }, { withNodes: true, withMarkdown: true }); + } + + return lang('SuggestedPostRefundedByChannel', { + amount: formatStarsAsText(lang, starsAmount), + peer: postSenderLink, + channel: channelLink, + }, { withNodes: true, withMarkdown: true }); + } + case 'suggestedPostApproval': { + const { isRejected, isBalanceTooLow, rejectComment } = action; + + if (isRejected) { + const senderTitle = sender && getPeerTitle(lang, sender); + const senderLink = renderPeerLink(sender?.id, senderTitle || userFallbackText, asPreview); + + return translateWithYou( + lang, + rejectComment ? 'SuggestedPostRejectedWithReason' : 'SuggestedPostRejected', + isOutgoing, + { peer: senderLink }, + { withMarkdown: true }, + ); + } + + if (isBalanceTooLow) { + const replyMessage = message.replyInfo?.type === 'message' && message.replyInfo.replyToMsgId + ? selectChatMessage(global, chatId, message.replyInfo.replyToMsgId) + : undefined; + + const replyMessageSender = replyMessage ? selectSender(global, replyMessage) : sender; + const replyPeerTitle = replyMessageSender && getPeerTitle(lang, replyMessageSender); + const userLink = renderPeerLink(replyMessageSender?.id, replyPeerTitle || userFallbackText, asPreview); + return lang('SuggestedPostBalanceTooLow', { + peer: userLink, + currency: lang('CurrencyStars'), + }, { withNodes: true, withMarkdown: true }); + } + + const channel = chat?.isMonoforum ? selectMonoforumChannel(global, chatId) : chat; + const channelTitle = channel && getPeerTitle(lang, channel); + const channelLink = renderPeerLink(channel?.id, channelTitle || channelFallbackText, asPreview); + + const { scheduleDate } = action; + if (scheduleDate) { + const publishDate = formatScheduledDateTime(scheduleDate, lang, oldLang); + const isPostPublished = scheduleDate <= getServerTime(); + + return translateWithYou( + lang, + isPostPublished ? 'SuggestedPostPublished' : 'SuggestedPostPublishSchedule', + isOutgoing, + { peer: channelLink, date: publishDate }, + { withMarkdown: true }, + ); + } + + return lang(UNSUPPORTED_LANG_KEY); + } case 'todoCompletions': { const { completedIds, incompletedIds } = action; diff --git a/src/components/middle/message/InlineButtons.scss b/src/components/middle/message/InlineButtons.scss index 1bf50692a..60d8c6ad2 100644 --- a/src/components/middle/message/InlineButtons.scss +++ b/src/components/middle/message/InlineButtons.scss @@ -59,6 +59,10 @@ } } + .left-icon { + margin-right: 0.25rem; + } + .inline-button-text { overflow: hidden; text-overflow: ellipsis; diff --git a/src/components/middle/message/InlineButtons.tsx b/src/components/middle/message/InlineButtons.tsx index 2a0119ec6..e7925800f 100644 --- a/src/components/middle/message/InlineButtons.tsx +++ b/src/components/middle/message/InlineButtons.tsx @@ -43,6 +43,17 @@ const InlineButtons: FC = ({ message, onClick }) => { return ; case 'copy': return ; + case 'suggestedMessage': + if (button.buttonType === 'suggestChanges') { + return ; + } + if (button.buttonType === 'approve') { + return ; + } + if (button.buttonType === 'decline') { + return ; + } + break; } return undefined; }; @@ -63,14 +74,14 @@ const InlineButtons: FC = ({ message, onClick }) => { ))}
diff --git a/src/components/middle/message/Message.tsx b/src/components/middle/message/Message.tsx index 17b992788..5be37224d 100644 --- a/src/components/middle/message/Message.tsx +++ b/src/components/middle/message/Message.tsx @@ -28,6 +28,7 @@ import type { ApiTypeStory, ApiUser, } from '../../../api/types'; +import type { ActionPayloads } from '../../../global/types'; import type { ObserveFn } from '../../../hooks/useIntersectionObserver'; import type { ActiveEmojiInteraction, @@ -44,7 +45,7 @@ import type { OnIntersectPinnedMessage } from '../hooks/usePinnedMessage'; import { MAIN_THREAD_ID } from '../../../api/types'; import { AudioOrigin } from '../../../types'; -import { EMOJI_STATUS_LOOP_LIMIT, MESSAGE_APPEARANCE_DELAY } from '../../../config'; +import { EMOJI_STATUS_LOOP_LIMIT, MESSAGE_APPEARANCE_DELAY, STARS_SUGGESTED_POST_FUTURE_MIN } from '../../../config'; import { areReactionsEmpty, getIsDownloading, @@ -120,6 +121,7 @@ import { IS_ANDROID, IS_ELECTRON, IS_TRANSLATION_SUPPORTED } from '../../../util import buildClassName from '../../../util/buildClassName'; import { isUserId } from '../../../util/entities/ids'; import { getMessageKey } from '../../../util/keys/messageKey'; +import { getServerTime } from '../../../util/serverTime'; import stopEvent from '../../../util/stopEvent'; import { isElementInViewport } from '../../../util/visibility/isElementInViewport'; import { calculateDimensionsForMessageMedia, getStickerDimensions, REM } from '../../common/helpers/mediaDimensions'; @@ -137,6 +139,7 @@ import useEnsureMessage from '../../../hooks/useEnsureMessage'; import useEnsureStory from '../../../hooks/useEnsureStory'; import useFlag from '../../../hooks/useFlag'; import { useOnIntersect } from '../../../hooks/useIntersectionObserver'; +import useLang from '../../../hooks/useLang'; import useLastCallback from '../../../hooks/useLastCallback'; import useOldLang from '../../../hooks/useOldLang'; import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated'; @@ -164,6 +167,8 @@ import ReactionStaticEmoji from '../../common/reactions/ReactionStaticEmoji'; import TopicChip from '../../common/TopicChip'; import { animateSnap } from '../../main/visualEffects/SnapEffectContainer'; import Button from '../../ui/Button'; +import ConfirmDialog from '../../ui/ConfirmDialog'; +import InputText from '../../ui/InputText'; import Album from './Album'; import AnimatedCustomEmoji from './AnimatedCustomEmoji'; import AnimatedEmoji from './AnimatedEmoji'; @@ -310,6 +315,7 @@ type StateProps = { paidMessageStars?: number; isChatWithUser?: boolean; isAccountFrozen?: boolean; + minFutureTime?: number; }; type MetaPosition = @@ -328,6 +334,7 @@ const NBSP = '\u00A0'; const NO_MEDIA_CORNERS_THRESHOLD = 18; const QUICK_REACTION_SIZE = 1.75 * REM; const EXTRA_SPACE_FOR_REACTIONS = 2.25 * REM; +const MAX_REASON_LENGTH = 200; const Message: FC = ({ message, @@ -434,10 +441,14 @@ const Message: FC = ({ paidMessageStars, isChatWithUser, isAccountFrozen, + minFutureTime, }) => { const { toggleMessageSelection, clickBotInlineButton, + clickSuggestedMessageButton, + rejectSuggestedPost, + openSuggestedPostApprovalModal, disableContextMenuHint, animateUnreadReaction, focusLastMessage, @@ -448,12 +459,15 @@ const Message: FC = ({ const bottomMarkerRef = useRef(); const quickReactionRef = useRef(); - const lang = useOldLang(); + const oldLang = useOldLang(); + const lang = useLang(); const [isTranscriptionHidden, setTranscriptionHidden] = useState(false); const [isPlayingSnapAnimation, setIsPlayingSnapAnimation] = useState(false); const [isPlayingDeleteAnimation, setIsPlayingDeleteAnimation] = useState(false); const [shouldPlayEffect, requestEffect, hideEffect] = useFlag(); + const [isDeclineDialogOpen, openDeclineDialog, closeDeclineDialog] = useFlag(); + const [declineReason, setDeclineReason] = useState(''); const { isMobile, isTouchScreen } = useAppLayout(); useOnIntersect(bottomMarkerRef, observeIntersectionForBottom); @@ -646,7 +660,7 @@ const Message: FC = ({ handleTopicChipClick, handleStoryClick, } = useInnerHandlers({ - lang, + lang: oldLang, selectMessage, message, chatId, @@ -857,7 +871,7 @@ const Message: FC = ({ scrollTargetPosition, }); - const viaBusinessBotTitle = viaBusinessBot ? getPeerFullTitle(lang, viaBusinessBot) : undefined; + const viaBusinessBotTitle = viaBusinessBot ? getPeerFullTitle(oldLang, viaBusinessBot) : undefined; const canShowPostAuthor = !message.senderId; const signature = viaBusinessBotTitle || (canShowPostAuthor && message.postAuthorTitle) @@ -1269,7 +1283,7 @@ const Message: FC = ({ )} dir="auto" > - {(isTranscriptionError ? lang('NoWordsRecognized') : ( + {(isTranscriptionError ? oldLang('NoWordsRecognized') : ( isTranscribing && transcribedText ? : transcribedText ))}

@@ -1482,24 +1496,59 @@ const Message: FC = ({ )} {asForwarded && ( - {lang('ForwardedFrom')} + {oldLang('ForwardedFrom')} )} ); } + const handleSuggestedMessageButton = useLastCallback((payload: ActionPayloads['clickBotInlineButton']) => { + if (payload.button.type !== 'suggestedMessage') return; + if (payload.button.buttonType === 'approve') { + openSuggestedPostApprovalModal({ + chatId, + messageId: message.id, + }); + return; + } + + if (payload.button.buttonType === 'decline') { + openDeclineDialog(); + return; + } + + clickSuggestedMessageButton({ + ...payload, + button: payload.button, + }); + }); + + const handleDeclineReasonChange = useLastCallback((e: React.ChangeEvent) => { + setDeclineReason(e.target.value); + }); + + const handleDeclineConfirm = useLastCallback(() => { + rejectSuggestedPost({ + chatId, + messageId: message.id, + rejectComment: declineReason.trim() || undefined, + }); + closeDeclineDialog(); + setDeclineReason(''); + }); + function renderSenderName( shouldSkipRenderForwardTitle: boolean = false, shouldSkipRenderAdminTitle: boolean = false, ) { let senderTitle; let senderColor; if (senderPeer && !(isCustomShape && viaBotId)) { - senderTitle = getPeerFullTitle(lang, senderPeer); + senderTitle = getPeerFullTitle(oldLang, senderPeer); } else if (forwardInfo?.hiddenUserName) { senderTitle = forwardInfo.hiddenUserName; } else if (storyData && originSender) { - senderTitle = getPeerFullTitle(lang, originSender); + senderTitle = getPeerFullTitle(oldLang, originSender); } const senderEmojiStatus = senderPeer && 'emojiStatus' in senderPeer && senderPeer.emojiStatus; const senderIsPremium = senderPeer && 'isPremium' in senderPeer && senderPeer.isPremium; @@ -1551,7 +1600,7 @@ const Message: FC = ({ ) : undefined} {botSender?.hasUsername && ( - {lang('ViaBot')} + {oldLang('ViaBot')} = ({ )}
{!shouldSkipRenderAdminTitle && !hasBotSenderUsername ? (forwardInfo?.isLinkedChannelPost ? ( - {lang('DiscussChannel')} + {oldLang('DiscussChannel')} ) : message.postAuthorTitle && isGroup && !asForwarded ? ( {message.postAuthorTitle} ) : senderAdminMember && !asForwarded && !viaBotId ? ( - {senderAdminMember.customTitle || lang( + {senderAdminMember.customTitle || oldLang( senderAdminMember.isOwner ? 'GroupInfo.LabelOwner' : 'GroupInfo.LabelAdmin', )} @@ -1583,6 +1632,14 @@ const Message: FC = ({ } const forwardAuthor = isGroup && asForwarded ? message.postAuthorTitle : undefined; + const shouldRenderSuggestedPostButtons = message.suggestedPostInfo + && !message.isOutgoing && !message.suggestedPostInfo.isAccepted && !message.suggestedPostInfo.isRejected; + + const isSuggestedPostExpired = useMemo(() => { + if (!message.suggestedPostInfo?.scheduleDate || !minFutureTime) return false; + const now = getServerTime(); + return message.suggestedPostInfo.scheduleDate <= now + minFutureTime; + }, [message.suggestedPostInfo, minFutureTime]); return (
= ({ color="translucent-white" round size="tiny" - ariaLabel={lang('lng_context_forward_msg')} + ariaLabel={oldLang('lng_context_forward_msg')} onClick={isLastInDocumentGroup ? handleGroupForward : handleForward} > @@ -1699,6 +1756,36 @@ const Message: FC = ({ {message.inlineButtons && ( )} + {shouldRenderSuggestedPostButtons && ( + + )} {reactionsPosition === 'outside' && !isStoryMention && ( = ({ detectedLanguage={detectedLanguage} /> )} + {isDeclineDialogOpen && ( + +
+ {renderText(lang('DeclinePostDialogQuestion', { + sender: sender ? getPeerFullTitle(oldLang, sender) : '', + }, { withNodes: true, withMarkdown: true }))} +
+ +
+ )}
); }; @@ -1871,6 +1980,8 @@ export default memo(withGlobal( const lastPlaybackTimestamp = selectMessageLastPlaybackTimestamp(global, chatId, message.id); const isAccountFrozen = selectIsCurrentUserFrozen(global); + const minFutureTime = global.appConfig?.starsSuggestedPostFutureMin || STARS_SUGGESTED_POST_FUTURE_MIN; + return { theme: selectTheme(global), forceSenderName, @@ -1957,6 +2068,7 @@ export default memo(withGlobal( tags: global.savedReactionTags?.byKey, canTranscribeVoice, viaBusinessBot, + minFutureTime, effect, poll, maxTimestamp, diff --git a/src/components/middle/message/_message-content.scss b/src/components/middle/message/_message-content.scss index 617a43d71..5fe7cc139 100644 --- a/src/components/middle/message/_message-content.scss +++ b/src/components/middle/message/_message-content.scss @@ -20,6 +20,10 @@ } } +.decline-dialog-question { + margin-bottom: 1rem; +} + .message-content { position: relative; max-width: var(--max-width); diff --git a/src/components/middle/message/actions/SuggestedPostApproval.tsx b/src/components/middle/message/actions/SuggestedPostApproval.tsx new file mode 100644 index 000000000..d5956d506 --- /dev/null +++ b/src/components/middle/message/actions/SuggestedPostApproval.tsx @@ -0,0 +1,148 @@ +import { memo } from '../../../../lib/teact/teact'; +import { withGlobal } from '../../../../global'; + +import type { ApiMessage, ApiPeer } from '../../../../api/types'; +import type { ApiMessageActionSuggestedPostApproval } from '../../../../api/types/messageActions'; + +import { STARS_SUGGESTED_POST_AGE_MIN } from '../../../../config'; +import { getPeerFullTitle } from '../../../../global/helpers/peers'; +import { getMessageReplyInfo } from '../../../../global/helpers/replies'; +import { selectIsMonoforumAdmin, selectMonoforumChannel, + selectReplyMessage, + selectSender } from '../../../../global/selectors'; +import buildClassName from '../../../../util/buildClassName'; +import { formatScheduledDateTime, formatShortDuration } from '../../../../util/dates/dateFormat'; +import { formatStarsAsText } from '../../../../util/localization/format'; +import { getServerTime } from '../../../../util/serverTime'; +import renderText from '../../../common/helpers/renderText'; +import { renderPeerLink, translateWithYou } from '../helpers/messageActions'; + +import useLang from '../../../../hooks/useLang'; +import useOldLang from '../../../../hooks/useOldLang'; + +import styles from '../ActionMessage.module.scss'; + +type OwnProps = { + message: ApiMessage; + action: ApiMessageActionSuggestedPostApproval; + onClick?: NoneToVoidFunction; +}; + +type StateProps = { + sender?: ApiPeer; + chat?: ApiPeer; + originalSender?: ApiPeer; + ageMinSeconds: number; + isAdmin: boolean; +}; + +const SuggestedPostApproval = ({ + message, + action, + sender, + chat, + originalSender, + ageMinSeconds, + isAdmin, + onClick, +}: OwnProps & StateProps) => { + const lang = useLang(); + const oldLang = useOldLang(); + const { scheduleDate, amount } = action; + + const chatTitle = chat && getPeerFullTitle(lang, chat); + const renderChatLink = () => renderPeerLink(chat?.id, chatTitle || lang('ActionFallbackChat')); + + const originalSenderTitle = originalSender && getPeerFullTitle(lang, originalSender); + const originalSenderLink = renderPeerLink(originalSender?.id, originalSenderTitle || lang('ActionFallbackUser')); + + const publishDate = scheduleDate + ? formatScheduledDateTime(scheduleDate, lang, oldLang) + : lang('TitleAnytime'); + + const isPostPublished = scheduleDate ? scheduleDate <= getServerTime() : false; + + const starsText = amount?.amount ? formatStarsAsText(lang, amount.amount) : undefined; + + const duration = formatShortDuration(lang, ageMinSeconds, true); + + return ( +
+
+ {renderText(lang('SuggestedPostAgreementReached'))} +
+ +
+ {translateWithYou( + lang, + isPostPublished ? 'SuggestedPostPublished' : 'SuggestedPostPublishSchedule', + !isAdmin, + { peer: renderChatLink(), date: publishDate }, + { withMarkdown: true }, + )} +
+ + {starsText && ( +
+ {translateWithYou(lang, + 'SuggestedPostCharged', + !isAdmin, + { + user: originalSenderLink, + amount: starsText, + }, + { withMarkdown: true }, + )} +
+ )} + + {isPostPublished && starsText && ( + <> +
+ {translateWithYou(lang, 'SuggestedPostReceiveAmount', !isAdmin, { + peer: renderChatLink(), duration, currency: lang('CurrencyStars'), + }, { withMarkdown: true })} +
+ +
+ {translateWithYou(lang, 'SuggestedPostRefund', !isAdmin, { + peer: renderChatLink(), duration, currency: lang('CurrencyStars'), + }, { withMarkdown: true })} +
+ + )} +
+ ); +}; + +export default memo(withGlobal( + (global, { message }): StateProps => { + const sender = selectSender(global, message); + const chat = selectMonoforumChannel(global, message.chatId); + + const replyInfo = getMessageReplyInfo(message); + let originalSender: ApiPeer | undefined; + + if (replyInfo?.type === 'message' && replyInfo.replyToMsgId) { + const replyMessage = selectReplyMessage(global, message); + if (replyMessage) { + originalSender = selectSender(global, replyMessage); + } + } + + const { appConfig } = global; + const ageMinSeconds = appConfig?.starsSuggestedPostAgeMin || STARS_SUGGESTED_POST_AGE_MIN; + const isAdmin = chat ? Boolean(selectIsMonoforumAdmin(global, message.chatId)) : false; + + return { + sender, + chat, + originalSender, + ageMinSeconds, + isAdmin, + }; + }, +)(SuggestedPostApproval)); diff --git a/src/components/middle/message/actions/SuggestedPostBalanceTooLow.tsx b/src/components/middle/message/actions/SuggestedPostBalanceTooLow.tsx new file mode 100644 index 000000000..dc4033ce7 --- /dev/null +++ b/src/components/middle/message/actions/SuggestedPostBalanceTooLow.tsx @@ -0,0 +1,86 @@ +import { memo } from '../../../../lib/teact/teact'; +import { getActions, withGlobal } from '../../../../global'; + +import type { ApiMessage, ApiPeer } from '../../../../api/types'; +import type { ApiMessageActionSuggestedPostApproval } from '../../../../api/types/messageActions'; + +import { getPeerFullTitle } from '../../../../global/helpers/peers'; +import { selectChatMessage, selectSender } from '../../../../global/selectors'; +import buildClassName from '../../../../util/buildClassName'; +import { renderPeerLink } from '../helpers/messageActions'; + +import useLang from '../../../../hooks/useLang'; +import useLastCallback from '../../../../hooks/useLastCallback'; + +import Sparkles from '../../../common/Sparkles'; + +import styles from '../ActionMessage.module.scss'; + +type OwnProps = { + message: ApiMessage; + action: ApiMessageActionSuggestedPostApproval; + onClick?: NoneToVoidFunction; +}; + +type StateProps = { + sender?: ApiPeer; + replyMessageSender?: ApiPeer; +}; + +const SuggestedPostBalanceTooLow = ({ + onClick, + message, + sender, + replyMessageSender, +}: OwnProps & StateProps) => { + const { openStarsBalanceModal } = getActions(); + const lang = useLang(); + + const handleGetMoreStars = useLastCallback((e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + openStarsBalanceModal({}); + }); + + const targetPeer = replyMessageSender || sender; + const peerTitle = targetPeer && getPeerFullTitle(lang, targetPeer); + const peerLink = renderPeerLink(targetPeer?.id, peerTitle || lang('ActionFallbackUser')); + + return ( +
+
+ {lang('SuggestedPostBalanceTooLow', { + peer: peerLink, + currency: lang('CurrencyStars'), + }, { withNodes: true, withMarkdown: true })} +
+ + {!message.isOutgoing && ( +
+ + {lang('ButtonBuyStars')} +
+ )} +
+ ); +}; + +export default memo(withGlobal( + (global, { message }): StateProps => { + const sender = selectSender(global, message); + + const replyMessage = message.replyInfo?.type === 'message' && message.replyInfo.replyToMsgId + ? selectChatMessage(global, message.chatId, message.replyInfo.replyToMsgId) + : undefined; + + const replyMessageSender = replyMessage ? selectSender(global, replyMessage) : undefined; + + return { + sender, + replyMessageSender, + }; + }, +)(SuggestedPostBalanceTooLow)); diff --git a/src/components/middle/message/actions/SuggestedPostRejected.tsx b/src/components/middle/message/actions/SuggestedPostRejected.tsx new file mode 100644 index 000000000..6fe8cde9c --- /dev/null +++ b/src/components/middle/message/actions/SuggestedPostRejected.tsx @@ -0,0 +1,74 @@ +import { memo } from '../../../../lib/teact/teact'; +import { withGlobal } from '../../../../global'; + +import type { ApiMessage, ApiPeer } from '../../../../api/types'; +import type { ApiMessageActionSuggestedPostApproval } from '../../../../api/types/messageActions'; + +import { getPeerTitle } from '../../../../global/helpers/peers'; +import { selectSender } from '../../../../global/selectors'; +import buildClassName from '../../../../util/buildClassName'; +import { renderPeerLink, translateWithYou } from '../helpers/messageActions'; + +import useLang from '../../../../hooks/useLang'; + +import Icon from '../../../common/icons/Icon'; + +import styles from '../ActionMessage.module.scss'; + +type OwnProps = { + message: ApiMessage; + action: ApiMessageActionSuggestedPostApproval; + onClick?: NoneToVoidFunction; +}; + +type StateProps = { + sender?: ApiPeer; +}; + +const SuggestedPostRejected = ({ + message, + action, + sender, + onClick, +}: OwnProps & StateProps) => { + const lang = useLang(); + const { isOutgoing } = message; + const { rejectComment } = action; + + const senderTitle = sender && getPeerTitle(lang, sender); + const senderLink = renderPeerLink(sender?.id, senderTitle || lang('ActionFallbackUser')); + + return ( +
+
+ + {translateWithYou( + lang, + rejectComment ? 'SuggestedPostRejectedWithReason' : 'SuggestedPostRejected', + isOutgoing, + { peer: senderLink }, + { withMarkdown: true }, + )} +
+ + {rejectComment && ( +
+ {lang('SuggestedPostRejectedComment', { comment: rejectComment })} +
+ )} +
+ ); +}; + +export default memo(withGlobal( + (global, { message }): StateProps => { + const sender = selectSender(global, message); + + return { + sender, + }; + }, +)(SuggestedPostRejected)); diff --git a/src/components/modals/ModalContainer.tsx b/src/components/modals/ModalContainer.tsx index cabe4bac8..330052338 100644 --- a/src/components/modals/ModalContainer.tsx +++ b/src/components/modals/ModalContainer.tsx @@ -42,7 +42,9 @@ import StarsBalanceModal from './stars/StarsBalanceModal.async'; import StarsPaymentModal from './stars/StarsPaymentModal.async'; import StarsSubscriptionModal from './stars/subscription/StarsSubscriptionModal.async'; import StarsTransactionInfoModal from './stars/transaction/StarsTransactionModal.async'; +import SuggestedPostApprovalModal from './suggestedPostApproval/SuggestedPostApprovalModal.async'; import SuggestedStatusModal from './suggestedStatus/SuggestedStatusModal.async'; +import SuggestMessageModal from './suggestMessage/SuggestMessageModal.async'; import UrlAuthModal from './urlAuth/UrlAuthModal.async'; import WebAppModal from './webApp/WebAppModal.async'; @@ -63,6 +65,8 @@ type ModalKey = keyof Pick { + const { + openChat, + } = getActions(); + const lang = useLang(); const activeKey = useTransitionActiveKey([modelAttribute, backdropAttribute, patternAttribute]); const subtitleColor = backdropAttribute?.textColor; @@ -77,10 +85,18 @@ const UniqueGiftHeader = ({ /> {title &&

{title}

} - {subtitle && ( -

+ {Boolean(subtitle) && ( +

{ + if (subtitlePeer) { + openChat({ id: subtitlePeer.id }); + } + }} + > {subtitle} -

+
)} {resellPrice && (

diff --git a/src/components/modals/gift/info/GiftInfoModal.tsx b/src/components/modals/gift/info/GiftInfoModal.tsx index b458de476..c368d5ee9 100644 --- a/src/components/modals/gift/info/GiftInfoModal.tsx +++ b/src/components/modals/gift/info/GiftInfoModal.tsx @@ -10,7 +10,8 @@ import type { import type { TabState } from '../../../../global/types'; import { getHasAdminRight } from '../../../../global/helpers'; -import { getPeerTitle, isApiPeerChat } from '../../../../global/helpers/peers'; +import { getPeerTitle, isApiPeerChat, isApiPeerUser } from '../../../../global/helpers/peers'; +import { getMainUsername } from '../../../../global/helpers/users'; import { selectPeer, selectUser } from '../../../../global/selectors'; import buildClassName from '../../../../util/buildClassName'; import { copyTextToClipboard } from '../../../../util/clipboard'; @@ -51,6 +52,7 @@ export type OwnProps = { type StateProps = { fromPeer?: ApiPeer; targetPeer?: ApiPeer; + releasedByPeer?: ApiPeer; currentUserId?: string; starGiftMaxConvertPeriod?: number; hasAdminRights?: boolean; @@ -67,6 +69,7 @@ const GiftInfoModal = ({ modal, fromPeer, targetPeer, + releasedByPeer, currentUserId, starGiftMaxConvertPeriod, hasAdminRights, @@ -117,6 +120,26 @@ const GiftInfoModal = ({ const isGiftUnique = gift && gift.type === 'starGiftUnique'; const uniqueGift = isGiftUnique ? gift : undefined; + const giftSubtitle = useMemo(() => { + if (!gift || gift.type !== 'starGiftUnique') return undefined; + + if (releasedByPeer) { + const releasedByUsername = `@${getMainUsername(releasedByPeer)}`; + const ownerTitle = releasedByUsername || getPeerTitle(lang, releasedByPeer); + const fallbackText = isApiPeerUser(releasedByPeer) + ? lang('ActionFallbackUser') + : lang('ActionFallbackChannel'); + + return lang('GiftInfoCollectibleBy', { + number: gift.number, owner: ownerTitle || fallbackText }, { + withNodes: true, + withMarkdown: true, + }); + } + + return lang('GiftInfoCollectible', { number: gift.number }); + }, [gift, releasedByPeer, lang]); + const canFocusUpgrade = Boolean(savedGift?.upgradeMsgId); const canManage = !canFocusUpgrade && savedGift?.inputGift && ( isTargetChat ? hasAdminRights : renderingTargetPeer?.id === currentUserId @@ -406,7 +429,8 @@ const GiftInfoModal = ({ patternAttribute={giftAttributes!.pattern!} modelAttribute={giftAttributes!.model!} title={gift.title} - subtitle={lang('GiftInfoCollectible', { number: gift.number })} + subtitle={giftSubtitle} + subtitlePeer={releasedByPeer} />

); @@ -512,6 +536,7 @@ const GiftInfoModal = ({ if (isGiftUnique) { const { ownerName, ownerAddress, ownerId } = gift; + const ownerPeer = ownerId ? selectPeer(getGlobal(), ownerId) : undefined; const { model, backdrop, pattern, originalDetails, } = giftAttributes || {}; @@ -533,7 +558,7 @@ const GiftInfoModal = ({
, ]); - } else { + } else if (ownerPeer || ownerName) { tableData.push([ lang('GiftInfoOwner'), ownerId ? { chatId: ownerId, withEmojiStatus: true } : ownerName || '', @@ -702,7 +727,8 @@ const GiftInfoModal = ({ gift, giftAttributes, renderFooterButton, isTargetChat, SettingsMenuButton, isGiftUnique, renderingModal, collectibleEmojiStatuses, currentUserEmojiStatus, saleDateInfo, - canBuyGift, giftOwnerTitle, isOpen, resellPriceInStars, + canBuyGift, giftOwnerTitle, isOpen, resellPriceInStars, giftSubtitle, + releasedByPeer, ]); return ( @@ -812,9 +838,14 @@ export default memo(withGlobal( const currentUserEmojiStatus = currentUser?.emojiStatus; const collectibleEmojiStatuses = global.collectibleEmojiStatuses?.statuses; + const gift = isSavedGift ? typeGift.gift : typeGift; + const releasedByPeerId = gift?.type === 'starGiftUnique' && gift.releasedByPeerId; + const releasedByPeer = releasedByPeerId ? selectPeer(global, releasedByPeerId) : undefined; + return { fromPeer, targetPeer, + releasedByPeer, currentUserId, starGiftMaxConvertPeriod: global.appConfig?.starGiftMaxConvertPeriod, tonExplorerUrl: global.appConfig?.tonExplorerUrl, diff --git a/src/components/modals/stars/chatRefund/ChatRefundModal.tsx b/src/components/modals/stars/chatRefund/ChatRefundModal.tsx index 17a7300bd..f126084a1 100644 --- a/src/components/modals/stars/chatRefund/ChatRefundModal.tsx +++ b/src/components/modals/stars/chatRefund/ChatRefundModal.tsx @@ -40,6 +40,7 @@ const ChatRefundModal = ({ modal, user }: OwnProps & StateProps) => { const handleConfirmRemoveFee = useLastCallback(() => { closeChatRefundModal(); if (!userId) return; + toggleNoPaidMessagesException ({ userId, shouldRefundCharged: shouldRefundStars }); }); diff --git a/src/components/modals/suggestMessage/SuggestMessageModal.async.tsx b/src/components/modals/suggestMessage/SuggestMessageModal.async.tsx new file mode 100644 index 000000000..5019ffbc6 --- /dev/null +++ b/src/components/modals/suggestMessage/SuggestMessageModal.async.tsx @@ -0,0 +1,18 @@ +import type { FC } from '../../../lib/teact/teact'; + +import type { OwnProps } from './SuggestMessageModal'; + +import { Bundles } from '../../../util/moduleLoader'; + +import useModuleLoader from '../../../hooks/useModuleLoader'; + +import Loading from '../../ui/Loading'; + +const SuggestMessageModalAsync: FC = (props) => { + const { modal } = props; + const SuggestMessageModal = useModuleLoader(Bundles.Extra, 'SuggestMessageModal', !modal); + + return SuggestMessageModal ? : ; +}; + +export default SuggestMessageModalAsync; diff --git a/src/components/modals/suggestMessage/SuggestMessageModal.module.scss b/src/components/modals/suggestMessage/SuggestMessageModal.module.scss new file mode 100644 index 000000000..1afc09de9 --- /dev/null +++ b/src/components/modals/suggestMessage/SuggestMessageModal.module.scss @@ -0,0 +1,55 @@ +.content { + display: flex; + flex-direction: column; + max-height: min(92vh, 32rem) !important; +} + +.modalHeader { + padding-top: 0.25rem !important; +} + +.section, +.form { + display: flex; + flex-direction: column; +} + +.section { + margin-bottom: 1rem; +} + +.label { + margin-block: 0.5rem; + font-size: 1rem; + font-weight: var(--font-weight-medium); + color: var(--color-text-secondary); +} + +.input { + margin-bottom: 0.5rem; +} + +.description { + min-height: 2.75rem; + margin-bottom: 0.5rem; + margin-left: 1rem; + + font-size: 0.875rem; + color: var(--color-text-secondary); +} + +.timeInputIcon { + position: absolute; + top: 50%; + right: 1rem; + transform: translateY(-50%); + + font-size: 1.25rem; + color: var(--color-text-secondary); + + transition: color 0.15s ease; +} + +.offerButton { + font-weight: var(--font-weight-medium); +} diff --git a/src/components/modals/suggestMessage/SuggestMessageModal.tsx b/src/components/modals/suggestMessage/SuggestMessageModal.tsx new file mode 100644 index 000000000..779635ebd --- /dev/null +++ b/src/components/modals/suggestMessage/SuggestMessageModal.tsx @@ -0,0 +1,256 @@ +import type React from '../../../lib/teact/teact'; +import { + memo, useEffect, + useState } from '../../../lib/teact/teact'; +import { getActions, withGlobal } from '../../../global'; + +import type { ApiDraft, ApiStarsAmount } from '../../../api/types'; +import type { ApiPeer } from '../../../api/types'; +import type { TabState } from '../../../global/types'; +import { MAIN_THREAD_ID } from '../../../api/types'; + +import { + STARS_SUGGESTED_POST_AGE_MIN, + STARS_SUGGESTED_POST_AMOUNT_MAX, + STARS_SUGGESTED_POST_AMOUNT_MIN, + STARS_SUGGESTED_POST_FUTURE_MAX, + STARS_SUGGESTED_POST_FUTURE_MIN } from '../../../config'; +import { selectPeer } from '../../../global/selectors'; +import { selectDraft } from '../../../global/selectors/messages'; +import buildClassName from '../../../util/buildClassName'; +import { formatScheduledDateTime, formatShortDuration } from '../../../util/dates/dateFormat'; +import { formatStarsAsIcon, formatStarsAsText } from '../../../util/localization/format'; + +import useLang from '../../../hooks/useLang'; +import useLastCallback from '../../../hooks/useLastCallback'; +import useOldLang from '../../../hooks/useOldLang'; + +import CalendarModal from '../../common/CalendarModal'; +import Icon from '../../common/icons/Icon'; +import Button from '../../ui/Button'; +import InputText from '../../ui/InputText'; +import Modal from '../../ui/Modal'; + +import styles from './SuggestMessageModal.module.scss'; + +export type OwnProps = { + modal: TabState['suggestMessageModal']; +}; + +import useFlag from '../../../hooks/useFlag'; + +type StateProps = { + starBalance?: ApiStarsAmount; + peer?: ApiPeer; + currentDraft?: ApiDraft; + maxAmount: number; + minAmount: number; + ageMinSeconds: number; + futureMin: number; + futureMax: number; +}; + +const SuggestMessageModal = ({ + modal, + starBalance, + peer, + currentDraft, + maxAmount, + minAmount, + ageMinSeconds, + futureMin, + futureMax, +}: OwnProps & StateProps) => { + const { closeSuggestMessageModal, updateDraftSuggestedPostInfo, openStarsBalanceModal } = getActions(); + const [isCalendarOpened, openCalendar, closeCalendar] = useFlag(); + + const currentSuggestedPostInfo = currentDraft?.suggestedPostInfo; + const currentReplyInfo = currentDraft?.replyInfo; + const isInSuggestChangesMode = Boolean(currentReplyInfo); + + const [starsAmount, setStarsAmount] = useState( + currentSuggestedPostInfo?.price?.amount || undefined, + ); + const [scheduleDate, setScheduleDate] = useState( + currentSuggestedPostInfo?.scheduleDate + ? currentSuggestedPostInfo.scheduleDate * 1000 + : undefined, + ); + + useEffect(() => { + setStarsAmount(currentSuggestedPostInfo?.price?.amount || undefined); + setScheduleDate(currentSuggestedPostInfo?.scheduleDate + ? currentSuggestedPostInfo.scheduleDate * 1000 + : undefined); + }, [currentSuggestedPostInfo]); + + const lang = useLang(); + const oldLang = useOldLang(); + + const now = Math.floor(Date.now() / 1000); + const minAt = (now + futureMin) * 1000; + const maxAt = (now + futureMax) * 1000; + const defaultSelectedTime = (now + futureMin * 2) * 1000; + + const handleAmountChange = useLastCallback((e: React.ChangeEvent) => { + const value = e.target.value; + const number = parseFloat(value); + + const result = value === '' || Number.isNaN(number) ? undefined + : Math.min(Math.max(number, 0), maxAmount); + + setStarsAmount(result); + }); + + const handleExpireDateChange = useLastCallback((date: Date) => { + setScheduleDate(date.getTime()); + closeCalendar(); + }); + + const handleAnytimeClick = useLastCallback(() => { + setScheduleDate(undefined); + closeCalendar(); + }); + + const isDisabled = Boolean(starsAmount) && starsAmount < minAmount; + + const handleOffer = useLastCallback(() => { + const neededAmount = starsAmount || 0; + + if (isDisabled) { + return; + } + + const currentBalance = starBalance?.amount || 0; + + if (neededAmount > currentBalance) { + openStarsBalanceModal({ + topup: { + balanceNeeded: neededAmount, + }, + }); + return; + } + + updateDraftSuggestedPostInfo({ + price: { amount: neededAmount, nanos: 0 }, + scheduleDate: scheduleDate ? scheduleDate / 1000 : undefined, + }); + + closeSuggestMessageModal(); + }); + + return ( + +
+
+ +
+ {starsAmount !== undefined && starsAmount > 0 && starsAmount < minAmount + ? lang('DescriptionSuggestedPostMinimumOffer', { + amount: formatStarsAsText(lang, minAmount) }, + { withNodes: true, withMarkdown: true }) + : lang('SuggestMessagePriceDescription', { + currency: lang('CurrencyStars'), + })} +
+
+ +
+
+ + + +
+
+ {lang('SuggestMessageTimeDescription', { + hint: lang('SuggestMessageDateTimeHint'), + duration: formatShortDuration(lang, ageMinSeconds, true), + })} +
+
+ + + + +
+
+ ); +}; + +export default memo(withGlobal( + (global, { modal }): StateProps => { + const starBalance = global.stars?.balance; + const peer = modal ? selectPeer(global, modal.chatId) : undefined; + const currentDraft = modal ? selectDraft(global, modal.chatId, MAIN_THREAD_ID) : undefined; + + const { appConfig } = global; + const maxAmount = appConfig?.starsSuggestedPostAmountMax || STARS_SUGGESTED_POST_AMOUNT_MAX; + const minAmount = appConfig?.starsSuggestedPostAmountMin || STARS_SUGGESTED_POST_AMOUNT_MIN; + const ageMinSeconds = appConfig?.starsSuggestedPostAgeMin || STARS_SUGGESTED_POST_AGE_MIN; + const futureMin = appConfig?.starsSuggestedPostFutureMin || STARS_SUGGESTED_POST_FUTURE_MIN; + const futureMax = appConfig?.starsSuggestedPostFutureMax || STARS_SUGGESTED_POST_FUTURE_MAX; + + return { + peer, + starBalance, + currentDraft, + maxAmount, + minAmount, + ageMinSeconds, + futureMin, + futureMax, + }; + }, +)(SuggestMessageModal)); diff --git a/src/components/modals/suggestedPostApproval/SuggestedPostApprovalModal.async.tsx b/src/components/modals/suggestedPostApproval/SuggestedPostApprovalModal.async.tsx new file mode 100644 index 000000000..9fb82b86a --- /dev/null +++ b/src/components/modals/suggestedPostApproval/SuggestedPostApprovalModal.async.tsx @@ -0,0 +1,18 @@ +import type { FC } from '../../../lib/teact/teact'; + +import type { OwnProps } from './SuggestedPostApprovalModal'; + +import { Bundles } from '../../../util/moduleLoader'; + +import useModuleLoader from '../../../hooks/useModuleLoader'; + +import Loading from '../../ui/Loading'; + +const SuggestedPostApprovalModalAsync: FC = (props) => { + const { modal } = props; + const SuggestedPostApprovalModal = useModuleLoader(Bundles.Extra, 'SuggestedPostApprovalModal', !modal); + + return SuggestedPostApprovalModal ? : ; +}; + +export default SuggestedPostApprovalModalAsync; diff --git a/src/components/modals/suggestedPostApproval/SuggestedPostApprovalModal.module.scss b/src/components/modals/suggestedPostApproval/SuggestedPostApprovalModal.module.scss new file mode 100644 index 000000000..f0d224361 --- /dev/null +++ b/src/components/modals/suggestedPostApproval/SuggestedPostApprovalModal.module.scss @@ -0,0 +1,3 @@ +.details { + margin-top: 0.5rem; +} diff --git a/src/components/modals/suggestedPostApproval/SuggestedPostApprovalModal.tsx b/src/components/modals/suggestedPostApproval/SuggestedPostApprovalModal.tsx new file mode 100644 index 000000000..a54d011f6 --- /dev/null +++ b/src/components/modals/suggestedPostApproval/SuggestedPostApprovalModal.tsx @@ -0,0 +1,234 @@ +import { memo, useState } from '../../../lib/teact/teact'; +import { getActions, withGlobal } from '../../../global'; + +import type { ApiMessage, ApiPeer } from '../../../api/types'; +import type { TabState } from '../../../global/types'; + +import { STARS_SUGGESTED_POST_AGE_MIN, + STARS_SUGGESTED_POST_COMMISSION_PERMILLE, + STARS_SUGGESTED_POST_FUTURE_MAX, + STARS_SUGGESTED_POST_FUTURE_MIN, +} from '../../../config'; +import { getPeerFullTitle } from '../../../global/helpers/peers'; +import { selectChatMessage, selectIsMonoforumAdmin, selectSender } from '../../../global/selectors'; +import { formatScheduledDateTime, formatShortDuration } from '../../../util/dates/dateFormat'; +import { formatStarsAsText } from '../../../util/localization/format'; +import renderText from '../../common/helpers/renderText'; + +import useFlag from '../../../hooks/useFlag'; +import useLang from '../../../hooks/useLang'; +import useLastCallback from '../../../hooks/useLastCallback'; +import useOldLang from '../../../hooks/useOldLang'; + +import CalendarModal from '../../common/CalendarModal'; +import ConfirmDialog from '../../ui/ConfirmDialog'; + +import styles from './SuggestedPostApprovalModal.module.scss'; + +export type OwnProps = { + modal: TabState['suggestedPostApprovalModal']; +}; + +type StateProps = { + commissionPermille: number; + minAge: number; + futureMin: number; + futureMax: number; + message?: ApiMessage; + sender?: ApiPeer; + isAdmin?: boolean; + scheduleDate?: number; +}; + +const SuggestedPostApprovalModal = ({ + modal, + message, + sender, + isAdmin, + commissionPermille, + minAge, + futureMin, + futureMax, + scheduleDate, +}: OwnProps & StateProps) => { + const { + closeSuggestedPostApprovalModal, + approveSuggestedPost, + } = getActions(); + + const lang = useLang(); + const oldLang = useOldLang(); + const [isCalendarOpened, openCalendar, closeCalendar] = useFlag(); + + const now = Math.floor(Date.now() / 1000); + const minAt = (now + futureMin) * 1000; + const maxAt = (now + futureMax) * 1000; + const defaultSelectedTime = now + futureMin * 2; + + const [selectedScheduleDate, setSelectedScheduleDate] = useState(defaultSelectedTime); + + const handleClose = useLastCallback(() => { + closeSuggestedPostApprovalModal(); + }); + + const handleApprove = useLastCallback((date?: number) => { + if (!modal) return; + + approveSuggestedPost({ + chatId: modal.chatId, + messageId: modal.messageId, + scheduleDate: date, + }); + + closeSuggestedPostApprovalModal(); + }); + + const handleCalendarDateChange = useLastCallback((date: Date) => { + const time = Math.floor(date.getTime() / 1000); + setSelectedScheduleDate(time); + }); + + const handleCalendarSubmit = useLastCallback((date: Date) => { + const time = Math.floor(date.getTime() / 1000); + closeCalendar(); + handleApprove(time); + }); + + const handlePublishNow = useLastCallback(() => { + closeCalendar(); + handleApprove(); + }); + + const handleNext = useLastCallback(() => { + if (scheduleDate) { + handleApprove(scheduleDate); + } else { + openCalendar(); + } + }); + + if (!modal || !message) { + return undefined; + } + + const senderName = sender ? getPeerFullTitle(oldLang, sender) : ''; + + const renderContent = () => { + const amount = message?.suggestedPostInfo?.price?.amount; + const question = lang( + 'SuggestedPostConfirmMessage', + { peer: senderName }, + { withNodes: true, withMarkdown: true }); + + const questionText = renderText(question); + if (!amount) { + return questionText; + } + + const commission = (commissionPermille / 10); + const amountWithCommission = amount / 100 * commission; + + const starsText = formatStarsAsText(lang, amountWithCommission); + + const ageMinSeconds = minAge; + const duration = formatShortDuration(lang, ageMinSeconds, true); + + if (scheduleDate) { + const time = formatScheduledDateTime(scheduleDate, lang, oldLang); + + const key + = isAdmin ? 'SuggestedPostConfirmDetailsWithTimeAdmin' : 'SuggestedPostConfirmDetailsWithTimeUser'; + + return ( + <> +
+ {questionText} +
+
+ {renderText(lang(key, { + amount: starsText, + commission, + duration, + time, + }, { withNodes: true, withMarkdown: true }))} +
+ + ); + } + + const key = isAdmin ? 'SuggestedPostConfirmDetailsAdmin' : 'SuggestedPostConfirmDetailsUser'; + + return ( + <> +
+ {questionText} +
+
+ {renderText(lang(key, { + amount: starsText, + commission, + duration, + }, { withNodes: true, withMarkdown: true }))} +
+ + ); + }; + + return ( + <> + + {renderContent()} + + + + + ); +}; + +export default memo(withGlobal( + (global, { modal }): StateProps => { + const message = modal && selectChatMessage(global, modal.chatId, modal.messageId); + const sender = message ? selectSender(global, message) : undefined; + const isAdmin = modal && selectIsMonoforumAdmin(global, modal.chatId); + const { appConfig } = global; + const commissionPermille = appConfig?.starsSuggestedPostCommissionPermille + || STARS_SUGGESTED_POST_COMMISSION_PERMILLE; + const minAge = appConfig?.starsSuggestedPostAgeMin || STARS_SUGGESTED_POST_AGE_MIN; + const futureMin = (appConfig?.starsSuggestedPostFutureMin || STARS_SUGGESTED_POST_FUTURE_MIN) * 2; + const futureMax = appConfig?.starsSuggestedPostFutureMax || STARS_SUGGESTED_POST_FUTURE_MAX; + const scheduleDate = message?.suggestedPostInfo?.scheduleDate; + + return { + minAge, + futureMin, + futureMax, + message, + sender, + isAdmin, + commissionPermille, + scheduleDate, + }; + }, +)(SuggestedPostApprovalModal)); diff --git a/src/config.ts b/src/config.ts index ef9812e38..eb5d1c36e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -110,6 +110,15 @@ export const TODO_ITEMS_LIMIT = 30; export const TODO_TITLE_LENGTH_LIMIT = 32; export const TODO_ITEM_LENGTH_LIMIT = 64; +// Suggested Posts defaults +export const STARS_SUGGESTED_POST_AMOUNT_MAX = 100000; +export const STARS_SUGGESTED_POST_AMOUNT_MIN = 5; +export const STARS_SUGGESTED_POST_COMMISSION_PERMILLE = 850; +export const STARS_SUGGESTED_POST_AGE_MIN = 86400; // 24 hours in seconds +export const STARS_SUGGESTED_POST_FUTURE_MAX = 2678400; // 31 days in seconds +export const STARS_SUGGESTED_POST_FUTURE_MIN = 300; // 5 minutes in seconds +export const TON_SUGGESTED_POST_COMMISSION_PERMILLE = 850; + export const STORY_VIEWS_MIN_SEARCH = 15; export const STORY_MIN_REACTIONS_SORT = 10; export const STORY_VIEWS_MIN_CONTACTS_FILTER = 20; diff --git a/src/global/actions/api/bots.ts b/src/global/actions/api/bots.ts index 6742955cb..7958dd384 100644 --- a/src/global/actions/api/bots.ts +++ b/src/global/actions/api/bots.ts @@ -78,6 +78,23 @@ const TOP_PEERS_REQUEST_COOLDOWN = 60; // 1 min const runDebouncedForSearch = debounce((cb) => cb(), 500, false); let botFatherId: string | null; +addActionHandler('clickSuggestedMessageButton', (global, actions, payload): ActionReturnType => { + const { + chatId, messageId, button, tabId = getCurrentTabId(), + } = payload; + + const { buttonType } = button; + const message = selectChatMessage(global, chatId, messageId); + + switch (buttonType) { + case 'suggestChanges': + if (!message) break; + + actions.initDraftFromSuggestedMessage({ chatId, messageId, tabId }); + break; + } +}); + addActionHandler('clickBotInlineButton', (global, actions, payload): ActionReturnType => { const { chatId, messageId, button, tabId = getCurrentTabId(), diff --git a/src/global/actions/api/messages.ts b/src/global/actions/api/messages.ts index bb066c3a6..74614c812 100644 --- a/src/global/actions/api/messages.ts +++ b/src/global/actions/api/messages.ts @@ -6,10 +6,12 @@ import type { ApiError, ApiInputMessageReplyInfo, ApiInputStoryReplyInfo, + ApiInputSuggestedPostInfo, ApiMessage, ApiOnProgress, ApiStory, ApiUser, + MediaContent, } from '../../../api/types'; import type { ForwardMessagesParams, @@ -31,6 +33,7 @@ import { MESSAGE_LIST_SLICE, RE_TELEGRAM_LINK, SERVICE_NOTIFICATIONS_USER_ID, + STARS_SUGGESTED_POST_FUTURE_MIN, SUPPORTED_AUDIO_CONTENT_TYPES, SUPPORTED_PHOTO_CONTENT_TYPES, SUPPORTED_VIDEO_CONTENT_TYPES, @@ -124,6 +127,7 @@ import { selectIsChatWithSelf, selectIsCurrentUserFrozen, selectIsCurrentUserPremium, + selectIsMonoforumAdmin, selectLanguageCode, selectListedIds, selectMessageReplyInfo, @@ -135,6 +139,7 @@ import { selectPollFromMessage, selectRealLastReadId, selectReplyCanBeSentToChat, + selectSavedDialogIdFromMessage, selectScheduledMessage, selectSendAs, selectTabState, @@ -337,6 +342,8 @@ addActionHandler('sendMessage', async (global, actions, payload): Promise const isForwarding = selectTabState(global, tabId).forwardMessages?.messageIds?.length; const draftReplyInfo = !isForwarding && !isStoryReply ? draft?.replyInfo : undefined; + const draftSuggestedPostInfo = !isForwarding && !isStoryReply + ? draft?.suggestedPostInfo : undefined; const storyReplyInfo = isStoryReply ? { type: 'story', @@ -354,16 +361,41 @@ addActionHandler('sendMessage', async (global, actions, payload): Promise const messagePriceInStars = await getPeerStarsForMessage(global, chatId!); + const suggestedPostPrice = draftSuggestedPostInfo?.price?.amount || 0; + if (suggestedPostPrice && !draftReplyInfo) { + const currentBalance = global.stars?.balance?.amount || 0; + + if (suggestedPostPrice > currentBalance) { + actions.openStarsBalanceModal({ + topup: { + balanceNeeded: suggestedPostPrice, + }, + tabId, + }); + return; + } + } + + const suggestedMessage = draftReplyInfo && draftSuggestedPostInfo + ? selectChatMessage(global, chatId!, draftReplyInfo.replyToMsgId) : undefined; + let suggestedMedia: MediaContent | undefined; + if (draftSuggestedPostInfo && suggestedMessage?.content) { + suggestedMedia = suggestedMessage.content; + } + const params: SendMessageParams = { ...payload, chat, replyInfo, + suggestedPostInfo: draftSuggestedPostInfo, + suggestedMedia, noWebPage: selectNoWebPage(global, chatId!, threadId!), sendAs: selectSendAs(global, chatId!), lastMessageId, messagePriceInStars, isStoryReply, isPending: messagePriceInStars ? true : undefined, + ...suggestedMessage && { isInvertedMedia: suggestedMessage?.isInvertedMedia }, }; if (!isStoryReply) { @@ -608,7 +640,7 @@ addActionHandler('saveDraft', (global, actions, payload): ActionReturnType => { const currentDraft = selectDraft(global, chatId, threadId); - if (chat.isMonoforum && !currentDraft?.replyInfo) { + if (chat.isMonoforum && !currentDraft?.replyInfo && !currentDraft?.suggestedPostInfo) { return; // Monoforum doesn't support drafts outside threads } @@ -616,6 +648,7 @@ addActionHandler('saveDraft', (global, actions, payload): ActionReturnType => { text, replyInfo: currentDraft?.replyInfo, effectId: currentDraft?.effectId, + suggestedPostInfo: currentDraft?.suggestedPostInfo, }; saveDraft({ @@ -625,7 +658,7 @@ addActionHandler('saveDraft', (global, actions, payload): ActionReturnType => { addActionHandler('clearDraft', (global, actions, payload): ActionReturnType => { const { - chatId, threadId = MAIN_THREAD_ID, isLocalOnly, shouldKeepReply, + chatId, threadId = MAIN_THREAD_ID, isLocalOnly, shouldKeepReply, shouldKeepSuggestedPost, } = payload; const currentDraft = selectDraft(global, chatId, threadId); if (!currentDraft) { @@ -634,8 +667,9 @@ addActionHandler('clearDraft', (global, actions, payload): ActionReturnType => { const currentReplyInfo = currentDraft.replyInfo; - const newDraft: ApiDraft | undefined = shouldKeepReply && currentReplyInfo ? { - replyInfo: currentReplyInfo, + const newDraft: ApiDraft | undefined = (shouldKeepReply || shouldKeepSuggestedPost) ? { + replyInfo: shouldKeepReply ? currentReplyInfo : undefined, + suggestedPostInfo: shouldKeepSuggestedPost ? currentDraft.suggestedPostInfo : undefined, } : undefined; saveDraft({ @@ -665,6 +699,7 @@ addActionHandler('updateDraftReplyInfo', (global, actions, payload): ActionRetur const newDraft: ApiDraft = { ...currentDraft, replyInfo: updatedReplyInfo, + suggestedPostInfo: undefined, }; saveDraft({ @@ -682,7 +717,7 @@ addActionHandler('resetDraftReplyInfo', (global, actions, payload): ActionReturn const chat = selectChat(global, chatId); const currentDraft = selectDraft(global, chatId, threadId); - if (chat?.isMonoforum && !currentDraft?.replyInfo) { + if (chat?.isMonoforum && !currentDraft?.replyInfo && !currentDraft?.suggestedPostInfo) { return; // Monoforum doesn't support drafts outside threads } const newDraft: ApiDraft | undefined = !currentDraft?.text ? undefined : { @@ -695,6 +730,92 @@ addActionHandler('resetDraftReplyInfo', (global, actions, payload): ActionReturn }); }); +addActionHandler('updateDraftSuggestedPostInfo', (global, actions, payload): ActionReturnType => { + const { tabId = getCurrentTabId(), ...update } = payload; + const currentMessageList = selectCurrentMessageList(global, tabId); + if (!currentMessageList) { + return; + } + + const { chatId, threadId } = currentMessageList; + + const currentDraft = selectDraft(global, chatId, threadId); + + const updatedSuggestedPostInfo = { + ...currentDraft?.suggestedPostInfo, + ...update, + } as ApiInputSuggestedPostInfo; + + const newDraft: ApiDraft = { + ...currentDraft, + suggestedPostInfo: updatedSuggestedPostInfo, + }; + + saveDraft({ + global, chatId, threadId, draft: newDraft, isLocalOnly: true, noLocalTimeUpdate: true, + }); +}); + +addActionHandler('resetDraftSuggestedPostInfo', (global, actions, payload): ActionReturnType => { + const { tabId = getCurrentTabId() } = payload || {}; + const currentMessageList = selectCurrentMessageList(global, tabId); + if (!currentMessageList) { + return; + } + const { chatId, threadId } = currentMessageList; + + saveDraft({ + global, chatId, threadId, draft: undefined, isLocalOnly: false, + }); +}); + +addActionHandler('initDraftFromSuggestedMessage', (global, actions, payload): ActionReturnType => { + const { chatId, messageId, tabId = getCurrentTabId() } = payload; + const message = selectChatMessage(global, chatId, messageId); + if (!message) { + return; + } + + const currentMessageList = selectCurrentMessageList(global, tabId); + if (!currentMessageList) { + return; + } + + const { threadId } = currentMessageList; + + actions.clearDraft({ + chatId, + threadId, + isLocalOnly: true, + }); + + actions.updateDraftReplyInfo({ + replyToMsgId: messageId, + monoforumPeerId: selectSavedDialogIdFromMessage(global, message), + tabId, + }); + + if (message.suggestedPostInfo) { + const { scheduleDate, ...messageSuggestedPost } = message.suggestedPostInfo; + const now = getServerTime(); + const futureMin = global.appConfig?.starsSuggestedPostFutureMin || STARS_SUGGESTED_POST_FUTURE_MIN; + + const validScheduleDate = scheduleDate && scheduleDate > now + futureMin ? scheduleDate : undefined; + + actions.updateDraftSuggestedPostInfo({ + ...messageSuggestedPost, + scheduleDate: validScheduleDate, + tabId, + }); + } + + actions.saveDraft({ + chatId, + threadId, + text: message.content.text, + }); +}); + addActionHandler('saveEffectInDraft', (global, actions, payload): ActionReturnType => { const { chatId, threadId, effectId, @@ -702,7 +823,7 @@ addActionHandler('saveEffectInDraft', (global, actions, payload): ActionReturnTy const chat = selectChat(global, chatId); const currentDraft = selectDraft(global, chatId, threadId); - if (chat?.isMonoforum && !currentDraft?.replyInfo) { + if (chat?.isMonoforum && !currentDraft?.replyInfo && !currentDraft?.suggestedPostInfo) { return; // Monoforum doesn't support drafts outside threads } @@ -1713,6 +1834,9 @@ export async function getPeerStarsForMessage( if (!peer) return undefined; if (isApiPeerChat(peer)) { + if (selectIsMonoforumAdmin(global, peerId)) { + return undefined; + } return peer.paidMessagesStars; } @@ -2068,6 +2192,65 @@ addActionHandler('fetchUnreadMentions', async (global, actions, payload): Promis await fetchUnreadMentions(global, chatId, offsetId); }); +addActionHandler('approveSuggestedPost', async (global, actions, payload): Promise => { + const { chatId, messageId, scheduleDate, tabId = getCurrentTabId() } = payload; + const chat = selectChat(global, chatId); + if (!chat) return; + + const message = selectChatMessage(global, chatId, messageId); + + const isAdmin = selectIsMonoforumAdmin(global, chatId); + + if (!isAdmin && message?.suggestedPostInfo?.price?.amount) { + const neededAmount = message.suggestedPostInfo.price.amount; + const currentBalance = global.stars?.balance?.amount || 0; + + if (neededAmount > currentBalance) { + actions.openStarsBalanceModal({ + topup: { + balanceNeeded: neededAmount, + }, + tabId, + }); + return; + } + } + + const result = await callApi('toggleSuggestedPostApproval', { + chat, + messageId, + reject: false, + scheduleDate, + }); + + if (!result) return; + + actions.showNotification({ + message: { key: 'SuggestedPostApproved' }, + tabId, + }); +}); + +addActionHandler('rejectSuggestedPost', async (global, actions, payload): Promise => { + const { chatId, messageId, rejectComment, tabId = getCurrentTabId() } = payload; + const chat = selectChat(global, chatId); + if (!chat) return; + + const result = await callApi('toggleSuggestedPostApproval', { + chat, + messageId, + reject: true, + rejectComment, + }); + + if (!result) return; + + actions.showNotification({ + message: { key: 'SuggestedPostRejectedNotification' }, + tabId, + }); +}); + async function fetchUnreadMentions(global: T, chatId: string, offsetId?: number) { const chat = selectChat(global, chatId); if (!chat) return; diff --git a/src/global/actions/api/stars.ts b/src/global/actions/api/stars.ts index 021b381d6..127f0a797 100644 --- a/src/global/actions/api/stars.ts +++ b/src/global/actions/api/stars.ts @@ -99,7 +99,9 @@ addActionHandler('loadStarGifts', async (global): Promise => { return; } - const byId = buildCollectionByKey(result, 'id'); + global = getGlobal(); + + const byId = buildCollectionByKey(result.gifts, 'id'); const idsByCategoryName: Record = { all: [], @@ -134,7 +136,6 @@ addActionHandler('loadStarGifts', async (global): Promise => { idsByCategoryName[starsCategory].push(gift.id); }); - global = getGlobal(); global = { ...global, starGifts: { diff --git a/src/global/actions/ui/chats.ts b/src/global/actions/ui/chats.ts index 424676d84..7ba737171 100644 --- a/src/global/actions/ui/chats.ts +++ b/src/global/actions/ui/chats.ts @@ -41,6 +41,7 @@ addActionHandler('processOpenChatOrThread', (global, actions, payload): ActionRe actions.closeStoryViewer({ tabId }); actions.closeStarsBalanceModal({ tabId }); actions.closeStarsTransactionModal({ tabId }); + actions.closeGiftInfoModal({ tabId }); if (!currentMessageList || ( currentMessageList.chatId !== chatId diff --git a/src/global/actions/ui/messages.ts b/src/global/actions/ui/messages.ts index aac841b45..7cdea102d 100644 --- a/src/global/actions/ui/messages.ts +++ b/src/global/actions/ui/messages.ts @@ -1020,6 +1020,34 @@ addActionHandler('closePaidReactionModal', (global, actions, payload): ActionRet }, tabId); }); +addActionHandler('openSuggestMessageModal', (global, actions, payload): ActionReturnType => { + const { chatId, messageId, tabId = getCurrentTabId() } = payload; + return updateTabState(global, { + suggestMessageModal: { chatId, messageId }, + }, tabId); +}); + +addActionHandler('closeSuggestMessageModal', (global, actions, payload): ActionReturnType => { + const { tabId = getCurrentTabId() } = payload || {}; + return updateTabState(global, { + suggestMessageModal: undefined, + }, tabId); +}); + +addActionHandler('openSuggestedPostApprovalModal', (global, actions, payload): ActionReturnType => { + const { chatId, messageId, tabId = getCurrentTabId() } = payload; + return updateTabState(global, { + suggestedPostApprovalModal: { chatId, messageId }, + }, tabId); +}); + +addActionHandler('closeSuggestedPostApprovalModal', (global, actions, payload): ActionReturnType => { + const { tabId = getCurrentTabId() } = payload || {}; + return updateTabState(global, { + suggestedPostApprovalModal: undefined, + }, tabId); +}); + function copyTextForMessages(global: GlobalState, chatId: string, messageIds: number[]) { const { type: messageListType, threadId } = selectCurrentMessageList(global) || {}; const lang = langProvider.oldTranslate; diff --git a/src/global/helpers/messages.ts b/src/global/helpers/messages.ts index b6ff19c7c..f8f4673ff 100644 --- a/src/global/helpers/messages.ts +++ b/src/global/helpers/messages.ts @@ -1,3 +1,5 @@ +import type { TeactNode } from '../../lib/teact/teact'; + import type { ApiAttachment, ApiMessage, @@ -10,6 +12,7 @@ import type { ApiPoll, MediaContainer, StatefulMediaContent, } from '../../api/types/messages'; import type { ThreadId } from '../../types'; +import type { LangFn } from '../../util/localization'; import type { GlobalState } from '../types'; import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types'; @@ -25,6 +28,7 @@ import { VERIFICATION_CODES_USER_ID, VIDEO_STICKER_MIME_TYPE, } from '../../config'; +import { areDeepEqual } from '../../util/areDeepEqual'; import { getCleanPeerId, isUserId } from '../../util/entities/ids'; import { areSortedArraysIntersecting, unique } from '../../util/iteratees'; import { isLocalMessageId } from '../../util/keys/messageKey'; @@ -409,3 +413,84 @@ export function splitMessagesForForwarding(messages: ApiMessage[], limit: number return result; } + +export interface SuggestedChangesInfo { + isNewText: boolean; + isNewPrice: boolean; + isNewTime: boolean; + isNewMedia: boolean; +} + +export function getSuggestedChangesInfo( + message: ApiMessage, + originalMessage?: ApiMessage, +): SuggestedChangesInfo | undefined { + if (!message.suggestedPostInfo || message.replyInfo?.type !== 'message' + || !message.replyInfo?.replyToMsgId || !originalMessage) { + return undefined; + } + + if (!originalMessage.suggestedPostInfo) { + return undefined; + } + + const original = originalMessage.suggestedPostInfo; + const suggested = message.suggestedPostInfo; + + const originalContent = originalMessage.content; + const suggestedContent = message.content; + const { text: originalText, ...originalMediaContent } = originalContent; + const { text: suggestedText, ...suggestedMediaContent } = suggestedContent; + + const isNewText = !areDeepEqual(originalText, suggestedText); + const isNewMedia = !areDeepEqual(originalMediaContent, suggestedMediaContent); + + const originalPrice = original.price?.amount; + const suggestedPrice = suggested.price?.amount; + const isNewPrice = originalPrice !== suggestedPrice; + + const originalTime = original.scheduleDate; + const suggestedTime = suggested.scheduleDate; + const isNewTime = originalTime !== suggestedTime; + + if (!isNewText && !isNewPrice && !isNewTime && !isNewMedia) { + return undefined; + } + + return { + isNewText, + isNewPrice, + isNewTime, + isNewMedia, + }; +} + +export function getSuggestedChangesActionText( + lang: LangFn, + message: ApiMessage, + originalMessage?: ApiMessage, + isOutgoing?: boolean, + senderLink?: TeactNode, +): TeactNode | undefined { + const changesInfo = getSuggestedChangesInfo(message, originalMessage); + if (!changesInfo) { + return undefined; + } + + const changesParts: string[] = []; + if (changesInfo.isNewPrice) changesParts.push(lang('ActionSuggestedChangesPrice')); + if (changesInfo.isNewTime) changesParts.push(lang('ActionSuggestedChangesTime')); + if (changesInfo.isNewText) changesParts.push(lang('ActionSuggestedChangesText')); + if (changesInfo.isNewMedia) changesParts.push(lang('ActionSuggestedChangesMedia')); + + const changesText = lang.conjunction(changesParts); + + const langKey = isOutgoing ? 'ActionSuggestedChangesOutgoing' : 'ActionSuggestedChangesIncoming'; + return lang(langKey, { + changes: changesText, + user: senderLink, + }, { + withNodes: true, + withMarkdown: true, + }); +} diff --git a/src/global/types/actions.ts b/src/global/types/actions.ts index f6b055ad5..979a1bbf2 100644 --- a/src/global/types/actions.ts +++ b/src/global/types/actions.ts @@ -18,7 +18,9 @@ import type { ApiInputInvoiceStarGift, ApiInputMessageReplyInfo, ApiInputSavedStarGift, + ApiInputSuggestedPostInfo, ApiKeyboardButton, + ApiKeyboardButtonSuggestedMessage, ApiLimitTypeWithModal, ApiMessage, ApiMessageEntity, @@ -607,6 +609,21 @@ export interface ActionPayloads { description?: string; option?: string; } & WithTabId; + approveSuggestedPost: { + chatId: string; + messageId: number; + scheduleDate?: number; + } & WithTabId; + + confirmApproveSuggestedPost: { + chatId: string; + messageId: number; + } & WithTabId; + rejectSuggestedPost: { + chatId: string; + messageId: number; + rejectComment?: string; + } & WithTabId; sendMessageAction: { action: ApiSendMessageAction; chatId: string; @@ -672,6 +689,7 @@ export interface ActionPayloads { threadId?: ThreadId; isLocalOnly?: boolean; shouldKeepReply?: boolean; + shouldKeepSuggestedPost?: boolean; }; loadPinnedMessages: { chatId: string; @@ -985,6 +1003,12 @@ export interface ActionPayloads { focusLastMessage: WithTabId | undefined; updateDraftReplyInfo: Partial & WithTabId; resetDraftReplyInfo: WithTabId | undefined; + updateDraftSuggestedPostInfo: Partial & WithTabId; + resetDraftSuggestedPostInfo: WithTabId | undefined; + initDraftFromSuggestedMessage: { + chatId: string; + messageId: number; + } & WithTabId; updateInsertingPeerIdMention: { peerId?: string; } & WithTabId; @@ -1997,6 +2021,11 @@ export interface ActionPayloads { messageId: number; button: ApiKeyboardButton; } & WithTabId; + clickSuggestedMessageButton: { + chatId: string; + messageId: number; + button: ApiKeyboardButtonSuggestedMessage; + } & WithTabId; switchBotInline: { messageId?: number; @@ -2428,6 +2457,18 @@ export interface ActionPayloads { } & WithTabId; closePaidReactionModal: WithTabId | undefined; + openSuggestMessageModal: { + chatId: string; + messageId?: number; + } & WithTabId; + closeSuggestMessageModal: WithTabId | undefined; + + openSuggestedPostApprovalModal: { + chatId: string; + messageId: number; + } & WithTabId; + closeSuggestedPostApprovalModal: WithTabId | undefined; + openDeleteMessageModal: ({ chatId: string; messageIds: number[]; diff --git a/src/global/types/tabState.ts b/src/global/types/tabState.ts index 1c34d70a4..04c93b740 100644 --- a/src/global/types/tabState.ts +++ b/src/global/types/tabState.ts @@ -758,6 +758,16 @@ export type TabState = { messageId: number; }; + suggestMessageModal?: { + chatId: string; + messageId?: number; + }; + + suggestedPostApprovalModal?: { + chatId: string; + messageId: number; + }; + inviteViaLinkModal?: { missingUsers: ApiMissingInvitedUser[]; chatId: string; diff --git a/src/lib/gramjs/tl/AllTLObjects.ts b/src/lib/gramjs/tl/AllTLObjects.ts index 5124c9fb2..549cd6738 100644 --- a/src/lib/gramjs/tl/AllTLObjects.ts +++ b/src/lib/gramjs/tl/AllTLObjects.ts @@ -12,5 +12,6 @@ for (const tl of Object.values(Api)) { } } -export const LAYER = 205; +export const LAYER = 207; + export { tlobjects }; diff --git a/src/lib/gramjs/tl/api.d.ts b/src/lib/gramjs/tl/api.d.ts index 4c6613801..c3f9b4970 100644 --- a/src/lib/gramjs/tl/api.d.ts +++ b/src/lib/gramjs/tl/api.d.ts @@ -69,7 +69,7 @@ namespace Api { export type TypeChatPhoto = ChatPhotoEmpty | ChatPhoto; export type TypeMessage = MessageEmpty | Message | MessageService; export type TypeMessageMedia = MessageMediaEmpty | MessageMediaPhoto | MessageMediaGeo | MessageMediaContact | MessageMediaUnsupported | MessageMediaDocument | MessageMediaWebPage | MessageMediaVenue | MessageMediaGame | MessageMediaInvoice | MessageMediaGeoLive | MessageMediaPoll | MessageMediaDice | MessageMediaStory | MessageMediaGiveaway | MessageMediaGiveawayResults | MessageMediaPaidMedia | MessageMediaToDo; - export type TypeMessageAction = MessageActionEmpty | MessageActionChatCreate | MessageActionChatEditTitle | MessageActionChatEditPhoto | MessageActionChatDeletePhoto | MessageActionChatAddUser | MessageActionChatDeleteUser | MessageActionChatJoinedByLink | MessageActionChannelCreate | MessageActionChatMigrateTo | MessageActionChannelMigrateFrom | MessageActionPinMessage | MessageActionHistoryClear | MessageActionGameScore | MessageActionPaymentSentMe | MessageActionPaymentSent | MessageActionPhoneCall | MessageActionScreenshotTaken | MessageActionCustomAction | MessageActionBotAllowed | MessageActionSecureValuesSentMe | MessageActionSecureValuesSent | MessageActionContactSignUp | MessageActionGeoProximityReached | MessageActionGroupCall | MessageActionInviteToGroupCall | MessageActionSetMessagesTTL | MessageActionGroupCallScheduled | MessageActionSetChatTheme | MessageActionChatJoinedByRequest | MessageActionWebViewDataSentMe | MessageActionWebViewDataSent | MessageActionGiftPremium | MessageActionTopicCreate | MessageActionTopicEdit | MessageActionSuggestProfilePhoto | MessageActionRequestedPeer | MessageActionSetChatWallPaper | MessageActionGiftCode | MessageActionGiveawayLaunch | MessageActionGiveawayResults | MessageActionBoostApply | MessageActionRequestedPeerSentMe | MessageActionPaymentRefunded | MessageActionGiftStars | MessageActionPrizeStars | MessageActionStarGift | MessageActionStarGiftUnique | MessageActionPaidMessagesRefunded | MessageActionPaidMessagesPrice | MessageActionConferenceCall | MessageActionTodoCompletions | MessageActionTodoAppendTasks; + export type TypeMessageAction = MessageActionEmpty | MessageActionChatCreate | MessageActionChatEditTitle | MessageActionChatEditPhoto | MessageActionChatDeletePhoto | MessageActionChatAddUser | MessageActionChatDeleteUser | MessageActionChatJoinedByLink | MessageActionChannelCreate | MessageActionChatMigrateTo | MessageActionChannelMigrateFrom | MessageActionPinMessage | MessageActionHistoryClear | MessageActionGameScore | MessageActionPaymentSentMe | MessageActionPaymentSent | MessageActionPhoneCall | MessageActionScreenshotTaken | MessageActionCustomAction | MessageActionBotAllowed | MessageActionSecureValuesSentMe | MessageActionSecureValuesSent | MessageActionContactSignUp | MessageActionGeoProximityReached | MessageActionGroupCall | MessageActionInviteToGroupCall | MessageActionSetMessagesTTL | MessageActionGroupCallScheduled | MessageActionSetChatTheme | MessageActionChatJoinedByRequest | MessageActionWebViewDataSentMe | MessageActionWebViewDataSent | MessageActionGiftPremium | MessageActionTopicCreate | MessageActionTopicEdit | MessageActionSuggestProfilePhoto | MessageActionRequestedPeer | MessageActionSetChatWallPaper | MessageActionGiftCode | MessageActionGiveawayLaunch | MessageActionGiveawayResults | MessageActionBoostApply | MessageActionRequestedPeerSentMe | MessageActionPaymentRefunded | MessageActionGiftStars | MessageActionPrizeStars | MessageActionStarGift | MessageActionStarGiftUnique | MessageActionPaidMessagesRefunded | MessageActionPaidMessagesPrice | MessageActionConferenceCall | MessageActionTodoCompletions | MessageActionTodoAppendTasks | MessageActionSuggestedPostApproval | MessageActionSuggestedPostSuccess | MessageActionSuggestedPostRefund | MessageActionGiftTon; export type TypeDialog = Dialog | DialogFolder; export type TypePhoto = PhotoEmpty | Photo; export type TypePhotoSize = PhotoSizeEmpty | PhotoSize | PhotoCachedSize | PhotoStrippedSize | PhotoSizeProgressive | PhotoPathSize; @@ -85,7 +85,7 @@ namespace Api { export type TypeImportedContact = ImportedContact; export type TypeContactStatus = ContactStatus; export type TypeMessagesFilter = InputMessagesFilterEmpty | InputMessagesFilterPhotos | InputMessagesFilterVideo | InputMessagesFilterPhotoVideo | InputMessagesFilterDocument | InputMessagesFilterUrl | InputMessagesFilterGif | InputMessagesFilterVoice | InputMessagesFilterMusic | InputMessagesFilterChatPhotos | InputMessagesFilterPhoneCalls | InputMessagesFilterRoundVoice | InputMessagesFilterRoundVideo | InputMessagesFilterMyMentions | InputMessagesFilterGeo | InputMessagesFilterContacts | InputMessagesFilterPinned; - export type TypeUpdate = UpdateNewMessage | UpdateMessageID | UpdateDeleteMessages | UpdateUserTyping | UpdateChatUserTyping | UpdateChatParticipants | UpdateUserStatus | UpdateUserName | UpdateNewAuthorization | UpdateNewEncryptedMessage | UpdateEncryptedChatTyping | UpdateEncryption | UpdateEncryptedMessagesRead | UpdateChatParticipantAdd | UpdateChatParticipantDelete | UpdateDcOptions | UpdateNotifySettings | UpdateServiceNotification | UpdatePrivacy | UpdateUserPhone | UpdateReadHistoryInbox | UpdateReadHistoryOutbox | UpdateWebPage | UpdateReadMessagesContents | UpdateChannelTooLong | UpdateChannel | UpdateNewChannelMessage | UpdateReadChannelInbox | UpdateDeleteChannelMessages | UpdateChannelMessageViews | UpdateChatParticipantAdmin | UpdateNewStickerSet | UpdateStickerSetsOrder | UpdateStickerSets | UpdateSavedGifs | UpdateBotInlineQuery | UpdateBotInlineSend | UpdateEditChannelMessage | UpdateBotCallbackQuery | UpdateEditMessage | UpdateInlineBotCallbackQuery | UpdateReadChannelOutbox | UpdateDraftMessage | UpdateReadFeaturedStickers | UpdateRecentStickers | UpdateConfig | UpdatePtsChanged | UpdateChannelWebPage | UpdateDialogPinned | UpdatePinnedDialogs | UpdateBotWebhookJSON | UpdateBotWebhookJSONQuery | UpdateBotShippingQuery | UpdateBotPrecheckoutQuery | UpdatePhoneCall | UpdateLangPackTooLong | UpdateLangPack | UpdateFavedStickers | UpdateChannelReadMessagesContents | UpdateContactsReset | UpdateChannelAvailableMessages | UpdateDialogUnreadMark | UpdateMessagePoll | UpdateChatDefaultBannedRights | UpdateFolderPeers | UpdatePeerSettings | UpdatePeerLocated | UpdateNewScheduledMessage | UpdateDeleteScheduledMessages | UpdateTheme | UpdateGeoLiveViewed | UpdateLoginToken | UpdateMessagePollVote | UpdateDialogFilter | UpdateDialogFilterOrder | UpdateDialogFilters | UpdatePhoneCallSignalingData | UpdateChannelMessageForwards | UpdateReadChannelDiscussionInbox | UpdateReadChannelDiscussionOutbox | UpdatePeerBlocked | UpdateChannelUserTyping | UpdatePinnedMessages | UpdatePinnedChannelMessages | UpdateChat | UpdateGroupCallParticipants | UpdateGroupCall | UpdatePeerHistoryTTL | UpdateChatParticipant | UpdateChannelParticipant | UpdateBotStopped | UpdateGroupCallConnection | UpdateBotCommands | UpdatePendingJoinRequests | UpdateBotChatInviteRequester | UpdateMessageReactions | UpdateAttachMenuBots | UpdateWebViewResultSent | UpdateBotMenuButton | UpdateSavedRingtones | UpdateTranscribedAudio | UpdateReadFeaturedEmojiStickers | UpdateUserEmojiStatus | UpdateRecentEmojiStatuses | UpdateRecentReactions | UpdateMoveStickerSetToTop | UpdateMessageExtendedMedia | UpdateChannelPinnedTopic | UpdateChannelPinnedTopics | UpdateUser | UpdateAutoSaveSettings | UpdateStory | UpdateReadStories | UpdateStoryID | UpdateStoriesStealthMode | UpdateSentStoryReaction | UpdateBotChatBoost | UpdateChannelViewForumAsMessages | UpdatePeerWallpaper | UpdateBotMessageReaction | UpdateBotMessageReactions | UpdateSavedDialogPinned | UpdatePinnedSavedDialogs | UpdateSavedReactionTags | UpdateSmsJob | UpdateQuickReplies | UpdateNewQuickReply | UpdateDeleteQuickReply | UpdateQuickReplyMessage | UpdateDeleteQuickReplyMessages | UpdateBotBusinessConnect | UpdateBotNewBusinessMessage | UpdateBotEditBusinessMessage | UpdateBotDeleteBusinessMessage | UpdateNewStoryReaction | UpdateBroadcastRevenueTransactions | UpdateStarsBalance | UpdateBusinessBotCallbackQuery | UpdateStarsRevenueStatus | UpdateBotPurchasedPaidMedia | UpdatePaidReactionPrivacy | UpdateSentPhoneCode | UpdateGroupCallChainBlocks | UpdateReadMonoForumInbox | UpdateReadMonoForumOutbox; + export type TypeUpdate = UpdateNewMessage | UpdateMessageID | UpdateDeleteMessages | UpdateUserTyping | UpdateChatUserTyping | UpdateChatParticipants | UpdateUserStatus | UpdateUserName | UpdateNewAuthorization | UpdateNewEncryptedMessage | UpdateEncryptedChatTyping | UpdateEncryption | UpdateEncryptedMessagesRead | UpdateChatParticipantAdd | UpdateChatParticipantDelete | UpdateDcOptions | UpdateNotifySettings | UpdateServiceNotification | UpdatePrivacy | UpdateUserPhone | UpdateReadHistoryInbox | UpdateReadHistoryOutbox | UpdateWebPage | UpdateReadMessagesContents | UpdateChannelTooLong | UpdateChannel | UpdateNewChannelMessage | UpdateReadChannelInbox | UpdateDeleteChannelMessages | UpdateChannelMessageViews | UpdateChatParticipantAdmin | UpdateNewStickerSet | UpdateStickerSetsOrder | UpdateStickerSets | UpdateSavedGifs | UpdateBotInlineQuery | UpdateBotInlineSend | UpdateEditChannelMessage | UpdateBotCallbackQuery | UpdateEditMessage | UpdateInlineBotCallbackQuery | UpdateReadChannelOutbox | UpdateDraftMessage | UpdateReadFeaturedStickers | UpdateRecentStickers | UpdateConfig | UpdatePtsChanged | UpdateChannelWebPage | UpdateDialogPinned | UpdatePinnedDialogs | UpdateBotWebhookJSON | UpdateBotWebhookJSONQuery | UpdateBotShippingQuery | UpdateBotPrecheckoutQuery | UpdatePhoneCall | UpdateLangPackTooLong | UpdateLangPack | UpdateFavedStickers | UpdateChannelReadMessagesContents | UpdateContactsReset | UpdateChannelAvailableMessages | UpdateDialogUnreadMark | UpdateMessagePoll | UpdateChatDefaultBannedRights | UpdateFolderPeers | UpdatePeerSettings | UpdatePeerLocated | UpdateNewScheduledMessage | UpdateDeleteScheduledMessages | UpdateTheme | UpdateGeoLiveViewed | UpdateLoginToken | UpdateMessagePollVote | UpdateDialogFilter | UpdateDialogFilterOrder | UpdateDialogFilters | UpdatePhoneCallSignalingData | UpdateChannelMessageForwards | UpdateReadChannelDiscussionInbox | UpdateReadChannelDiscussionOutbox | UpdatePeerBlocked | UpdateChannelUserTyping | UpdatePinnedMessages | UpdatePinnedChannelMessages | UpdateChat | UpdateGroupCallParticipants | UpdateGroupCall | UpdatePeerHistoryTTL | UpdateChatParticipant | UpdateChannelParticipant | UpdateBotStopped | UpdateGroupCallConnection | UpdateBotCommands | UpdatePendingJoinRequests | UpdateBotChatInviteRequester | UpdateMessageReactions | UpdateAttachMenuBots | UpdateWebViewResultSent | UpdateBotMenuButton | UpdateSavedRingtones | UpdateTranscribedAudio | UpdateReadFeaturedEmojiStickers | UpdateUserEmojiStatus | UpdateRecentEmojiStatuses | UpdateRecentReactions | UpdateMoveStickerSetToTop | UpdateMessageExtendedMedia | UpdateChannelPinnedTopic | UpdateChannelPinnedTopics | UpdateUser | UpdateAutoSaveSettings | UpdateStory | UpdateReadStories | UpdateStoryID | UpdateStoriesStealthMode | UpdateSentStoryReaction | UpdateBotChatBoost | UpdateChannelViewForumAsMessages | UpdatePeerWallpaper | UpdateBotMessageReaction | UpdateBotMessageReactions | UpdateSavedDialogPinned | UpdatePinnedSavedDialogs | UpdateSavedReactionTags | UpdateSmsJob | UpdateQuickReplies | UpdateNewQuickReply | UpdateDeleteQuickReply | UpdateQuickReplyMessage | UpdateDeleteQuickReplyMessages | UpdateBotBusinessConnect | UpdateBotNewBusinessMessage | UpdateBotEditBusinessMessage | UpdateBotDeleteBusinessMessage | UpdateNewStoryReaction | UpdateStarsBalance | UpdateBusinessBotCallbackQuery | UpdateStarsRevenueStatus | UpdateBotPurchasedPaidMedia | UpdatePaidReactionPrivacy | UpdateSentPhoneCode | UpdateGroupCallChainBlocks | UpdateReadMonoForumInbox | UpdateReadMonoForumOutbox | UpdateMonoForumNoPaidException; export type TypeUpdates = UpdatesTooLong | UpdateShortMessage | UpdateShortChatMessage | UpdateShort | UpdatesCombined | Updates | UpdateShortSentMessage; export type TypeDcOption = DcOption; export type TypeConfig = Config; @@ -111,7 +111,7 @@ namespace Api { export type TypeReceivedNotifyMessage = ReceivedNotifyMessage; export type TypeExportedChatInvite = ChatInviteExported | ChatInvitePublicJoinRequests; export type TypeChatInvite = ChatInviteAlready | ChatInvite | ChatInvitePeek; - export type TypeInputStickerSet = InputStickerSetEmpty | InputStickerSetID | InputStickerSetShortName | InputStickerSetAnimatedEmoji | InputStickerSetDice | InputStickerSetAnimatedEmojiAnimations | InputStickerSetPremiumGifts | InputStickerSetEmojiGenericAnimations | InputStickerSetEmojiDefaultStatuses | InputStickerSetEmojiDefaultTopicIcons | InputStickerSetEmojiChannelDefaultStatuses; + export type TypeInputStickerSet = InputStickerSetEmpty | InputStickerSetID | InputStickerSetShortName | InputStickerSetAnimatedEmoji | InputStickerSetDice | InputStickerSetAnimatedEmojiAnimations | InputStickerSetPremiumGifts | InputStickerSetEmojiGenericAnimations | InputStickerSetEmojiDefaultStatuses | InputStickerSetEmojiDefaultTopicIcons | InputStickerSetEmojiChannelDefaultStatuses | InputStickerSetTonGifts; export type TypeStickerSet = StickerSet; export type TypeBotCommand = BotCommand; export type TypeBotInfo = BotInfo; @@ -355,10 +355,8 @@ namespace Api { export type TypeBusinessChatLink = BusinessChatLink; export type TypeRequestedPeer = RequestedPeerUser | RequestedPeerChat | RequestedPeerChannel; export type TypeSponsoredMessageReportOption = SponsoredMessageReportOption; - export type TypeBroadcastRevenueTransaction = BroadcastRevenueTransactionProceeds | BroadcastRevenueTransactionWithdrawal | BroadcastRevenueTransactionRefund; export type TypeReactionNotificationsFrom = ReactionNotificationsFromContacts | ReactionNotificationsFromAll; export type TypeReactionsNotifySettings = ReactionsNotifySettings; - export type TypeBroadcastRevenueBalances = BroadcastRevenueBalances; export type TypeAvailableEffect = AvailableEffect; export type TypeFactCheck = FactCheck; export type TypeStarsTransactionPeer = StarsTransactionPeerUnsupported | StarsTransactionPeerAppStore | StarsTransactionPeerPlayMarket | StarsTransactionPeerPremiumBot | StarsTransactionPeerFragment | StarsTransactionPeer | StarsTransactionPeerAds | StarsTransactionPeerAPI; @@ -381,7 +379,7 @@ namespace Api { export type TypeBotAppSettings = BotAppSettings; export type TypeStarRefProgram = StarRefProgram; export type TypeConnectedBotStarRef = ConnectedBotStarRef; - export type TypeStarsAmount = StarsAmount; + export type TypeStarsAmount = StarsAmount | StarsTonAmount; export type TypeBotVerifierSettings = BotVerifierSettings; export type TypeBotVerification = BotVerification; export type TypeStarGiftAttribute = StarGiftAttributeModel | StarGiftAttributePattern | StarGiftAttributeBackdrop | StarGiftAttributeOriginalDetails; @@ -398,6 +396,7 @@ namespace Api { export type TypeTodoItem = TodoItem; export type TypeTodoList = TodoList; export type TypeTodoCompletion = TodoCompletion; + export type TypeSuggestedPost = SuggestedPost; export type TypeResPQ = ResPQ; export type TypeP_Q_inner_data = PQInnerData | PQInnerDataDc | PQInnerDataTemp | PQInnerDataTempDc; export type TypeServer_DH_Params = ServerDHParamsFail | ServerDHParamsOk; @@ -631,9 +630,6 @@ namespace Api { export type TypeMessageStats = stats.MessageStats; export type TypeStoryStats = stats.StoryStats; export type TypePublicForwards = stats.PublicForwards; - export type TypeBroadcastRevenueStats = stats.BroadcastRevenueStats; - export type TypeBroadcastRevenueWithdrawalUrl = stats.BroadcastRevenueWithdrawalUrl; - export type TypeBroadcastRevenueTransactions = stats.BroadcastRevenueTransactions; } export namespace stickers { @@ -2140,6 +2136,8 @@ namespace Api { // flags2: Api.Type; offline?: true; videoProcessingPending?: true; + paidSuggestedPostStars?: true; + paidSuggestedPostTon?: true; id: int; fromId?: Api.TypePeer; fromBoostsApplied?: int; @@ -2168,6 +2166,7 @@ namespace Api { factcheck?: Api.TypeFactCheck; reportDeliveryUntilDate?: int; paidMessageStars?: long; + suggestedPost?: Api.TypeSuggestedPost; }> { // flags: Api.Type; out?: true; @@ -2184,6 +2183,8 @@ namespace Api { // flags2: Api.Type; offline?: true; videoProcessingPending?: true; + paidSuggestedPostStars?: true; + paidSuggestedPostTon?: true; id: int; fromId?: Api.TypePeer; fromBoostsApplied?: int; @@ -2212,7 +2213,8 @@ namespace Api { factcheck?: Api.TypeFactCheck; reportDeliveryUntilDate?: int; paidMessageStars?: long; - CONSTRUCTOR_ID: 3938245965; + suggestedPost?: Api.TypeSuggestedPost; + CONSTRUCTOR_ID: 2551566024; SUBCLASS_OF_ID: 2030045667; className: 'Message'; @@ -3327,6 +3329,68 @@ namespace Api { static fromReader(reader: Reader): MessageActionTodoAppendTasks; } + export class MessageActionSuggestedPostApproval extends VirtualClass<{ + // flags: Api.Type; + rejected?: true; + balanceTooLow?: true; + rejectComment?: string; + scheduleDate?: int; + price?: Api.TypeStarsAmount; + } | void> { + // flags: Api.Type; + rejected?: true; + balanceTooLow?: true; + rejectComment?: string; + scheduleDate?: int; + price?: Api.TypeStarsAmount; + CONSTRUCTOR_ID: 4000978326; + SUBCLASS_OF_ID: 2256589094; + className: 'MessageActionSuggestedPostApproval'; + + static fromReader(reader: Reader): MessageActionSuggestedPostApproval; + } + export class MessageActionSuggestedPostSuccess extends VirtualClass<{ + price: Api.TypeStarsAmount; + }> { + price: Api.TypeStarsAmount; + CONSTRUCTOR_ID: 2514341737; + SUBCLASS_OF_ID: 2256589094; + className: 'MessageActionSuggestedPostSuccess'; + + static fromReader(reader: Reader): MessageActionSuggestedPostSuccess; + } + export class MessageActionSuggestedPostRefund extends VirtualClass<{ + // flags: Api.Type; + payerInitiated?: true; + } | void> { + // flags: Api.Type; + payerInitiated?: true; + CONSTRUCTOR_ID: 1777932024; + SUBCLASS_OF_ID: 2256589094; + className: 'MessageActionSuggestedPostRefund'; + + static fromReader(reader: Reader): MessageActionSuggestedPostRefund; + } + export class MessageActionGiftTon extends VirtualClass<{ + // flags: Api.Type; + currency: string; + amount: long; + cryptoCurrency: string; + cryptoAmount: long; + transactionId?: string; + }> { + // flags: Api.Type; + currency: string; + amount: long; + cryptoCurrency: string; + cryptoAmount: long; + transactionId?: string; + CONSTRUCTOR_ID: 2829305497; + SUBCLASS_OF_ID: 2256589094; + className: 'MessageActionGiftTon'; + + static fromReader(reader: Reader): MessageActionGiftTon; + } export class Dialog extends VirtualClass<{ // flags: Api.Type; pinned?: true; @@ -5927,18 +5991,6 @@ namespace Api { static fromReader(reader: Reader): UpdateNewStoryReaction; } - export class UpdateBroadcastRevenueTransactions extends VirtualClass<{ - peer: Api.TypePeer; - balances: Api.TypeBroadcastRevenueBalances; - }> { - peer: Api.TypePeer; - balances: Api.TypeBroadcastRevenueBalances; - CONSTRUCTOR_ID: 3755565557; - SUBCLASS_OF_ID: 2676568142; - className: 'UpdateBroadcastRevenueTransactions'; - - static fromReader(reader: Reader): UpdateBroadcastRevenueTransactions; - } export class UpdateStarsBalance extends VirtualClass<{ balance: Api.TypeStarsAmount; }> { @@ -6063,6 +6115,22 @@ namespace Api { static fromReader(reader: Reader): UpdateReadMonoForumOutbox; } + export class UpdateMonoForumNoPaidException extends VirtualClass<{ + // flags: Api.Type; + exception?: true; + channelId: long; + savedPeerId: Api.TypePeer; + }> { + // flags: Api.Type; + exception?: true; + channelId: long; + savedPeerId: Api.TypePeer; + CONSTRUCTOR_ID: 2676042504; + SUBCLASS_OF_ID: 2676568142; + className: 'UpdateMonoForumNoPaidException'; + + static fromReader(reader: Reader): UpdateMonoForumNoPaidException; + } export class UpdatesTooLong extends VirtualClass { CONSTRUCTOR_ID: 3809980286; SUBCLASS_OF_ID: 2331323052; @@ -7709,6 +7777,13 @@ namespace Api { static fromReader(reader: Reader): InputStickerSetEmojiChannelDefaultStatuses; } + export class InputStickerSetTonGifts extends VirtualClass { + CONSTRUCTOR_ID: 485912992; + SUBCLASS_OF_ID: 1034127786; + className: 'InputStickerSetTonGifts'; + + static fromReader(reader: Reader): InputStickerSetTonGifts; + } export class StickerSet extends VirtualClass<{ // flags: Api.Type; archived?: true; @@ -9281,6 +9356,7 @@ namespace Api { media?: Api.TypeInputMedia; date: int; effect?: long; + suggestedPost?: Api.TypeSuggestedPost; }> { // flags: Api.Type; noWebpage?: true; @@ -9291,7 +9367,8 @@ namespace Api { media?: Api.TypeInputMedia; date: int; effect?: long; - CONSTRUCTOR_ID: 761606687; + suggestedPost?: Api.TypeSuggestedPost; + CONSTRUCTOR_ID: 2531960299; SUBCLASS_OF_ID: 869564229; className: 'DraftMessage'; @@ -12374,6 +12451,7 @@ namespace Api { postStories?: true; editStories?: true; deleteStories?: true; + manageDirectMessages?: true; } | void> { // flags: Api.Type; changeInfo?: true; @@ -12391,6 +12469,7 @@ namespace Api { postStories?: true; editStories?: true; deleteStories?: true; + manageDirectMessages?: true; CONSTRUCTOR_ID: 1605510357; SUBCLASS_OF_ID: 2252195780; className: 'ChatAdminRights'; @@ -16243,58 +16322,6 @@ namespace Api { static fromReader(reader: Reader): SponsoredMessageReportOption; } - export class BroadcastRevenueTransactionProceeds extends VirtualClass<{ - amount: long; - fromDate: int; - toDate: int; - }> { - amount: long; - fromDate: int; - toDate: int; - CONSTRUCTOR_ID: 1434332356; - SUBCLASS_OF_ID: 1962590909; - className: 'BroadcastRevenueTransactionProceeds'; - - static fromReader(reader: Reader): BroadcastRevenueTransactionProceeds; - } - export class BroadcastRevenueTransactionWithdrawal extends VirtualClass<{ - // flags: Api.Type; - pending?: true; - failed?: true; - amount: long; - date: int; - provider: string; - transactionDate?: int; - transactionUrl?: string; - }> { - // flags: Api.Type; - pending?: true; - failed?: true; - amount: long; - date: int; - provider: string; - transactionDate?: int; - transactionUrl?: string; - CONSTRUCTOR_ID: 1515784568; - SUBCLASS_OF_ID: 1962590909; - className: 'BroadcastRevenueTransactionWithdrawal'; - - static fromReader(reader: Reader): BroadcastRevenueTransactionWithdrawal; - } - export class BroadcastRevenueTransactionRefund extends VirtualClass<{ - amount: long; - date: int; - provider: string; - }> { - amount: long; - date: int; - provider: string; - CONSTRUCTOR_ID: 1121127726; - SUBCLASS_OF_ID: 1962590909; - className: 'BroadcastRevenueTransactionRefund'; - - static fromReader(reader: Reader): BroadcastRevenueTransactionRefund; - } export class ReactionNotificationsFromContacts extends VirtualClass { CONSTRUCTOR_ID: 3133384218; SUBCLASS_OF_ID: 878672192; @@ -16327,24 +16354,6 @@ namespace Api { static fromReader(reader: Reader): ReactionsNotifySettings; } - export class BroadcastRevenueBalances extends VirtualClass<{ - // flags: Api.Type; - withdrawalEnabled?: true; - currentBalance: long; - availableBalance: long; - overallRevenue: long; - }> { - // flags: Api.Type; - withdrawalEnabled?: true; - currentBalance: long; - availableBalance: long; - overallRevenue: long; - CONSTRUCTOR_ID: 3288297959; - SUBCLASS_OF_ID: 365072370; - className: 'BroadcastRevenueBalances'; - - static fromReader(reader: Reader): BroadcastRevenueBalances; - } export class AvailableEffect extends VirtualClass<{ // flags: Api.Type; premiumRequired?: true; @@ -16475,7 +16484,7 @@ namespace Api { businessTransfer?: true; stargiftResale?: true; id: string; - stars: Api.TypeStarsAmount; + amount: Api.TypeStarsAmount; date: int; peer: Api.TypeStarsTransactionPeer; title?: string; @@ -16495,6 +16504,8 @@ namespace Api { starrefAmount?: Api.TypeStarsAmount; paidMessages?: int; premiumGiftMonths?: int; + adsProceedsFromDate?: int; + adsProceedsToDate?: int; }> { // flags: Api.Type; refund?: true; @@ -16506,7 +16517,7 @@ namespace Api { businessTransfer?: true; stargiftResale?: true; id: string; - stars: Api.TypeStarsAmount; + amount: Api.TypeStarsAmount; date: int; peer: Api.TypeStarsTransactionPeer; title?: string; @@ -16526,7 +16537,9 @@ namespace Api { starrefAmount?: Api.TypeStarsAmount; paidMessages?: int; premiumGiftMonths?: int; - CONSTRUCTOR_ID: 2745162058; + adsProceedsFromDate?: int; + adsProceedsToDate?: int; + CONSTRUCTOR_ID: 325426864; SUBCLASS_OF_ID: 2257078130; className: 'StarsTransaction'; @@ -16753,6 +16766,7 @@ namespace Api { upgradeStars?: long; resellMinStars?: long; title?: string; + releasedBy?: Api.TypePeer; }> { // flags: Api.Type; limited?: true; @@ -16770,7 +16784,8 @@ namespace Api { upgradeStars?: long; resellMinStars?: long; title?: string; - CONSTRUCTOR_ID: 3324693032; + releasedBy?: Api.TypePeer; + CONSTRUCTOR_ID: 2139438098; SUBCLASS_OF_ID: 3273414923; className: 'StarGift'; @@ -16790,6 +16805,7 @@ namespace Api { availabilityTotal: int; giftAddress?: string; resellStars?: long; + releasedBy?: Api.TypePeer; }> { // flags: Api.Type; id: long; @@ -16804,7 +16820,8 @@ namespace Api { availabilityTotal: int; giftAddress?: string; resellStars?: long; - CONSTRUCTOR_ID: 1678891913; + releasedBy?: Api.TypePeer; + CONSTRUCTOR_ID: 4130830510; SUBCLASS_OF_ID: 3273414923; className: 'StarGiftUnique'; @@ -16933,6 +16950,16 @@ namespace Api { static fromReader(reader: Reader): StarsAmount; } + export class StarsTonAmount extends VirtualClass<{ + amount: long; + }> { + amount: long; + CONSTRUCTOR_ID: 1957618656; + SUBCLASS_OF_ID: 895169088; + className: 'StarsTonAmount'; + + static fromReader(reader: Reader): StarsTonAmount; + } export class BotVerifierSettings extends VirtualClass<{ // flags: Api.Type; canModifyCustomDescription?: true; @@ -17333,6 +17360,24 @@ namespace Api { static fromReader(reader: Reader): TodoCompletion; } + export class SuggestedPost extends VirtualClass<{ + // flags: Api.Type; + accepted?: true; + rejected?: true; + price?: Api.TypeStarsAmount; + scheduleDate?: int; + } | void> { + // flags: Api.Type; + accepted?: true; + rejected?: true; + price?: Api.TypeStarsAmount; + scheduleDate?: int; + CONSTRUCTOR_ID: 244201445; + SUBCLASS_OF_ID: 2389869056; + className: 'SuggestedPost'; + + static fromReader(reader: Reader): SuggestedPost; + } export class ResPQ extends VirtualClass<{ nonce: int128; serverNonce: int128; @@ -21168,14 +21213,18 @@ namespace Api { static fromReader(reader: Reader): StarsStatus; } export class StarsRevenueStats extends VirtualClass<{ + // flags: Api.Type; + topHoursGraph?: Api.TypeStatsGraph; revenueGraph: Api.TypeStatsGraph; status: Api.TypeStarsRevenueStatus; usdRate: double; }> { + // flags: Api.Type; + topHoursGraph?: Api.TypeStatsGraph; revenueGraph: Api.TypeStatsGraph; status: Api.TypeStarsRevenueStatus; usdRate: double; - CONSTRUCTOR_ID: 3375085371; + CONSTRUCTOR_ID: 1814066038; SUBCLASS_OF_ID: 2772915699; className: 'StarsRevenueStats'; @@ -21211,10 +21260,14 @@ namespace Api { export class StarGifts extends VirtualClass<{ hash: int; gifts: Api.TypeStarGift[]; + chats: Api.TypeChat[]; + users: Api.TypeUser[]; }> { hash: int; gifts: Api.TypeStarGift[]; - CONSTRUCTOR_ID: 2417396202; + chats: Api.TypeChat[]; + users: Api.TypeUser[]; + CONSTRUCTOR_ID: 785918357; SUBCLASS_OF_ID: 1635309988; className: 'StarGifts'; @@ -21572,44 +21625,6 @@ namespace Api { static fromReader(reader: Reader): PublicForwards; } - export class BroadcastRevenueStats extends VirtualClass<{ - topHoursGraph: Api.TypeStatsGraph; - revenueGraph: Api.TypeStatsGraph; - balances: Api.TypeBroadcastRevenueBalances; - usdRate: double; - }> { - topHoursGraph: Api.TypeStatsGraph; - revenueGraph: Api.TypeStatsGraph; - balances: Api.TypeBroadcastRevenueBalances; - usdRate: double; - CONSTRUCTOR_ID: 1409802903; - SUBCLASS_OF_ID: 753807480; - className: 'BroadcastRevenueStats'; - - static fromReader(reader: Reader): BroadcastRevenueStats; - } - export class BroadcastRevenueWithdrawalUrl extends VirtualClass<{ - url: string; - }> { - url: string; - CONSTRUCTOR_ID: 3966080823; - SUBCLASS_OF_ID: 3512518885; - className: 'BroadcastRevenueWithdrawalUrl'; - - static fromReader(reader: Reader): BroadcastRevenueWithdrawalUrl; - } - export class BroadcastRevenueTransactions extends VirtualClass<{ - count: int; - transactions: Api.TypeBroadcastRevenueTransaction[]; - }> { - count: int; - transactions: Api.TypeBroadcastRevenueTransaction[]; - CONSTRUCTOR_ID: 2266334310; - SUBCLASS_OF_ID: 108456469; - className: 'BroadcastRevenueTransactions'; - - static fromReader(reader: Reader): BroadcastRevenueTransactions; - } } export namespace stickers { @@ -23520,6 +23535,7 @@ namespace Api { quickReplyShortcut?: Api.TypeInputQuickReplyShortcut; effect?: long; allowPaidStars?: long; + suggestedPost?: Api.TypeSuggestedPost; }, Api.TypeUpdates> { // flags: Api.Type; noWebpage?: true; @@ -23541,6 +23557,7 @@ namespace Api { quickReplyShortcut?: Api.TypeInputQuickReplyShortcut; effect?: long; allowPaidStars?: long; + suggestedPost?: Api.TypeSuggestedPost; } export class SendMedia extends Request<{ // flags: Api.Type; @@ -23563,6 +23580,7 @@ namespace Api { quickReplyShortcut?: Api.TypeInputQuickReplyShortcut; effect?: long; allowPaidStars?: long; + suggestedPost?: Api.TypeSuggestedPost; }, Api.TypeUpdates> { // flags: Api.Type; silent?: true; @@ -23584,6 +23602,7 @@ namespace Api { quickReplyShortcut?: Api.TypeInputQuickReplyShortcut; effect?: long; allowPaidStars?: long; + suggestedPost?: Api.TypeSuggestedPost; } export class ForwardMessages extends Request<{ // flags: Api.Type; @@ -23605,6 +23624,7 @@ namespace Api { quickReplyShortcut?: Api.TypeInputQuickReplyShortcut; videoTimestamp?: int; allowPaidStars?: long; + suggestedPost?: Api.TypeSuggestedPost; }, Api.TypeUpdates> { // flags: Api.Type; silent?: true; @@ -23625,6 +23645,7 @@ namespace Api { quickReplyShortcut?: Api.TypeInputQuickReplyShortcut; videoTimestamp?: int; allowPaidStars?: long; + suggestedPost?: Api.TypeSuggestedPost; } export class ReportSpam extends Request<{ peer: Api.TypeInputPeer; @@ -24129,6 +24150,7 @@ namespace Api { entities?: Api.TypeMessageEntity[]; media?: Api.TypeInputMedia; effect?: long; + suggestedPost?: Api.TypeSuggestedPost; }, Bool> { // flags: Api.Type; noWebpage?: true; @@ -24139,6 +24161,7 @@ namespace Api { entities?: Api.TypeMessageEntity[]; media?: Api.TypeInputMedia; effect?: long; + suggestedPost?: Api.TypeSuggestedPost; } export class GetAllDrafts extends Request {} export class GetFeaturedStickers extends Request<{ @@ -25679,6 +25702,21 @@ namespace Api { msgId: int; list: Api.TypeTodoItem[]; } + export class ToggleSuggestedPostApproval extends Request<{ + // flags: Api.Type; + reject?: true; + peer: Api.TypeInputPeer; + msgId: int; + scheduleDate?: int; + rejectComment?: string; + }, Api.TypeUpdates> { + // flags: Api.Type; + reject?: true; + peer: Api.TypeInputPeer; + msgId: int; + scheduleDate?: int; + rejectComment?: string; + } } export namespace updates { @@ -26852,8 +26890,12 @@ namespace Api { } export class GetStarsTopupOptions extends Request {} export class GetStarsStatus extends Request<{ + // flags: Api.Type; + ton?: true; peer: Api.TypeInputPeer; }, payments.TypeStarsStatus> { + // flags: Api.Type; + ton?: true; peer: Api.TypeInputPeer; } export class GetStarsTransactions extends Request<{ @@ -26861,6 +26903,7 @@ namespace Api { inbound?: true; outbound?: true; ascending?: true; + ton?: true; subscriptionId?: string; peer: Api.TypeInputPeer; offset: string; @@ -26870,6 +26913,7 @@ namespace Api { inbound?: true; outbound?: true; ascending?: true; + ton?: true; subscriptionId?: string; peer: Api.TypeInputPeer; offset: string; @@ -26892,19 +26936,25 @@ namespace Api { export class GetStarsRevenueStats extends Request<{ // flags: Api.Type; dark?: true; + ton?: true; peer: Api.TypeInputPeer; }, payments.TypeStarsRevenueStats> { // flags: Api.Type; dark?: true; + ton?: true; peer: Api.TypeInputPeer; } export class GetStarsRevenueWithdrawalUrl extends Request<{ + // flags: Api.Type; + ton?: true; peer: Api.TypeInputPeer; - stars: long; + amount?: long; password: Api.TypeInputCheckPasswordSRP; }, payments.TypeStarsRevenueWithdrawalUrl> { + // flags: Api.Type; + ton?: true; peer: Api.TypeInputPeer; - stars: long; + amount?: long; password: Api.TypeInputCheckPasswordSRP; } export class GetStarsRevenueAdsAccountUrl extends Request<{ @@ -26913,9 +26963,13 @@ namespace Api { peer: Api.TypeInputPeer; } export class GetStarsTransactionsByID extends Request<{ + // flags: Api.Type; + ton?: true; peer: Api.TypeInputPeer; id: Api.TypeInputStarsTransaction[]; }, payments.TypeStarsStatus> { + // flags: Api.Type; + ton?: true; peer: Api.TypeInputPeer; id: Api.TypeInputStarsTransaction[]; } @@ -27721,31 +27775,6 @@ namespace Api { offset: string; limit: int; } - export class GetBroadcastRevenueStats extends Request<{ - // flags: Api.Type; - dark?: true; - peer: Api.TypeInputPeer; - }, stats.TypeBroadcastRevenueStats> { - // flags: Api.Type; - dark?: true; - peer: Api.TypeInputPeer; - } - export class GetBroadcastRevenueWithdrawalUrl extends Request<{ - peer: Api.TypeInputPeer; - password: Api.TypeInputCheckPasswordSRP; - }, stats.TypeBroadcastRevenueWithdrawalUrl> { - peer: Api.TypeInputPeer; - password: Api.TypeInputCheckPasswordSRP; - } - export class GetBroadcastRevenueTransactions extends Request<{ - peer: Api.TypeInputPeer; - offset: int; - limit: int; - }, stats.TypeBroadcastRevenueTransactions> { - peer: Api.TypeInputPeer; - offset: int; - limit: int; - } } export namespace chatlists { @@ -28160,7 +28189,7 @@ namespace Api { | account.RegisterDevice | account.UnregisterDevice | account.UpdateNotifySettings | account.GetNotifySettings | account.ResetNotifySettings | account.UpdateProfile | account.UpdateStatus | account.GetWallPapers | account.ReportPeer | account.CheckUsername | account.UpdateUsername | account.GetPrivacy | account.SetPrivacy | account.DeleteAccount | account.GetAccountTTL | account.SetAccountTTL | account.SendChangePhoneCode | account.ChangePhone | account.UpdateDeviceLocked | account.GetAuthorizations | account.ResetAuthorization | account.GetPassword | account.GetPasswordSettings | account.UpdatePasswordSettings | account.SendConfirmPhoneCode | account.ConfirmPhone | account.GetTmpPassword | account.GetWebAuthorizations | account.ResetWebAuthorization | account.ResetWebAuthorizations | account.GetAllSecureValues | account.GetSecureValue | account.SaveSecureValue | account.DeleteSecureValue | account.GetAuthorizationForm | account.AcceptAuthorization | account.SendVerifyPhoneCode | account.VerifyPhone | account.SendVerifyEmailCode | account.VerifyEmail | account.InitTakeoutSession | account.FinishTakeoutSession | account.ConfirmPasswordEmail | account.ResendPasswordEmail | account.CancelPasswordEmail | account.GetContactSignUpNotification | account.SetContactSignUpNotification | account.GetNotifyExceptions | account.GetWallPaper | account.UploadWallPaper | account.SaveWallPaper | account.InstallWallPaper | account.ResetWallPapers | account.GetAutoDownloadSettings | account.SaveAutoDownloadSettings | account.UploadTheme | account.CreateTheme | account.UpdateTheme | account.SaveTheme | account.InstallTheme | account.GetTheme | account.GetThemes | account.SetContentSettings | account.GetContentSettings | account.GetMultiWallPapers | account.GetGlobalPrivacySettings | account.SetGlobalPrivacySettings | account.ReportProfilePhoto | account.ResetPassword | account.DeclinePasswordReset | account.GetChatThemes | account.SetAuthorizationTTL | account.ChangeAuthorizationSettings | account.GetSavedRingtones | account.SaveRingtone | account.UploadRingtone | account.UpdateEmojiStatus | account.GetDefaultEmojiStatuses | account.GetRecentEmojiStatuses | account.ClearRecentEmojiStatuses | account.ReorderUsernames | account.ToggleUsername | account.GetDefaultProfilePhotoEmojis | account.GetDefaultGroupPhotoEmojis | account.GetAutoSaveSettings | account.SaveAutoSaveSettings | account.DeleteAutoSaveExceptions | account.InvalidateSignInCodes | account.UpdateColor | account.GetDefaultBackgroundEmojis | account.GetChannelDefaultEmojiStatuses | account.GetChannelRestrictedStatusEmojis | account.UpdateBusinessWorkHours | account.UpdateBusinessLocation | account.UpdateBusinessGreetingMessage | account.UpdateBusinessAwayMessage | account.UpdateConnectedBot | account.GetConnectedBots | account.GetBotBusinessConnection | account.UpdateBusinessIntro | account.ToggleConnectedBotPaused | account.DisablePeerConnectedBot | account.UpdateBirthday | account.CreateBusinessChatLink | account.EditBusinessChatLink | account.DeleteBusinessChatLink | account.GetBusinessChatLinks | account.ResolveBusinessChatLink | account.UpdatePersonalChannel | account.ToggleSponsoredMessages | account.GetReactionsNotifySettings | account.SetReactionsNotifySettings | account.GetCollectibleEmojiStatuses | account.GetPaidMessagesRevenue | account.ToggleNoPaidMessagesException | users.GetUsers | users.GetFullUser | users.SetSecureValueErrors | users.GetRequirementsToContact | contacts.GetContactIDs | contacts.GetStatuses | contacts.GetContacts | contacts.ImportContacts | contacts.DeleteContacts | contacts.DeleteByPhones | contacts.Block | contacts.Unblock | contacts.GetBlocked | contacts.Search | contacts.ResolveUsername | contacts.GetTopPeers | contacts.ResetTopPeerRating | contacts.ResetSaved | contacts.GetSaved | contacts.ToggleTopPeers | contacts.AddContact | contacts.AcceptContact | contacts.GetLocated | contacts.BlockFromReplies | contacts.ResolvePhone | contacts.ExportContactToken | contacts.ImportContactToken | contacts.EditCloseFriends | contacts.SetBlocked | contacts.GetBirthdays | contacts.GetSponsoredPeers - | messages.GetMessages | messages.GetDialogs | messages.GetHistory | messages.Search | messages.ReadHistory | messages.DeleteHistory | messages.DeleteMessages | messages.ReceivedMessages | messages.SetTyping | messages.SendMessage | messages.SendMedia | messages.ForwardMessages | messages.ReportSpam | messages.GetPeerSettings | messages.Report | messages.GetChats | messages.GetFullChat | messages.EditChatTitle | messages.EditChatPhoto | messages.AddChatUser | messages.DeleteChatUser | messages.CreateChat | messages.GetDhConfig | messages.RequestEncryption | messages.AcceptEncryption | messages.DiscardEncryption | messages.SetEncryptedTyping | messages.ReadEncryptedHistory | messages.SendEncrypted | messages.SendEncryptedFile | messages.SendEncryptedService | messages.ReceivedQueue | messages.ReportEncryptedSpam | messages.ReadMessageContents | messages.GetStickers | messages.GetAllStickers | messages.GetWebPagePreview | messages.ExportChatInvite | messages.CheckChatInvite | messages.ImportChatInvite | messages.GetStickerSet | messages.InstallStickerSet | messages.UninstallStickerSet | messages.StartBot | messages.GetMessagesViews | messages.EditChatAdmin | messages.MigrateChat | messages.SearchGlobal | messages.ReorderStickerSets | messages.GetDocumentByHash | messages.GetSavedGifs | messages.SaveGif | messages.GetInlineBotResults | messages.SetInlineBotResults | messages.SendInlineBotResult | messages.GetMessageEditData | messages.EditMessage | messages.EditInlineBotMessage | messages.GetBotCallbackAnswer | messages.SetBotCallbackAnswer | messages.GetPeerDialogs | messages.SaveDraft | messages.GetAllDrafts | messages.GetFeaturedStickers | messages.ReadFeaturedStickers | messages.GetRecentStickers | messages.SaveRecentSticker | messages.ClearRecentStickers | messages.GetArchivedStickers | messages.GetMaskStickers | messages.GetAttachedStickers | messages.SetGameScore | messages.SetInlineGameScore | messages.GetGameHighScores | messages.GetInlineGameHighScores | messages.GetCommonChats | messages.GetWebPage | messages.ToggleDialogPin | messages.ReorderPinnedDialogs | messages.GetPinnedDialogs | messages.SetBotShippingResults | messages.SetBotPrecheckoutResults | messages.UploadMedia | messages.SendScreenshotNotification | messages.GetFavedStickers | messages.FaveSticker | messages.GetUnreadMentions | messages.ReadMentions | messages.GetRecentLocations | messages.SendMultiMedia | messages.UploadEncryptedFile | messages.SearchStickerSets | messages.GetSplitRanges | messages.MarkDialogUnread | messages.GetDialogUnreadMarks | messages.ClearAllDrafts | messages.UpdatePinnedMessage | messages.SendVote | messages.GetPollResults | messages.GetOnlines | messages.EditChatAbout | messages.EditChatDefaultBannedRights | messages.GetEmojiKeywords | messages.GetEmojiKeywordsDifference | messages.GetEmojiKeywordsLanguages | messages.GetEmojiURL | messages.GetSearchCounters | messages.RequestUrlAuth | messages.AcceptUrlAuth | messages.HidePeerSettingsBar | messages.GetScheduledHistory | messages.GetScheduledMessages | messages.SendScheduledMessages | messages.DeleteScheduledMessages | messages.GetPollVotes | messages.ToggleStickerSets | messages.GetDialogFilters | messages.GetSuggestedDialogFilters | messages.UpdateDialogFilter | messages.UpdateDialogFiltersOrder | messages.GetOldFeaturedStickers | messages.GetReplies | messages.GetDiscussionMessage | messages.ReadDiscussion | messages.UnpinAllMessages | messages.DeleteChat | messages.DeletePhoneCallHistory | messages.CheckHistoryImport | messages.InitHistoryImport | messages.UploadImportedMedia | messages.StartHistoryImport | messages.GetExportedChatInvites | messages.GetExportedChatInvite | messages.EditExportedChatInvite | messages.DeleteRevokedExportedChatInvites | messages.DeleteExportedChatInvite | messages.GetAdminsWithInvites | messages.GetChatInviteImporters | messages.SetHistoryTTL | messages.CheckHistoryImportPeer | messages.SetChatTheme | messages.GetMessageReadParticipants | messages.GetSearchResultsCalendar | messages.GetSearchResultsPositions | messages.HideChatJoinRequest | messages.HideAllChatJoinRequests | messages.ToggleNoForwards | messages.SaveDefaultSendAs | messages.SendReaction | messages.GetMessagesReactions | messages.GetMessageReactionsList | messages.SetChatAvailableReactions | messages.GetAvailableReactions | messages.SetDefaultReaction | messages.TranslateText | messages.GetUnreadReactions | messages.ReadReactions | messages.SearchSentMedia | messages.GetAttachMenuBots | messages.GetAttachMenuBot | messages.ToggleBotInAttachMenu | messages.RequestWebView | messages.ProlongWebView | messages.RequestSimpleWebView | messages.SendWebViewResultMessage | messages.SendWebViewData | messages.TranscribeAudio | messages.RateTranscribedAudio | messages.GetCustomEmojiDocuments | messages.GetEmojiStickers | messages.GetFeaturedEmojiStickers | messages.ReportReaction | messages.GetTopReactions | messages.GetRecentReactions | messages.ClearRecentReactions | messages.GetExtendedMedia | messages.SetDefaultHistoryTTL | messages.GetDefaultHistoryTTL | messages.SendBotRequestedPeer | messages.GetEmojiGroups | messages.GetEmojiStatusGroups | messages.GetEmojiProfilePhotoGroups | messages.SearchCustomEmoji | messages.TogglePeerTranslations | messages.GetBotApp | messages.RequestAppWebView | messages.SetChatWallPaper | messages.SearchEmojiStickerSets | messages.GetSavedDialogs | messages.GetSavedHistory | messages.DeleteSavedHistory | messages.GetPinnedSavedDialogs | messages.ToggleSavedDialogPin | messages.ReorderPinnedSavedDialogs | messages.GetSavedReactionTags | messages.UpdateSavedReactionTag | messages.GetDefaultTagReactions | messages.GetOutboxReadDate | messages.GetQuickReplies | messages.ReorderQuickReplies | messages.CheckQuickReplyShortcut | messages.EditQuickReplyShortcut | messages.DeleteQuickReplyShortcut | messages.GetQuickReplyMessages | messages.SendQuickReplyMessages | messages.DeleteQuickReplyMessages | messages.ToggleDialogFilterTags | messages.GetMyStickers | messages.GetEmojiStickerGroups | messages.GetAvailableEffects | messages.EditFactCheck | messages.DeleteFactCheck | messages.GetFactCheck | messages.RequestMainWebView | messages.SendPaidReaction | messages.TogglePaidReactionPrivacy | messages.GetPaidReactionPrivacy | messages.ViewSponsoredMessage | messages.ClickSponsoredMessage | messages.ReportSponsoredMessage | messages.GetSponsoredMessages | messages.SavePreparedInlineMessage | messages.GetPreparedInlineMessage | messages.SearchStickers | messages.ReportMessagesDelivery | messages.GetSavedDialogsByID | messages.ReadSavedHistory | messages.ToggleTodoCompleted | messages.AppendTodoList + | messages.GetMessages | messages.GetDialogs | messages.GetHistory | messages.Search | messages.ReadHistory | messages.DeleteHistory | messages.DeleteMessages | messages.ReceivedMessages | messages.SetTyping | messages.SendMessage | messages.SendMedia | messages.ForwardMessages | messages.ReportSpam | messages.GetPeerSettings | messages.Report | messages.GetChats | messages.GetFullChat | messages.EditChatTitle | messages.EditChatPhoto | messages.AddChatUser | messages.DeleteChatUser | messages.CreateChat | messages.GetDhConfig | messages.RequestEncryption | messages.AcceptEncryption | messages.DiscardEncryption | messages.SetEncryptedTyping | messages.ReadEncryptedHistory | messages.SendEncrypted | messages.SendEncryptedFile | messages.SendEncryptedService | messages.ReceivedQueue | messages.ReportEncryptedSpam | messages.ReadMessageContents | messages.GetStickers | messages.GetAllStickers | messages.GetWebPagePreview | messages.ExportChatInvite | messages.CheckChatInvite | messages.ImportChatInvite | messages.GetStickerSet | messages.InstallStickerSet | messages.UninstallStickerSet | messages.StartBot | messages.GetMessagesViews | messages.EditChatAdmin | messages.MigrateChat | messages.SearchGlobal | messages.ReorderStickerSets | messages.GetDocumentByHash | messages.GetSavedGifs | messages.SaveGif | messages.GetInlineBotResults | messages.SetInlineBotResults | messages.SendInlineBotResult | messages.GetMessageEditData | messages.EditMessage | messages.EditInlineBotMessage | messages.GetBotCallbackAnswer | messages.SetBotCallbackAnswer | messages.GetPeerDialogs | messages.SaveDraft | messages.GetAllDrafts | messages.GetFeaturedStickers | messages.ReadFeaturedStickers | messages.GetRecentStickers | messages.SaveRecentSticker | messages.ClearRecentStickers | messages.GetArchivedStickers | messages.GetMaskStickers | messages.GetAttachedStickers | messages.SetGameScore | messages.SetInlineGameScore | messages.GetGameHighScores | messages.GetInlineGameHighScores | messages.GetCommonChats | messages.GetWebPage | messages.ToggleDialogPin | messages.ReorderPinnedDialogs | messages.GetPinnedDialogs | messages.SetBotShippingResults | messages.SetBotPrecheckoutResults | messages.UploadMedia | messages.SendScreenshotNotification | messages.GetFavedStickers | messages.FaveSticker | messages.GetUnreadMentions | messages.ReadMentions | messages.GetRecentLocations | messages.SendMultiMedia | messages.UploadEncryptedFile | messages.SearchStickerSets | messages.GetSplitRanges | messages.MarkDialogUnread | messages.GetDialogUnreadMarks | messages.ClearAllDrafts | messages.UpdatePinnedMessage | messages.SendVote | messages.GetPollResults | messages.GetOnlines | messages.EditChatAbout | messages.EditChatDefaultBannedRights | messages.GetEmojiKeywords | messages.GetEmojiKeywordsDifference | messages.GetEmojiKeywordsLanguages | messages.GetEmojiURL | messages.GetSearchCounters | messages.RequestUrlAuth | messages.AcceptUrlAuth | messages.HidePeerSettingsBar | messages.GetScheduledHistory | messages.GetScheduledMessages | messages.SendScheduledMessages | messages.DeleteScheduledMessages | messages.GetPollVotes | messages.ToggleStickerSets | messages.GetDialogFilters | messages.GetSuggestedDialogFilters | messages.UpdateDialogFilter | messages.UpdateDialogFiltersOrder | messages.GetOldFeaturedStickers | messages.GetReplies | messages.GetDiscussionMessage | messages.ReadDiscussion | messages.UnpinAllMessages | messages.DeleteChat | messages.DeletePhoneCallHistory | messages.CheckHistoryImport | messages.InitHistoryImport | messages.UploadImportedMedia | messages.StartHistoryImport | messages.GetExportedChatInvites | messages.GetExportedChatInvite | messages.EditExportedChatInvite | messages.DeleteRevokedExportedChatInvites | messages.DeleteExportedChatInvite | messages.GetAdminsWithInvites | messages.GetChatInviteImporters | messages.SetHistoryTTL | messages.CheckHistoryImportPeer | messages.SetChatTheme | messages.GetMessageReadParticipants | messages.GetSearchResultsCalendar | messages.GetSearchResultsPositions | messages.HideChatJoinRequest | messages.HideAllChatJoinRequests | messages.ToggleNoForwards | messages.SaveDefaultSendAs | messages.SendReaction | messages.GetMessagesReactions | messages.GetMessageReactionsList | messages.SetChatAvailableReactions | messages.GetAvailableReactions | messages.SetDefaultReaction | messages.TranslateText | messages.GetUnreadReactions | messages.ReadReactions | messages.SearchSentMedia | messages.GetAttachMenuBots | messages.GetAttachMenuBot | messages.ToggleBotInAttachMenu | messages.RequestWebView | messages.ProlongWebView | messages.RequestSimpleWebView | messages.SendWebViewResultMessage | messages.SendWebViewData | messages.TranscribeAudio | messages.RateTranscribedAudio | messages.GetCustomEmojiDocuments | messages.GetEmojiStickers | messages.GetFeaturedEmojiStickers | messages.ReportReaction | messages.GetTopReactions | messages.GetRecentReactions | messages.ClearRecentReactions | messages.GetExtendedMedia | messages.SetDefaultHistoryTTL | messages.GetDefaultHistoryTTL | messages.SendBotRequestedPeer | messages.GetEmojiGroups | messages.GetEmojiStatusGroups | messages.GetEmojiProfilePhotoGroups | messages.SearchCustomEmoji | messages.TogglePeerTranslations | messages.GetBotApp | messages.RequestAppWebView | messages.SetChatWallPaper | messages.SearchEmojiStickerSets | messages.GetSavedDialogs | messages.GetSavedHistory | messages.DeleteSavedHistory | messages.GetPinnedSavedDialogs | messages.ToggleSavedDialogPin | messages.ReorderPinnedSavedDialogs | messages.GetSavedReactionTags | messages.UpdateSavedReactionTag | messages.GetDefaultTagReactions | messages.GetOutboxReadDate | messages.GetQuickReplies | messages.ReorderQuickReplies | messages.CheckQuickReplyShortcut | messages.EditQuickReplyShortcut | messages.DeleteQuickReplyShortcut | messages.GetQuickReplyMessages | messages.SendQuickReplyMessages | messages.DeleteQuickReplyMessages | messages.ToggleDialogFilterTags | messages.GetMyStickers | messages.GetEmojiStickerGroups | messages.GetAvailableEffects | messages.EditFactCheck | messages.DeleteFactCheck | messages.GetFactCheck | messages.RequestMainWebView | messages.SendPaidReaction | messages.TogglePaidReactionPrivacy | messages.GetPaidReactionPrivacy | messages.ViewSponsoredMessage | messages.ClickSponsoredMessage | messages.ReportSponsoredMessage | messages.GetSponsoredMessages | messages.SavePreparedInlineMessage | messages.GetPreparedInlineMessage | messages.SearchStickers | messages.ReportMessagesDelivery | messages.GetSavedDialogsByID | messages.ReadSavedHistory | messages.ToggleTodoCompleted | messages.AppendTodoList | messages.ToggleSuggestedPostApproval | updates.GetState | updates.GetDifference | updates.GetChannelDifference | photos.UpdateProfilePhoto | photos.UploadProfilePhoto | photos.DeletePhotos | photos.GetUserPhotos | photos.UploadContactProfilePhoto | upload.SaveFilePart | upload.GetFile | upload.SaveBigFilePart | upload.GetWebFile | upload.GetCdnFile | upload.ReuploadCdnFile | upload.GetCdnFileHashes | upload.GetFileHashes @@ -28172,7 +28201,7 @@ namespace Api { | phone.GetCallConfig | phone.RequestCall | phone.AcceptCall | phone.ConfirmCall | phone.ReceivedCall | phone.DiscardCall | phone.SetCallRating | phone.SaveCallDebug | phone.SendSignalingData | phone.CreateGroupCall | phone.JoinGroupCall | phone.LeaveGroupCall | phone.InviteToGroupCall | phone.DiscardGroupCall | phone.ToggleGroupCallSettings | phone.GetGroupCall | phone.GetGroupParticipants | phone.CheckGroupCall | phone.ToggleGroupCallRecord | phone.EditGroupCallParticipant | phone.EditGroupCallTitle | phone.GetGroupCallJoinAs | phone.ExportGroupCallInvite | phone.ToggleGroupCallStartSubscription | phone.StartScheduledGroupCall | phone.SaveDefaultGroupCallJoinAs | phone.JoinGroupCallPresentation | phone.LeaveGroupCallPresentation | phone.GetGroupCallStreamChannels | phone.GetGroupCallStreamRtmpUrl | phone.SaveCallLog | phone.CreateConferenceCall | phone.DeleteConferenceCallParticipants | phone.SendConferenceCallBroadcast | phone.InviteConferenceCallParticipant | phone.DeclineConferenceCallInvite | phone.GetGroupCallChainBlocks | langpack.GetLangPack | langpack.GetStrings | langpack.GetDifference | langpack.GetLanguages | langpack.GetLanguage | folders.EditPeerFolders - | stats.GetBroadcastStats | stats.LoadAsyncGraph | stats.GetMegagroupStats | stats.GetMessagePublicForwards | stats.GetMessageStats | stats.GetStoryStats | stats.GetStoryPublicForwards | stats.GetBroadcastRevenueStats | stats.GetBroadcastRevenueWithdrawalUrl | stats.GetBroadcastRevenueTransactions + | stats.GetBroadcastStats | stats.LoadAsyncGraph | stats.GetMegagroupStats | stats.GetMessagePublicForwards | stats.GetMessageStats | stats.GetStoryStats | stats.GetStoryPublicForwards | chatlists.ExportChatlistInvite | chatlists.DeleteExportedInvite | chatlists.EditExportedInvite | chatlists.GetExportedInvites | chatlists.CheckChatlistInvite | chatlists.JoinChatlistInvite | chatlists.GetChatlistUpdates | chatlists.JoinChatlistUpdates | chatlists.HideChatlistUpdates | chatlists.GetLeaveChatlistSuggestions | chatlists.LeaveChatlist | stories.CanSendStory | stories.SendStory | stories.EditStory | stories.DeleteStories | stories.TogglePinned | stories.GetAllStories | stories.GetPinnedStories | stories.GetStoriesArchive | stories.GetStoriesByID | stories.ToggleAllStoriesHidden | stories.ReadStories | stories.IncrementStoryViews | stories.GetStoryViewsList | stories.GetStoriesViews | stories.ExportStoryLink | stories.Report | stories.ActivateStealthMode | stories.SendReaction | stories.GetPeerStories | stories.GetAllReadPeerStories | stories.GetPeerMaxIDs | stories.GetChatsToSend | stories.TogglePeerStoriesHidden | stories.GetStoryReactionsList | stories.TogglePinnedToTop | stories.SearchPosts | premium.GetBoostsList | premium.GetMyBoosts | premium.ApplyBoost | premium.GetBoostsStatus | premium.GetUserBoosts diff --git a/src/lib/gramjs/tl/apiTl.ts b/src/lib/gramjs/tl/apiTl.ts index 93cfcf53d..5812e7fd5 100644 --- a/src/lib/gramjs/tl/apiTl.ts +++ b/src/lib/gramjs/tl/apiTl.ts @@ -93,7 +93,7 @@ chatParticipants#3cbc93f8 chat_id:long participants:Vector vers chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto; messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; -message#eabcdd4d flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true flags2:# offline:flags2.1?true video_processing_pending:flags2.4?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long via_business_bot_id:flags2.0?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int effect:flags2.2?long factcheck:flags2.3?FactCheck report_delivery_until_date:flags2.5?int paid_message_stars:flags2.6?long = Message; +message#9815cec8 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true flags2:# offline:flags2.1?true video_processing_pending:flags2.4?true paid_suggested_post_stars:flags2.8?true paid_suggested_post_ton:flags2.9?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long via_business_bot_id:flags2.0?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int effect:flags2.2?long factcheck:flags2.3?FactCheck report_delivery_until_date:flags2.5?int paid_message_stars:flags2.6?long suggested_post:flags2.7?SuggestedPost = Message; messageService#7a800e0a flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true reactions_are_possible:flags.9?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer saved_peer_id:flags.28?Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction reactions:flags.20?MessageReactions ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; messageMediaPhoto#695150d7 flags:# spoiler:flags.3?true photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia; @@ -166,6 +166,10 @@ messageActionPaidMessagesPrice#84b88578 flags:# broadcast_messages_allowed:flags messageActionConferenceCall#2ffe2f7a flags:# missed:flags.0?true active:flags.1?true video:flags.4?true call_id:long duration:flags.2?int other_participants:flags.3?Vector = MessageAction; messageActionTodoCompletions#cc7c5c89 completed:Vector incompleted:Vector = MessageAction; messageActionTodoAppendTasks#c7edbc83 list:Vector = MessageAction; +messageActionSuggestedPostApproval#ee7a1596 flags:# rejected:flags.0?true balance_too_low:flags.1?true reject_comment:flags.2?string schedule_date:flags.3?int price:flags.4?StarsAmount = MessageAction; +messageActionSuggestedPostSuccess#95ddcf69 price:StarsAmount = MessageAction; +messageActionSuggestedPostRefund#69f916f8 flags:# payer_initiated:flags.0?true = MessageAction; +messageActionGiftTon#a8a3c699 flags:# currency:string amount:long crypto_currency:string crypto_amount:long transaction_id:flags.0?string = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; photoEmpty#2331b22d id:long = Photo; @@ -376,7 +380,6 @@ updateBotNewBusinessMessage#9ddb347c flags:# connection_id:string message:Messag updateBotEditBusinessMessage#7df587c flags:# connection_id:string message:Message reply_to_message:flags.0?Message qts:int = Update; updateBotDeleteBusinessMessage#a02a982e connection_id:string peer:Peer messages:Vector qts:int = Update; updateNewStoryReaction#1824e40b story_id:int peer:Peer reaction:Reaction = Update; -updateBroadcastRevenueTransactions#dfd961f5 peer:Peer balances:BroadcastRevenueBalances = Update; updateStarsBalance#4e80a379 balance:StarsAmount = Update; updateBusinessBotCallbackQuery#1ea2fda7 flags:# query_id:long user_id:long connection_id:string message:Message reply_to_message:flags.2?Message chat_instance:long data:flags.0?bytes = Update; updateStarsRevenueStatus#a584b019 peer:Peer status:StarsRevenueStatus = Update; @@ -386,6 +389,7 @@ updateSentPhoneCode#504aa18f sent_code:auth.SentCode = Update; updateGroupCallChainBlocks#a477288f call:InputGroupCall sub_chain_id:int blocks:Vector next_offset:int = Update; updateReadMonoForumInbox#77b0e372 channel_id:long saved_peer_id:Peer read_max_id:int = Update; updateReadMonoForumOutbox#a4a79376 channel_id:long saved_peer_id:Peer read_max_id:int = Update; +updateMonoForumNoPaidException#9f812b08 flags:# exception:flags.0?true channel_id:long saved_peer_id:Peer = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; updates.differenceEmpty#5d75a138 date:int seq:int = updates.Difference; updates.difference#f49ca0 new_messages:Vector new_encrypted_messages:Vector other_updates:Vector chats:Vector users:Vector state:updates.State = updates.Difference; @@ -549,6 +553,7 @@ inputStickerSetEmojiGenericAnimations#4c4d4ce = InputStickerSet; inputStickerSetEmojiDefaultStatuses#29d0f5ee = InputStickerSet; inputStickerSetEmojiDefaultTopicIcons#44c1f8e9 = InputStickerSet; inputStickerSetEmojiChannelDefaultStatuses#49748553 = InputStickerSet; +inputStickerSetTonGifts#1cf671a0 = InputStickerSet; stickerSet#2dd14edc flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true emojis:flags.7?true text_color:flags.9?true channel_emoji_status:flags.10?true creator:flags.11?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int thumb_document_id:flags.8?long count:int hash:int = StickerSet; messages.stickerSet#6e153f16 set:StickerSet packs:Vector keywords:Vector documents:Vector = messages.StickerSet; messages.stickerSetNotModified#d3f924eb = messages.StickerSet; @@ -689,7 +694,7 @@ contacts.topPeersNotModified#de266ef5 = contacts.TopPeers; contacts.topPeers#70b772a8 categories:Vector chats:Vector users:Vector = contacts.TopPeers; contacts.topPeersDisabled#b52c939d = contacts.TopPeers; draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage; -draftMessage#2d65321f flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo message:string entities:flags.3?Vector media:flags.5?InputMedia date:int effect:flags.7?long = DraftMessage; +draftMessage#96eaa5eb flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo message:string entities:flags.3?Vector media:flags.5?InputMedia date:int effect:flags.7?long suggested_post:flags.8?SuggestedPost = DraftMessage; messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers; messages.featuredStickers#be382906 flags:# premium:flags.0?true hash:long count:int sets:Vector unread:Vector = messages.FeaturedStickers; messages.recentStickersNotModified#b17f890 = messages.RecentStickers; @@ -966,7 +971,7 @@ pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true correct:flags.1?true optio pollResults#7adf2420 flags:# min:flags.0?true results:flags.1?Vector total_voters:flags.2?int recent_voters:flags.3?Vector solution:flags.4?string solution_entities:flags.4?Vector = PollResults; chatOnlines#f041e250 onlines:int = ChatOnlines; statsURL#47a971e0 url:string = StatsURL; -chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true manage_call:flags.11?true other:flags.12?true manage_topics:flags.13?true post_stories:flags.14?true edit_stories:flags.15?true delete_stories:flags.16?true = ChatAdminRights; +chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true manage_call:flags.11?true other:flags.12?true manage_topics:flags.13?true post_stories:flags.14?true edit_stories:flags.15?true delete_stories:flags.16?true manage_direct_messages:flags.17?true = ChatAdminRights; chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true manage_topics:flags.18?true send_photos:flags.19?true send_videos:flags.20?true send_roundvideos:flags.21?true send_audios:flags.22?true send_voices:flags.23?true send_docs:flags.24?true send_plain:flags.25?true until_date:int = ChatBannedRights; inputWallPaper#e630b979 id:long access_hash:long = InputWallPaper; inputWallPaperSlug#72091c80 slug:string = InputWallPaper; @@ -1346,16 +1351,9 @@ sponsoredMessageReportOption#430d3150 text:string option:bytes = SponsoredMessag channels.sponsoredMessageReportResultChooseOption#846f9e42 title:string options:Vector = channels.SponsoredMessageReportResult; channels.sponsoredMessageReportResultAdsHidden#3e3bcf2f = channels.SponsoredMessageReportResult; channels.sponsoredMessageReportResultReported#ad798849 = channels.SponsoredMessageReportResult; -stats.broadcastRevenueStats#5407e297 top_hours_graph:StatsGraph revenue_graph:StatsGraph balances:BroadcastRevenueBalances usd_rate:double = stats.BroadcastRevenueStats; -stats.broadcastRevenueWithdrawalUrl#ec659737 url:string = stats.BroadcastRevenueWithdrawalUrl; -broadcastRevenueTransactionProceeds#557e2cc4 amount:long from_date:int to_date:int = BroadcastRevenueTransaction; -broadcastRevenueTransactionWithdrawal#5a590978 flags:# pending:flags.0?true failed:flags.2?true amount:long date:int provider:string transaction_date:flags.1?int transaction_url:flags.1?string = BroadcastRevenueTransaction; -broadcastRevenueTransactionRefund#42d30d2e amount:long date:int provider:string = BroadcastRevenueTransaction; -stats.broadcastRevenueTransactions#87158466 count:int transactions:Vector = stats.BroadcastRevenueTransactions; reactionNotificationsFromContacts#bac3a61a = ReactionNotificationsFrom; reactionNotificationsFromAll#4b9e22a0 = ReactionNotificationsFrom; reactionsNotifySettings#56e34970 flags:# messages_notify_from:flags.0?ReactionNotificationsFrom stories_notify_from:flags.1?ReactionNotificationsFrom sound:NotificationSound show_previews:Bool = ReactionsNotifySettings; -broadcastRevenueBalances#c3ff71e7 flags:# withdrawal_enabled:flags.0?true current_balance:long available_balance:long overall_revenue:long = BroadcastRevenueBalances; availableEffect#93c3e27e flags:# premium_required:flags.2?true id:long emoticon:string static_icon_id:flags.0?long effect_sticker_id:long effect_animation_id:flags.1?long = AvailableEffect; messages.availableEffectsNotModified#d1ed9a5b = messages.AvailableEffects; messages.availableEffects#bddb616e hash:int effects:Vector documents:Vector = messages.AvailableEffects; @@ -1369,13 +1367,13 @@ starsTransactionPeer#d80da15d peer:Peer = StarsTransactionPeer; starsTransactionPeerAds#60682812 = StarsTransactionPeer; starsTransactionPeerAPI#f9677aad = StarsTransactionPeer; starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption; -starsTransaction#a39fd94a flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true business_transfer:flags.21?true stargift_resale:flags.22?true id:string stars:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount paid_messages:flags.19?int premium_gift_months:flags.20?int = StarsTransaction; +starsTransaction#13659eb0 flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true business_transfer:flags.21?true stargift_resale:flags.22?true id:string amount:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount paid_messages:flags.19?int premium_gift_months:flags.20?int ads_proceeds_from_date:flags.23?int ads_proceeds_to_date:flags.23?int = StarsTransaction; payments.starsStatus#6c9ce8ed flags:# balance:StarsAmount subscriptions:flags.1?Vector subscriptions_next_offset:flags.2?string subscriptions_missing_balance:flags.4?long history:flags.3?Vector next_offset:flags.0?string chats:Vector users:Vector = payments.StarsStatus; foundStory#e87acbc0 peer:Peer story:StoryItem = FoundStory; stories.foundStories#e2de7737 flags:# count:int stories:Vector next_offset:flags.0?string chats:Vector users:Vector = stories.FoundStories; geoPointAddress#de4c5d93 flags:# country_iso2:string state:flags.0?string city:flags.1?string street:flags.2?string = GeoPointAddress; starsRevenueStatus#febe5491 flags:# withdrawal_enabled:flags.0?true current_balance:StarsAmount available_balance:StarsAmount overall_revenue:StarsAmount next_withdrawal_at:flags.1?int = StarsRevenueStatus; -payments.starsRevenueStats#c92bb73b revenue_graph:StatsGraph status:StarsRevenueStatus usd_rate:double = payments.StarsRevenueStats; +payments.starsRevenueStats#6c207376 flags:# top_hours_graph:flags.0?StatsGraph revenue_graph:StatsGraph status:StarsRevenueStatus usd_rate:double = payments.StarsRevenueStats; payments.starsRevenueWithdrawalUrl#1dab80b7 url:string = payments.StarsRevenueWithdrawalUrl; payments.starsRevenueAdsAccountUrl#394e7f21 url:string = payments.StarsRevenueAdsAccountUrl; inputStarsTransaction#206ae6d1 flags:# refund:flags.0?true id:string = InputStarsTransaction; @@ -1388,10 +1386,10 @@ starsSubscription#2e6eab1a flags:# canceled:flags.0?true can_refulfill:flags.1?t messageReactor#4ba3a95a flags:# top:flags.0?true my:flags.1?true anonymous:flags.2?true peer_id:flags.3?Peer count:int = MessageReactor; starsGiveawayOption#94ce852a flags:# extended:flags.0?true default:flags.1?true stars:long yearly_boosts:int store_product:flags.2?string currency:string amount:long winners:Vector = StarsGiveawayOption; starsGiveawayWinnersOption#54236209 flags:# default:flags.0?true users:int per_user_stars:long = StarsGiveawayWinnersOption; -starGift#c62aca28 flags:# limited:flags.0?true sold_out:flags.1?true birthday:flags.2?true id:long sticker:Document stars:long availability_remains:flags.0?int availability_total:flags.0?int availability_resale:flags.4?long convert_stars:long first_sale_date:flags.1?int last_sale_date:flags.1?int upgrade_stars:flags.3?long resell_min_stars:flags.4?long title:flags.5?string = StarGift; -starGiftUnique#6411db89 flags:# id:long title:string slug:string num:int owner_id:flags.0?Peer owner_name:flags.1?string owner_address:flags.2?string attributes:Vector availability_issued:int availability_total:int gift_address:flags.3?string resell_stars:flags.4?long = StarGift; +starGift#7f853c12 flags:# limited:flags.0?true sold_out:flags.1?true birthday:flags.2?true id:long sticker:Document stars:long availability_remains:flags.0?int availability_total:flags.0?int availability_resale:flags.4?long convert_stars:long first_sale_date:flags.1?int last_sale_date:flags.1?int upgrade_stars:flags.3?long resell_min_stars:flags.4?long title:flags.5?string released_by:flags.6?Peer = StarGift; +starGiftUnique#f63778ae flags:# id:long title:string slug:string num:int owner_id:flags.0?Peer owner_name:flags.1?string owner_address:flags.2?string attributes:Vector availability_issued:int availability_total:int gift_address:flags.3?string resell_stars:flags.4?long released_by:flags.5?Peer = StarGift; payments.starGiftsNotModified#a388a368 = payments.StarGifts; -payments.starGifts#901689ea hash:int gifts:Vector = payments.StarGifts; +payments.starGifts#2ed82995 hash:int gifts:Vector chats:Vector users:Vector = payments.StarGifts; messageReportOption#7903e3d9 text:string option:bytes = MessageReportOption; reportResultChooseOption#f0e4e0b6 title:string options:Vector = ReportResult; reportResultAddComment#6f09ac31 flags:# optional:flags.0?true option:bytes = ReportResult; @@ -1404,6 +1402,7 @@ connectedBotStarRef#19a13f71 flags:# revoked:flags.1?true url:string date:int bo payments.connectedStarRefBots#98d5ea1d count:int connected_bots:Vector users:Vector = payments.ConnectedStarRefBots; payments.suggestedStarRefBots#b4d5d859 flags:# count:int suggested_bots:Vector users:Vector next_offset:flags.0?string = payments.SuggestedStarRefBots; starsAmount#bbb6b4a3 amount:long nanos:int = StarsAmount; +starsTonAmount#74aee3e0 amount:long = StarsAmount; messages.foundStickersNotModified#6010c534 flags:# next_offset:flags.0?int = messages.FoundStickers; messages.foundStickers#82c9e290 flags:# next_offset:flags.0?int hash:long stickers:Vector = messages.FoundStickers; botVerifierSettings#b0cd6617 flags:# can_modify_custom_description:flags.1?true icon:long company:string custom_description:flags.0?string = BotVerifierSettings; @@ -1445,6 +1444,7 @@ pendingSuggestion#e7e82e12 suggestion:string title:TextWithEntities description: todoItem#cba9a52f id:int title:TextWithEntities = TodoItem; todoList#49b92a26 flags:# others_can_append:flags.0?true others_can_complete:flags.1?true title:TextWithEntities list:Vector = TodoList; todoCompletion#4cc120b7 id:int completed_by:long date:int = TodoCompletion; +suggestedPost#e8e37e5 flags:# accepted:flags.1?true rejected:flags.2?true price:flags.3?StarsAmount schedule_date:flags.0?int = SuggestedPost; ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; initConnection#c1cd5ea9 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy params:flags.1?JSONValue query:!X = X; @@ -1511,6 +1511,7 @@ account.resolveBusinessChatLink#5492e5ee slug:string = account.ResolvedBusinessC account.toggleSponsoredMessages#b9d9a38d enabled:Bool = Bool; account.getCollectibleEmojiStatuses#2e7b4543 hash:long = account.EmojiStatuses; account.getPaidMessagesRevenue#19ba4a67 flags:# parent_peer:flags.0?InputPeer user_id:InputUser = account.PaidMessagesRevenue; +account.toggleNoPaidMessagesException#fe2eda76 flags:# refund_charged:flags.0?true require_payment:flags.2?true parent_peer:flags.1?InputPeer user_id:InputUser = Bool; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#b60f5918 id:InputUser = users.UserFull; contacts.getContacts#5dd69e12 hash:long = contacts.Contacts; @@ -1535,9 +1536,9 @@ messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?t messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; messages.receivedMessages#5a954c0 max_id:int = Vector; messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool; -messages.sendMessage#fbf2340a flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true allow_paid_floodskip:flags.19?true peer:InputPeer reply_to:flags.0?InputReplyTo message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long allow_paid_stars:flags.21?long = Updates; -messages.sendMedia#a550cd78 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true allow_paid_floodskip:flags.19?true peer:InputPeer reply_to:flags.0?InputReplyTo media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long allow_paid_stars:flags.21?long = Updates; -messages.forwardMessages#38f0188c flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true allow_paid_floodskip:flags.19?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer top_msg_id:flags.9?int reply_to:flags.22?InputReplyTo schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut video_timestamp:flags.20?int allow_paid_stars:flags.21?long = Updates; +messages.sendMessage#fe05dc9a flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true allow_paid_floodskip:flags.19?true peer:InputPeer reply_to:flags.0?InputReplyTo message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long allow_paid_stars:flags.21?long suggested_post:flags.22?SuggestedPost = Updates; +messages.sendMedia#ac55d9c1 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true allow_paid_floodskip:flags.19?true peer:InputPeer reply_to:flags.0?InputReplyTo media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long allow_paid_stars:flags.21?long suggested_post:flags.22?SuggestedPost = Updates; +messages.forwardMessages#978928ca flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true allow_paid_floodskip:flags.19?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer top_msg_id:flags.9?int reply_to:flags.22?InputReplyTo schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut video_timestamp:flags.20?int allow_paid_stars:flags.21?long suggested_post:flags.23?SuggestedPost = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings; messages.report#fc78af9b peer:InputPeer id:Vector option:bytes message:string = ReportResult; @@ -1571,7 +1572,7 @@ messages.sendInlineBotResult#c0cf7646 flags:# silent:flags.5?true background:fla messages.editMessage#dfd14005 flags:# no_webpage:flags.1?true invert_media:flags.16?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.15?int quick_reply_shortcut_id:flags.17?int = Updates; messages.getBotCallbackAnswer#9342ca07 flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes password:flags.2?InputCheckPasswordSRP = messages.BotCallbackAnswer; messages.getPeerDialogs#e470bcfd peers:Vector = messages.PeerDialogs; -messages.saveDraft#d372c5ce flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo peer:InputPeer message:string entities:flags.3?Vector media:flags.5?InputMedia effect:flags.7?long = Bool; +messages.saveDraft#54ae308e flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo peer:InputPeer message:string entities:flags.3?Vector media:flags.5?InputMedia effect:flags.7?long suggested_post:flags.8?SuggestedPost = Bool; messages.getFeaturedStickers#64780b14 hash:long = messages.FeaturedStickers; messages.readFeaturedStickers#5b118126 id:Vector = Bool; messages.getRecentStickers#9da9403b flags:# attached:flags.0?true hash:long = messages.RecentStickers; @@ -1674,6 +1675,7 @@ messages.getPreparedInlineMessage#857ebdb8 bot:InputUser id:string = messages.Pr messages.reportMessagesDelivery#5a6d7395 flags:# push:flags.0?true peer:InputPeer id:Vector = Bool; messages.toggleTodoCompleted#d3e03124 peer:InputPeer msg_id:int completed:Vector incompleted:Vector = Updates; messages.appendTodoList#21a61057 peer:InputPeer msg_id:int list:Vector = Updates; +messages.toggleSuggestedPostApproval#8107455c flags:# reject:flags.1?true peer:InputPeer msg_id:int schedule_date:flags.0?int reject_comment:flags.2?string = Updates; updates.getState#edd4882a = updates.State; updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference; updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference; @@ -1760,11 +1762,11 @@ payments.applyGiftCode#f6e26854 slug:string = Updates; payments.getGiveawayInfo#f4239425 peer:InputPeer msg_id:int = payments.GiveawayInfo; payments.launchPrepaidGiveaway#5ff58f20 peer:InputPeer giveaway_id:long purpose:InputStorePaymentPurpose = Updates; payments.getStarsTopupOptions#c00ec7d3 = Vector; -payments.getStarsStatus#104fcfa7 peer:InputPeer = payments.StarsStatus; -payments.getStarsTransactions#69da4557 flags:# inbound:flags.0?true outbound:flags.1?true ascending:flags.2?true subscription_id:flags.3?string peer:InputPeer offset:string limit:int = payments.StarsStatus; +payments.getStarsStatus#4ea9b3bf flags:# ton:flags.0?true peer:InputPeer = payments.StarsStatus; +payments.getStarsTransactions#69da4557 flags:# inbound:flags.0?true outbound:flags.1?true ascending:flags.2?true ton:flags.4?true subscription_id:flags.3?string peer:InputPeer offset:string limit:int = payments.StarsStatus; payments.sendStarsForm#7998c914 form_id:long invoice:InputInvoice = payments.PaymentResult; payments.refundStarsCharge#25ae8f4a user_id:InputUser charge_id:string = Updates; -payments.getStarsTransactionsByID#27842d2e peer:InputPeer id:Vector = payments.StarsStatus; +payments.getStarsTransactionsByID#2dca16b8 flags:# ton:flags.0?true peer:InputPeer id:Vector = payments.StarsStatus; payments.getStarsGiftOptions#d3c96bc8 flags:# user_id:flags.0?InputUser = Vector; payments.getStarsSubscriptions#32512c5 flags:# missing_balance:flags.0?true peer:InputPeer offset:string = payments.StarsStatus; payments.changeStarsSubscription#c7770878 flags:# peer:InputPeer subscription_id:string canceled:flags.0?Bool = Bool; @@ -1814,8 +1816,6 @@ stats.getMessagePublicForwards#5f150144 channel:InputChannel msg_id:int offset:s stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; 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#f788ee19 flags:# dark:flags.0?true peer:InputPeer = stats.BroadcastRevenueStats; -stats.getBroadcastRevenueWithdrawalUrl#9df4faad peer:InputPeer 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 8cd431ff4..5d760ca0b 100644 --- a/src/lib/gramjs/tl/static/api.json +++ b/src/lib/gramjs/tl/static/api.json @@ -61,6 +61,7 @@ "account.resolveBusinessChatLink", "account.toggleSponsoredMessages", "account.getCollectibleEmojiStatuses", + "account.toggleNoPaidMessagesException", "account.toggleNoPaidMessagesException ", "account.getPaidMessagesRevenue", "account.getAccountTTL", @@ -225,6 +226,7 @@ "messages.getSponsoredMessages", "messages.reportMessagesDelivery", "messages.getPreparedInlineMessage", + "messages.toggleSuggestedPostApproval", "messages.toggleTodoCompleted", "messages.appendTodoList", "updates.getState", diff --git a/src/lib/gramjs/tl/static/api.tl b/src/lib/gramjs/tl/static/api.tl index 17b1608ba..45adc634b 100644 --- a/src/lib/gramjs/tl/static/api.tl +++ b/src/lib/gramjs/tl/static/api.tl @@ -117,7 +117,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto; messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; -message#eabcdd4d flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true flags2:# offline:flags2.1?true video_processing_pending:flags2.4?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long via_business_bot_id:flags2.0?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int effect:flags2.2?long factcheck:flags2.3?FactCheck report_delivery_until_date:flags2.5?int paid_message_stars:flags2.6?long = Message; +message#9815cec8 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true flags2:# offline:flags2.1?true video_processing_pending:flags2.4?true paid_suggested_post_stars:flags2.8?true paid_suggested_post_ton:flags2.9?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long via_business_bot_id:flags2.0?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int effect:flags2.2?long factcheck:flags2.3?FactCheck report_delivery_until_date:flags2.5?int paid_message_stars:flags2.6?long suggested_post:flags2.7?SuggestedPost = Message; messageService#7a800e0a flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true reactions_are_possible:flags.9?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer saved_peer_id:flags.28?Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction reactions:flags.20?MessageReactions ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; @@ -192,6 +192,10 @@ messageActionPaidMessagesPrice#84b88578 flags:# broadcast_messages_allowed:flags messageActionConferenceCall#2ffe2f7a flags:# missed:flags.0?true active:flags.1?true video:flags.4?true call_id:long duration:flags.2?int other_participants:flags.3?Vector = MessageAction; messageActionTodoCompletions#cc7c5c89 completed:Vector incompleted:Vector = MessageAction; messageActionTodoAppendTasks#c7edbc83 list:Vector = MessageAction; +messageActionSuggestedPostApproval#ee7a1596 flags:# rejected:flags.0?true balance_too_low:flags.1?true reject_comment:flags.2?string schedule_date:flags.3?int price:flags.4?StarsAmount = MessageAction; +messageActionSuggestedPostSuccess#95ddcf69 price:StarsAmount = MessageAction; +messageActionSuggestedPostRefund#69f916f8 flags:# payer_initiated:flags.0?true = MessageAction; +messageActionGiftTon#a8a3c699 flags:# currency:string amount:long crypto_currency:string crypto_amount:long transaction_id:flags.0?string = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -429,7 +433,6 @@ updateBotNewBusinessMessage#9ddb347c flags:# connection_id:string message:Messag updateBotEditBusinessMessage#7df587c flags:# connection_id:string message:Message reply_to_message:flags.0?Message qts:int = Update; updateBotDeleteBusinessMessage#a02a982e connection_id:string peer:Peer messages:Vector qts:int = Update; updateNewStoryReaction#1824e40b story_id:int peer:Peer reaction:Reaction = Update; -updateBroadcastRevenueTransactions#dfd961f5 peer:Peer balances:BroadcastRevenueBalances = Update; updateStarsBalance#4e80a379 balance:StarsAmount = Update; updateBusinessBotCallbackQuery#1ea2fda7 flags:# query_id:long user_id:long connection_id:string message:Message reply_to_message:flags.2?Message chat_instance:long data:flags.0?bytes = Update; updateStarsRevenueStatus#a584b019 peer:Peer status:StarsRevenueStatus = Update; @@ -439,6 +442,7 @@ updateSentPhoneCode#504aa18f sent_code:auth.SentCode = Update; updateGroupCallChainBlocks#a477288f call:InputGroupCall sub_chain_id:int blocks:Vector next_offset:int = Update; updateReadMonoForumInbox#77b0e372 channel_id:long saved_peer_id:Peer read_max_id:int = Update; updateReadMonoForumOutbox#a4a79376 channel_id:long saved_peer_id:Peer read_max_id:int = Update; +updateMonoForumNoPaidException#9f812b08 flags:# exception:flags.0?true channel_id:long saved_peer_id:Peer = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -648,6 +652,7 @@ inputStickerSetEmojiGenericAnimations#4c4d4ce = InputStickerSet; inputStickerSetEmojiDefaultStatuses#29d0f5ee = InputStickerSet; inputStickerSetEmojiDefaultTopicIcons#44c1f8e9 = InputStickerSet; inputStickerSetEmojiChannelDefaultStatuses#49748553 = InputStickerSet; +inputStickerSetTonGifts#1cf671a0 = InputStickerSet; stickerSet#2dd14edc flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true emojis:flags.7?true text_color:flags.9?true channel_emoji_status:flags.10?true creator:flags.11?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int thumb_document_id:flags.8?long count:int hash:int = StickerSet; @@ -826,7 +831,7 @@ contacts.topPeers#70b772a8 categories:Vector chats:Vector< contacts.topPeersDisabled#b52c939d = contacts.TopPeers; draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage; -draftMessage#2d65321f flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo message:string entities:flags.3?Vector media:flags.5?InputMedia date:int effect:flags.7?long = DraftMessage; +draftMessage#96eaa5eb flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo message:string entities:flags.3?Vector media:flags.5?InputMedia date:int effect:flags.7?long suggested_post:flags.8?SuggestedPost = DraftMessage; messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers; messages.featuredStickers#be382906 flags:# premium:flags.0?true hash:long count:int sets:Vector unread:Vector = messages.FeaturedStickers; @@ -1206,7 +1211,7 @@ chatOnlines#f041e250 onlines:int = ChatOnlines; statsURL#47a971e0 url:string = StatsURL; -chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true manage_call:flags.11?true other:flags.12?true manage_topics:flags.13?true post_stories:flags.14?true edit_stories:flags.15?true delete_stories:flags.16?true = ChatAdminRights; +chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true manage_call:flags.11?true other:flags.12?true manage_topics:flags.13?true post_stories:flags.14?true edit_stories:flags.15?true delete_stories:flags.16?true manage_direct_messages:flags.17?true = ChatAdminRights; chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true manage_topics:flags.18?true send_photos:flags.19?true send_videos:flags.20?true send_roundvideos:flags.21?true send_audios:flags.22?true send_voices:flags.23?true send_docs:flags.24?true send_plain:flags.25?true until_date:int = ChatBannedRights; @@ -1824,23 +1829,11 @@ channels.sponsoredMessageReportResultChooseOption#846f9e42 title:string options: channels.sponsoredMessageReportResultAdsHidden#3e3bcf2f = channels.SponsoredMessageReportResult; channels.sponsoredMessageReportResultReported#ad798849 = channels.SponsoredMessageReportResult; -stats.broadcastRevenueStats#5407e297 top_hours_graph:StatsGraph revenue_graph:StatsGraph balances:BroadcastRevenueBalances usd_rate:double = stats.BroadcastRevenueStats; - -stats.broadcastRevenueWithdrawalUrl#ec659737 url:string = stats.BroadcastRevenueWithdrawalUrl; - -broadcastRevenueTransactionProceeds#557e2cc4 amount:long from_date:int to_date:int = BroadcastRevenueTransaction; -broadcastRevenueTransactionWithdrawal#5a590978 flags:# pending:flags.0?true failed:flags.2?true amount:long date:int provider:string transaction_date:flags.1?int transaction_url:flags.1?string = BroadcastRevenueTransaction; -broadcastRevenueTransactionRefund#42d30d2e amount:long date:int provider:string = BroadcastRevenueTransaction; - -stats.broadcastRevenueTransactions#87158466 count:int transactions:Vector = stats.BroadcastRevenueTransactions; - reactionNotificationsFromContacts#bac3a61a = ReactionNotificationsFrom; reactionNotificationsFromAll#4b9e22a0 = ReactionNotificationsFrom; reactionsNotifySettings#56e34970 flags:# messages_notify_from:flags.0?ReactionNotificationsFrom stories_notify_from:flags.1?ReactionNotificationsFrom sound:NotificationSound show_previews:Bool = ReactionsNotifySettings; -broadcastRevenueBalances#c3ff71e7 flags:# withdrawal_enabled:flags.0?true current_balance:long available_balance:long overall_revenue:long = BroadcastRevenueBalances; - availableEffect#93c3e27e flags:# premium_required:flags.2?true id:long emoticon:string static_icon_id:flags.0?long effect_sticker_id:long effect_animation_id:flags.1?long = AvailableEffect; messages.availableEffectsNotModified#d1ed9a5b = messages.AvailableEffects; @@ -1859,7 +1852,7 @@ starsTransactionPeerAPI#f9677aad = StarsTransactionPeer; starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption; -starsTransaction#a39fd94a flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true business_transfer:flags.21?true stargift_resale:flags.22?true id:string stars:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount paid_messages:flags.19?int premium_gift_months:flags.20?int = StarsTransaction; +starsTransaction#13659eb0 flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true business_transfer:flags.21?true stargift_resale:flags.22?true id:string amount:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount paid_messages:flags.19?int premium_gift_months:flags.20?int ads_proceeds_from_date:flags.23?int ads_proceeds_to_date:flags.23?int = StarsTransaction; payments.starsStatus#6c9ce8ed flags:# balance:StarsAmount subscriptions:flags.1?Vector subscriptions_next_offset:flags.2?string subscriptions_missing_balance:flags.4?long history:flags.3?Vector next_offset:flags.0?string chats:Vector users:Vector = payments.StarsStatus; @@ -1871,7 +1864,7 @@ geoPointAddress#de4c5d93 flags:# country_iso2:string state:flags.0?string city:f starsRevenueStatus#febe5491 flags:# withdrawal_enabled:flags.0?true current_balance:StarsAmount available_balance:StarsAmount overall_revenue:StarsAmount next_withdrawal_at:flags.1?int = StarsRevenueStatus; -payments.starsRevenueStats#c92bb73b revenue_graph:StatsGraph status:StarsRevenueStatus usd_rate:double = payments.StarsRevenueStats; +payments.starsRevenueStats#6c207376 flags:# top_hours_graph:flags.0?StatsGraph revenue_graph:StatsGraph status:StarsRevenueStatus usd_rate:double = payments.StarsRevenueStats; payments.starsRevenueWithdrawalUrl#1dab80b7 url:string = payments.StarsRevenueWithdrawalUrl; @@ -1897,11 +1890,11 @@ starsGiveawayOption#94ce852a flags:# extended:flags.0?true default:flags.1?true starsGiveawayWinnersOption#54236209 flags:# default:flags.0?true users:int per_user_stars:long = StarsGiveawayWinnersOption; -starGift#c62aca28 flags:# limited:flags.0?true sold_out:flags.1?true birthday:flags.2?true id:long sticker:Document stars:long availability_remains:flags.0?int availability_total:flags.0?int availability_resale:flags.4?long convert_stars:long first_sale_date:flags.1?int last_sale_date:flags.1?int upgrade_stars:flags.3?long resell_min_stars:flags.4?long title:flags.5?string = StarGift; -starGiftUnique#6411db89 flags:# id:long title:string slug:string num:int owner_id:flags.0?Peer owner_name:flags.1?string owner_address:flags.2?string attributes:Vector availability_issued:int availability_total:int gift_address:flags.3?string resell_stars:flags.4?long = StarGift; +starGift#7f853c12 flags:# limited:flags.0?true sold_out:flags.1?true birthday:flags.2?true id:long sticker:Document stars:long availability_remains:flags.0?int availability_total:flags.0?int availability_resale:flags.4?long convert_stars:long first_sale_date:flags.1?int last_sale_date:flags.1?int upgrade_stars:flags.3?long resell_min_stars:flags.4?long title:flags.5?string released_by:flags.6?Peer = StarGift; +starGiftUnique#f63778ae flags:# id:long title:string slug:string num:int owner_id:flags.0?Peer owner_name:flags.1?string owner_address:flags.2?string attributes:Vector availability_issued:int availability_total:int gift_address:flags.3?string resell_stars:flags.4?long released_by:flags.5?Peer = StarGift; payments.starGiftsNotModified#a388a368 = payments.StarGifts; -payments.starGifts#901689ea hash:int gifts:Vector = payments.StarGifts; +payments.starGifts#2ed82995 hash:int gifts:Vector chats:Vector users:Vector = payments.StarGifts; messageReportOption#7903e3d9 text:string option:bytes = MessageReportOption; @@ -1924,6 +1917,7 @@ payments.connectedStarRefBots#98d5ea1d count:int connected_bots:Vector users:Vector next_offset:flags.0?string = payments.SuggestedStarRefBots; starsAmount#bbb6b4a3 amount:long nanos:int = StarsAmount; +starsTonAmount#74aee3e0 amount:long = StarsAmount; messages.foundStickersNotModified#6010c534 flags:# next_offset:flags.0?int = messages.FoundStickers; messages.foundStickers#82c9e290 flags:# next_offset:flags.0?int hash:long stickers:Vector = messages.FoundStickers; @@ -1993,6 +1987,8 @@ todoList#49b92a26 flags:# others_can_append:flags.0?true others_can_complete:fla todoCompletion#4cc120b7 id:int completed_by:long date:int = TodoCompletion; +suggestedPost#e8e37e5 flags:# accepted:flags.1?true rejected:flags.2?true price:flags.3?StarsAmount schedule_date:flags.0?int = SuggestedPost; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -2004,7 +2000,7 @@ invokeWithMessagesRange#365275f2 {X:Type} range:MessageRange query:!X = X; invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X; invokeWithBusinessConnection#dd289f8e {X:Type} connection_id:string query:!X = X; invokeWithGooglePlayIntegrity#1df92984 {X:Type} nonce:string token:string query:!X = X; -invokeWithApnsSecret#0dae54f8 {X:Type} nonce:string secret:string query:!X = X; +invokeWithApnsSecret#dae54f8 {X:Type} nonce:string secret:string query:!X = X; invokeWithReCaptcha#adbb0f94 {X:Type} token:string query:!X = X; auth.sendCode#a677244f phone_number:string api_id:int api_hash:string settings:CodeSettings = auth.SentCode; @@ -2189,9 +2185,9 @@ messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?t messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; messages.receivedMessages#5a954c0 max_id:int = Vector; messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool; -messages.sendMessage#fbf2340a flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true allow_paid_floodskip:flags.19?true peer:InputPeer reply_to:flags.0?InputReplyTo message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long allow_paid_stars:flags.21?long = Updates; -messages.sendMedia#a550cd78 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true allow_paid_floodskip:flags.19?true peer:InputPeer reply_to:flags.0?InputReplyTo media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long allow_paid_stars:flags.21?long = Updates; -messages.forwardMessages#38f0188c flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true allow_paid_floodskip:flags.19?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer top_msg_id:flags.9?int reply_to:flags.22?InputReplyTo schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut video_timestamp:flags.20?int allow_paid_stars:flags.21?long = Updates; +messages.sendMessage#fe05dc9a flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true allow_paid_floodskip:flags.19?true peer:InputPeer reply_to:flags.0?InputReplyTo message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long allow_paid_stars:flags.21?long suggested_post:flags.22?SuggestedPost = Updates; +messages.sendMedia#ac55d9c1 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true allow_paid_floodskip:flags.19?true peer:InputPeer reply_to:flags.0?InputReplyTo media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long allow_paid_stars:flags.21?long suggested_post:flags.22?SuggestedPost = Updates; +messages.forwardMessages#978928ca flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true allow_paid_floodskip:flags.19?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer top_msg_id:flags.9?int reply_to:flags.22?InputReplyTo schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut video_timestamp:flags.20?int allow_paid_stars:flags.21?long suggested_post:flags.23?SuggestedPost = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings; messages.report#fc78af9b peer:InputPeer id:Vector option:bytes message:string = ReportResult; @@ -2241,7 +2237,7 @@ messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true invert_me messages.getBotCallbackAnswer#9342ca07 flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes password:flags.2?InputCheckPasswordSRP = messages.BotCallbackAnswer; messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool; messages.getPeerDialogs#e470bcfd peers:Vector = messages.PeerDialogs; -messages.saveDraft#d372c5ce flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo peer:InputPeer message:string entities:flags.3?Vector media:flags.5?InputMedia effect:flags.7?long = Bool; +messages.saveDraft#54ae308e flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo peer:InputPeer message:string entities:flags.3?Vector media:flags.5?InputMedia effect:flags.7?long suggested_post:flags.8?SuggestedPost = Bool; messages.getAllDrafts#6a3f8d65 = Updates; messages.getFeaturedStickers#64780b14 hash:long = messages.FeaturedStickers; messages.readFeaturedStickers#5b118126 id:Vector = Bool; @@ -2409,6 +2405,7 @@ messages.getSavedDialogsByID#6f6f9c96 flags:# parent_peer:flags.1?InputPeer ids: messages.readSavedHistory#ba4a3b5b parent_peer:InputPeer peer:InputPeer max_id:int = Bool; messages.toggleTodoCompleted#d3e03124 peer:InputPeer msg_id:int completed:Vector incompleted:Vector = Updates; messages.appendTodoList#21a61057 peer:InputPeer msg_id:int list:Vector = Updates; +messages.toggleSuggestedPostApproval#8107455c flags:# reject:flags.1?true peer:InputPeer msg_id:int schedule_date:flags.0?int reject_comment:flags.2?string = Updates; updates.getState#edd4882a = updates.State; updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference; @@ -2567,14 +2564,14 @@ payments.applyGiftCode#f6e26854 slug:string = Updates; payments.getGiveawayInfo#f4239425 peer:InputPeer msg_id:int = payments.GiveawayInfo; payments.launchPrepaidGiveaway#5ff58f20 peer:InputPeer giveaway_id:long purpose:InputStorePaymentPurpose = Updates; payments.getStarsTopupOptions#c00ec7d3 = Vector; -payments.getStarsStatus#104fcfa7 peer:InputPeer = payments.StarsStatus; -payments.getStarsTransactions#69da4557 flags:# inbound:flags.0?true outbound:flags.1?true ascending:flags.2?true subscription_id:flags.3?string peer:InputPeer offset:string limit:int = payments.StarsStatus; +payments.getStarsStatus#4ea9b3bf flags:# ton:flags.0?true peer:InputPeer = payments.StarsStatus; +payments.getStarsTransactions#69da4557 flags:# inbound:flags.0?true outbound:flags.1?true ascending:flags.2?true ton:flags.4?true subscription_id:flags.3?string peer:InputPeer offset:string limit:int = payments.StarsStatus; payments.sendStarsForm#7998c914 form_id:long invoice:InputInvoice = payments.PaymentResult; payments.refundStarsCharge#25ae8f4a user_id:InputUser charge_id:string = Updates; -payments.getStarsRevenueStats#d91ffad6 flags:# dark:flags.0?true peer:InputPeer = payments.StarsRevenueStats; -payments.getStarsRevenueWithdrawalUrl#13bbe8b3 peer:InputPeer stars:long password:InputCheckPasswordSRP = payments.StarsRevenueWithdrawalUrl; +payments.getStarsRevenueStats#d91ffad6 flags:# dark:flags.0?true ton:flags.1?true peer:InputPeer = payments.StarsRevenueStats; +payments.getStarsRevenueWithdrawalUrl#2433dc92 flags:# ton:flags.0?true peer:InputPeer amount:flags.1?long password:InputCheckPasswordSRP = payments.StarsRevenueWithdrawalUrl; payments.getStarsRevenueAdsAccountUrl#d1d7efc5 peer:InputPeer = payments.StarsRevenueAdsAccountUrl; -payments.getStarsTransactionsByID#27842d2e peer:InputPeer id:Vector = payments.StarsStatus; +payments.getStarsTransactionsByID#2dca16b8 flags:# ton:flags.0?true peer:InputPeer id:Vector = payments.StarsStatus; payments.getStarsGiftOptions#d3c96bc8 flags:# user_id:flags.0?InputUser = Vector; payments.getStarsSubscriptions#32512c5 flags:# missing_balance:flags.0?true peer:InputPeer offset:string = payments.StarsStatus; payments.changeStarsSubscription#c7770878 flags:# peer:InputPeer subscription_id:string canceled:flags.0?Bool = Bool; @@ -2667,9 +2664,6 @@ stats.getMessagePublicForwards#5f150144 channel:InputChannel msg_id:int offset:s stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; 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#f788ee19 flags:# dark:flags.0?true peer:InputPeer = stats.BroadcastRevenueStats; -stats.getBroadcastRevenueWithdrawalUrl#9df4faad peer:InputPeer password:InputCheckPasswordSRP = stats.BroadcastRevenueWithdrawalUrl; -stats.getBroadcastRevenueTransactions#70990b6d peer:InputPeer offset:int limit:int = stats.BroadcastRevenueTransactions; chatlists.exportChatlistInvite#8472478e chatlist:InputChatlist title:string peers:Vector = chatlists.ExportedChatlistInvite; chatlists.deleteExportedInvite#719c5c5e chatlist:InputChatlist slug:string = Bool; diff --git a/src/types/index.ts b/src/types/index.ts index f25379b41..f785207a0 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -16,6 +16,7 @@ import type { ApiFakeType, ApiFormattedText, ApiInputReplyInfo, + ApiInputSuggestedPostInfo, ApiLabeledPrice, ApiLanguage, ApiMediaFormat, @@ -40,6 +41,7 @@ import type { ApiTopic, ApiTypingStatus, ApiVideo, + MediaContent, StarGiftAttributeIdModel, } from '../api/types'; import type { DC_IDS } from '../config'; @@ -726,6 +728,7 @@ export type SendMessageParams = { text?: string; entities?: ApiMessageEntity[]; replyInfo?: ApiInputReplyInfo; + suggestedPostInfo?: ApiInputSuggestedPostInfo; attachment?: ApiAttachment; sticker?: ApiSticker; story?: ApiStory | ApiStorySkipped; @@ -755,6 +758,7 @@ export type SendMessageParams = { isForwarding?: boolean; forwardParams?: ForwardMessagesParams; isStoryReply?: boolean; + suggestedMedia?: MediaContent; }; export type ForwardedLocalMessagesSlice = { diff --git a/src/types/language.d.ts b/src/types/language.d.ts index a6a369e08..611df5183 100644 --- a/src/types/language.d.ts +++ b/src/types/language.d.ts @@ -1538,6 +1538,38 @@ export interface LangPair { 'MonoforumComposerPlaceholder': undefined; 'ChannelSendMessage': undefined; 'AutomaticTranslation': undefined; + 'ComposerEmbeddedMessageSuggestedPostTitle': undefined; + 'ComposerEmbeddedMessageSuggestedPostDescription': undefined; + 'ActionSuggestedPostOutgoing': undefined; + 'ActionSuggestedChangesPrice': undefined; + 'ActionSuggestedChangesText': undefined; + 'ActionSuggestedChangesTime': undefined; + 'ActionSuggestedChangesMedia': undefined; + 'TitlePrice': undefined; + 'TitleTime': undefined; + 'TitleSuggestMessage': undefined; + 'TitleSuggestedChanges': undefined; + 'EnterPriceInStars': undefined; + 'SuggestMessageDateTimeHint': undefined; + 'TitleAnytime': undefined; + 'ButtonOfferFree': undefined; + 'ButtonUpdateTerms': undefined; + 'InputPlaceholderPrice': undefined; + 'SuggestedPostApprove': undefined; + 'SuggestedPostDecline': undefined; + 'SuggestedPostSuggestChanges': undefined; + 'InputTitleSuggestMessageTime': undefined; + 'SuggestedPostApproved': undefined; + 'SuggestedPostRejectedNotification': undefined; + 'SuggestedPostAgreementReached': undefined; + 'CurrencyStars': undefined; + 'DeclineReasonPlaceholder': undefined; + 'SuggestedPostRejectedYou': undefined; + 'SuggestedPostRejectedWithReasonYou': undefined; + 'ComposerPlaceholderCaption': undefined; + 'SuggestedPostConfirmTitle': undefined; + 'ButtonPublish': undefined; + 'PublishNow': undefined; 'TitleNewToDoList': undefined; 'TitleEditToDoList': undefined; 'TitleAppendToDoList': undefined; @@ -2539,6 +2571,132 @@ export interface LangPairWithVariables { 'ComposerTitleForwardFrom': { 'users': V; }; + 'TitleSuggestedPostAmountForAnyTime': { + 'amount': V; + }; + 'ActionSuggestedPostIncoming': { + 'user': V; + }; + 'ActionSuggestedChangesOutgoing': { + 'changes': V; + }; + 'ActionSuggestedChangesIncoming': { + 'user': V; + 'changes': V; + }; + 'SuggestMessagePriceDescription': { + 'currency': V; + }; + 'SuggestMessageTimeDescription': { + 'hint': V; + 'duration': V; + }; + 'ButtonOfferAmount': { + 'amount': V; + }; + 'SuggestedPostPublishSchedule': { + 'peer': V; + 'date': V; + }; + 'SuggestedPostPublishScheduleYou': { + 'peer': V; + 'date': V; + }; + 'SuggestedPostPublished': { + 'peer': V; + 'date': V; + }; + 'SuggestedPostPublishedYou': { + 'peer': V; + 'date': V; + }; + 'SuggestedPostCharged': { + 'user': V; + 'amount': V; + }; + 'SuggestedPostChargedYou': { + 'amount': V; + }; + 'SuggestedPostReceiveAmount': { + 'peer': V; + 'currency': V; + 'duration': V; + }; + 'SuggestedPostReceiveAmountYou': { + 'peer': V; + 'currency': V; + 'duration': V; + }; + 'SuggestedPostRefund': { + 'peer': V; + 'duration': V; + }; + 'SuggestedPostRefundYou': { + 'peer': V; + 'duration': V; + 'currency': V; + }; + 'SuggestedPostBalanceTooLow': { + 'peer': V; + 'currency': V; + }; + 'SuggestedPostRefundedByUser': { + 'channel': V; + 'amount': V; + 'user': V; + }; + 'SuggestedPostRefundedByChannel': { + 'amount': V; + 'peer': V; + 'channel': V; + }; + 'DeclinePostDialogQuestion': { + 'sender': V; + }; + 'SuggestedPostRejected': { + 'peer': V; + }; + 'SuggestedPostRejectedWithReason': { + 'peer': V; + }; + 'SuggestedPostRejectedComment': { + 'comment': V; + }; + 'ActionSuggestedPostSuccess': { + 'channel': V; + 'amount': V; + }; + 'DescriptionSuggestedPostMinimumOffer': { + 'amount': V; + }; + 'SuggestedPostConfirmMessage': { + 'peer': V; + }; + 'SuggestedPostConfirmDetailsAdmin': { + 'amount': V; + 'commission': V; + 'duration': V; + }; + 'SuggestedPostConfirmDetailsUser': { + 'amount': V; + 'commission': V; + 'duration': V; + }; + 'SuggestedPostConfirmDetailsWithTimeAdmin': { + 'amount': V; + 'commission': V; + 'time': V; + 'duration': V; + }; + 'SuggestedPostConfirmDetailsWithTimeUser': { + 'amount': V; + 'commission': V; + 'time': V; + 'duration': V; + }; + 'ButtonPublishAtTime': { + 'time': V; + }; 'TitleUserToDoList': { 'peer': V; }; @@ -2595,6 +2753,10 @@ export interface LangPairWithVariables { 'HintTodoListTasksCount': { 'count': V; }; + 'GiftInfoCollectibleBy': { + 'number': V; + 'owner': V; + }; } export interface LangPairPlural { diff --git a/src/util/dates/dateFormat.ts b/src/util/dates/dateFormat.ts index 8bf921a46..b40e9e085 100644 --- a/src/util/dates/dateFormat.ts +++ b/src/util/dates/dateFormat.ts @@ -227,6 +227,21 @@ export function formatTimeDuration(lang: OldLangFn, duration: number, showLast = return out.map((part) => lang(part.type, part.duration, 'i')).join(', '); } +export function formatScheduledDateTime( + scheduleDateTimestamp: number, + lang: LangFn, + oldLang: OldLangFn, +): string { + const scheduleDate = new Date(scheduleDateTimestamp * 1000); + + return lang('FormatDateAtTime', { + date: isToday(scheduleDate) + ? lang('WeekdayToday') + : formatHumanDate(oldLang, scheduleDateTimestamp * 1000, true, false, true), + time: formatTime(oldLang, scheduleDateTimestamp * 1000), + }); +} + export function formatHumanDate( lang: OldLangFn, datetime: number | Date, @@ -407,7 +422,7 @@ export function formatDateAtTime( return lang('formatDateAtTime', [formattedDate, time]); } -export function formatShortDuration(lang: LangFn, duration: number) { +export function formatShortDuration(lang: LangFn, duration: number, hoursPriority?: boolean) { if (duration < 0) { return lang('RightNow'); } @@ -422,7 +437,7 @@ export function formatShortDuration(lang: LangFn, duration: number) { return lang('Minutes', { count }, { pluralValue: count }); } - if (duration < 60 * 60 * 24) { + if (duration < 60 * 60 * 24 || (hoursPriority && duration <= 60 * 60 * 24 * 2)) { const count = Math.ceil(duration / (60 * 60)); return lang('Hours', { count }, { pluralValue: count }); }