Layer: Support layer 206 and 207 (#6038)
This commit is contained in:
parent
d76356da1f
commit
1e12d4f628
399
CLAUDE.md
Normal file
399
CLAUDE.md
Normal file
@ -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";
|
||||
<div className={buildClassName(styles.myWrapper, "legacy-class")} />
|
||||
```
|
||||
- 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 <filename>`.
|
||||
|
||||
# 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<boolean>()` 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<OwnProps & StateProps> = ({ … }) => { … }
|
||||
|
||||
// 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<HTMLDivElement>(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 (
|
||||
<div ref={ref} className={styles.root + (className ? ` ${className}` : '')}>
|
||||
<button onClick={handleClick}>{lang('ButtonKey')}</button>
|
||||
<p>{stateValue}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(withGlobal<OwnProps>((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<void>`.
|
||||
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<OwnProps>((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: <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.
|
||||
@ -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,
|
||||
|
||||
@ -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(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 }),
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
) {
|
||||
|
||||
@ -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),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
}: {
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
}), {
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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[][];
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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";
|
||||
@ -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';
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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<OwnProps> = ({
|
||||
withTimePicker,
|
||||
submitButtonLabel,
|
||||
secondButtonLabel,
|
||||
description,
|
||||
onClose,
|
||||
onSubmit,
|
||||
onDateChange,
|
||||
onSecondButtonClick,
|
||||
}) => {
|
||||
const lang = useOldLang();
|
||||
@ -169,6 +173,7 @@ const CalendarModal: FC<OwnProps> = ({
|
||||
dateCopy.setMonth(currentMonth);
|
||||
dateCopy.setFullYear(currentYear);
|
||||
|
||||
onDateChange?.(dateCopy);
|
||||
return dateCopy;
|
||||
});
|
||||
}
|
||||
@ -196,11 +201,12 @@ const CalendarModal: FC<OwnProps> = ({
|
||||
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<HTMLInputElement>) => {
|
||||
const value = e.target.value.replace(/[^\d]+/g, '');
|
||||
@ -215,11 +221,12 @@ const CalendarModal: FC<OwnProps> = ({
|
||||
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<OwnProps> = ({
|
||||
{withTimePicker && renderTimePicker()}
|
||||
|
||||
<div className="footer">
|
||||
{description && (
|
||||
<div className="description">
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
<div className="footer">
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
|
||||
@ -235,6 +235,8 @@ type StateProps =
|
||||
isReactionPickerOpen?: boolean;
|
||||
shouldDisplayGiftsButton?: boolean;
|
||||
isForwarding?: boolean;
|
||||
isReplying?: boolean;
|
||||
hasSuggestedPost?: boolean;
|
||||
forwardedMessagesCount?: number;
|
||||
pollModal: TabState['pollModal'];
|
||||
todoListModal: TabState['todoListModal'];
|
||||
@ -358,6 +360,8 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
isReactionPickerOpen,
|
||||
shouldDisplayGiftsButton,
|
||||
isForwarding,
|
||||
isReplying,
|
||||
hasSuggestedPost,
|
||||
forwardedMessagesCount,
|
||||
pollModal,
|
||||
todoListModal,
|
||||
@ -460,6 +464,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
hideEffectInComposer,
|
||||
updateChatSilentPosting,
|
||||
updateInsertingPeerIdMention,
|
||||
updateDraftSuggestedPostInfo,
|
||||
} = getActions();
|
||||
|
||||
const oldLang = useOldLang();
|
||||
@ -816,7 +821,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
getHtml,
|
||||
setHtml,
|
||||
editedMessage: editingMessage,
|
||||
isDisabled: isInStoryViewer || Boolean(requestedDraft) || isMonoforum,
|
||||
isDisabled: isInStoryViewer || Boolean(requestedDraft) || (!hasSuggestedPost && isMonoforum),
|
||||
});
|
||||
|
||||
const resetComposer = useLastCallback((shouldPreserveInput = false) => {
|
||||
@ -872,6 +877,8 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
}, [disallowedGifts]);
|
||||
|
||||
const shouldShowGiftButton = Boolean(!isChatWithSelf && shouldDisplayGiftsButton && !areAllGiftsDisallowed);
|
||||
const shouldShowSuggestedPostButton = isMonoforum && !editingMessage
|
||||
&& !isForwarding && !isReplying && !draft?.suggestedPostInfo;
|
||||
|
||||
const showCustomEmojiPremiumNotification = useLastCallback(() => {
|
||||
const notificationNumber = customEmojiNotificationNumber.current;
|
||||
@ -1567,6 +1574,11 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
const handleGiftClick = useLastCallback(() => {
|
||||
openGiftModal({ forUserId: chatId });
|
||||
});
|
||||
const handleSuggestPostClick = useLastCallback(() => {
|
||||
updateDraftSuggestedPostInfo({
|
||||
price: { amount: 0, nanos: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
const handleToggleSilentPosting = useLastCallback(() => {
|
||||
const newValue = !isSilentPosting;
|
||||
@ -1638,6 +1650,10 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
}
|
||||
|
||||
if (isReplying && hasSuggestedPost) {
|
||||
return lang('ComposerPlaceholderCaption');
|
||||
}
|
||||
|
||||
if (chat?.adminRights?.anonymous) {
|
||||
return lang('ComposerPlaceholderAnonymous');
|
||||
}
|
||||
@ -1658,7 +1674,8 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
return lang('ComposerPlaceholderNoText');
|
||||
}, [
|
||||
activeVoiceRecording, botKeyboardPlaceholder, chat, inputPlaceholder, isChannel, isComposerBlocked,
|
||||
isInStoryViewer, isSilentPosting, lang, replyToTopic, threadId, windowWidth, paidMessagesStars,
|
||||
isInStoryViewer, isSilentPosting, lang, replyToTopic, isReplying, threadId, windowWidth, paidMessagesStars,
|
||||
hasSuggestedPost,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -2166,6 +2183,17 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
<Icon name="gift" />
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowSuggestedPostButton && (
|
||||
<Button
|
||||
round
|
||||
faded
|
||||
className="composer-action-button"
|
||||
color="translucent"
|
||||
onClick={handleSuggestPostClick}
|
||||
>
|
||||
<Icon name="cash-circle" />
|
||||
</Button>
|
||||
)}
|
||||
{Boolean(botKeyboardMessageId) && !activeVoiceRecording && !editingMessage && (
|
||||
<ResponsiveHoverButton
|
||||
className={buildClassName('composer-action-button', isBotKeyboardOpen && 'activated')}
|
||||
@ -2479,6 +2507,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
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<OwnProps>(
|
||||
botKeyboardMessageId,
|
||||
botKeyboardPlaceholder: keyboardMessage?.keyboardPlaceholder,
|
||||
isForwarding,
|
||||
isReplying,
|
||||
hasSuggestedPost,
|
||||
forwardedMessagesCount: isForwarding ? forwardMessageIds!.length : undefined,
|
||||
pollModal: tabState.pollModal,
|
||||
todoListModal: tabState.todoListModal,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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<OwnProps> = ({
|
||||
className,
|
||||
message,
|
||||
replyInfo,
|
||||
suggestedPostInfo,
|
||||
sender,
|
||||
senderChat,
|
||||
forwardSender,
|
||||
@ -139,6 +144,35 @@ const EmbeddedMessage: FC<OwnProps> = ({
|
||||
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 (
|
||||
<span>
|
||||
{priceText}
|
||||
{scheduleText ? ` • ${scheduleText}` : ''}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (replyInfo?.type === 'message' && replyInfo.quoteText) {
|
||||
return renderTextWithEntities({
|
||||
text: replyInfo.quoteText.text,
|
||||
@ -186,6 +220,14 @@ const EmbeddedMessage: FC<OwnProps> = ({
|
||||
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<OwnProps> = ({
|
||||
mediaThumbnail && 'with-thumb',
|
||||
'no-selection',
|
||||
composerForwardSenders && 'is-input-forward',
|
||||
suggestedPostInfo && 'is-suggested-post',
|
||||
)}
|
||||
dir={lang.isRtl ? 'rtl' : undefined}
|
||||
onClick={handleClick}
|
||||
|
||||
@ -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<OwnProps> = ({
|
||||
}
|
||||
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 (
|
||||
<div
|
||||
className={buildClassName('local-action-message')}
|
||||
key={`suggested-post-action-${message.id}`}
|
||||
>
|
||||
<span className={actionMessageStyles.suggestedPostContainer}>
|
||||
<div
|
||||
className={actionMessageStyles.suggestedPostTitle}
|
||||
>
|
||||
{titleText}
|
||||
</div>
|
||||
{Boolean(tableData.length) && (
|
||||
<MiniTable
|
||||
className={actionMessageStyles.suggestedPostInfo}
|
||||
data={tableData}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const messageCountToAnimate = noAppearanceAnimation ? 0 : messageGroups.reduce((acc, messageGroup) => {
|
||||
return acc + messageGroup.senderGroups.flat().length;
|
||||
}, 0);
|
||||
@ -268,6 +322,7 @@ const MessageListContent: FC<OwnProps> = ({
|
||||
return compact([
|
||||
message.id === memoUnreadDividerBeforeIdRef.current && unreadDivider,
|
||||
message.paidMessageStars && !withUsers && renderPaidMessageAction(message, album),
|
||||
message.suggestedPostInfo && renderSuggestedPostInfoAction(message),
|
||||
<Message
|
||||
key={key}
|
||||
message={message}
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
|
||||
import type {
|
||||
ApiChat, ApiInputMessageReplyInfo, ApiMessage, ApiPeer,
|
||||
ApiChat, ApiInputMessageReplyInfo, ApiInputSuggestedPostInfo, ApiMessage, ApiPeer,
|
||||
} from '../../../api/types';
|
||||
import type { MessageListType, ThreadId } from '../../../types/index';
|
||||
|
||||
@ -48,6 +48,7 @@ import './ComposerEmbeddedMessage.scss';
|
||||
|
||||
type StateProps = {
|
||||
replyInfo?: ApiInputMessageReplyInfo;
|
||||
suggestedPostInfo?: ApiInputSuggestedPostInfo;
|
||||
editingId?: number;
|
||||
message?: ApiMessage;
|
||||
sender?: ApiPeer;
|
||||
@ -80,6 +81,7 @@ const CLOSE_DURATION = 350;
|
||||
|
||||
const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||
replyInfo,
|
||||
suggestedPostInfo,
|
||||
editingId,
|
||||
message,
|
||||
sender,
|
||||
@ -104,6 +106,7 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||
}) => {
|
||||
const {
|
||||
resetDraftReplyInfo,
|
||||
resetDraftSuggestedPostInfo,
|
||||
updateDraftReplyInfo,
|
||||
setEditingId,
|
||||
focusMessage,
|
||||
@ -113,6 +116,7 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||
setForwardNoCaptions,
|
||||
exitForwardMode,
|
||||
setShouldPreventComposerAnimation,
|
||||
openSuggestMessageModal,
|
||||
} = getActions();
|
||||
const ref = useRef<HTMLDivElement>();
|
||||
const oldLang = useOldLang();
|
||||
@ -121,6 +125,7 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||
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<OwnProps & StateProps> = ({
|
||||
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<OwnProps & StateProps> = ({
|
||||
setEditingId({ messageId: undefined });
|
||||
} else if (forwardedMessagesCount) {
|
||||
exitForwardMode();
|
||||
} else if (isShowingSuggestedPost) {
|
||||
resetDraftSuggestedPostInfo();
|
||||
resetDraftReplyInfo();
|
||||
} else if (replyInfo && !shouldForceShowEditing) {
|
||||
resetDraftReplyInfo();
|
||||
}
|
||||
@ -187,6 +196,10 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||
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<OwnProps & StateProps> = ({
|
||||
if (editingId) {
|
||||
return 'edit';
|
||||
}
|
||||
if (isShowingSuggestedPost) {
|
||||
return 'cash-circle';
|
||||
}
|
||||
if (isForwarding) {
|
||||
return 'forward';
|
||||
}
|
||||
@ -244,7 +260,7 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [editingId, isForwarding, isShowingReply]);
|
||||
}, [editingId, isForwarding, isShowingReply, isShowingSuggestedPost]);
|
||||
|
||||
const customText = forwardedMessagesCount && forwardedMessagesCount > 1
|
||||
? oldLang('ForwardedMessageCount', forwardedMessagesCount)
|
||||
@ -284,6 +300,7 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||
isOpen={isShown}
|
||||
className="inside-input"
|
||||
replyInfo={replyInfo}
|
||||
suggestedPostInfo={suggestedPostInfo}
|
||||
isInComposer
|
||||
message={strippedMessage}
|
||||
sender={!noAuthors ? sender : undefined}
|
||||
@ -425,6 +442,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
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<OwnProps>(
|
||||
|
||||
return {
|
||||
replyInfo,
|
||||
suggestedPostInfo,
|
||||
editingId,
|
||||
message,
|
||||
sender,
|
||||
|
||||
@ -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<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
|
||||
if (messageInput) {
|
||||
focusEditableElement(messageInput, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const customEmojiIds = draft.text?.entities
|
||||
?.map((entity) => entity.type === ApiMessageEntityTypes.CustomEmoji && entity.documentId)
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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<ApiMessageAction['type']>([
|
||||
'todoAppendTasks',
|
||||
'unsupported',
|
||||
]);
|
||||
const HIDDEN_TEXT_ACTIONS = new Set<ApiMessageAction['type']>(['giftCode', 'prizeStars', 'suggestProfilePhoto']);
|
||||
const HIDDEN_TEXT_ACTIONS = new Set<ApiMessageAction['type']>(['giftCode', 'prizeStars',
|
||||
'suggestProfilePhoto', 'suggestedPostApproval']);
|
||||
|
||||
const ActionMessage = ({
|
||||
message,
|
||||
@ -139,6 +142,7 @@ const ActionMessage = ({
|
||||
toggleChannelRecommendations,
|
||||
animateUnreadReaction,
|
||||
markMentionsRead,
|
||||
focusMessage,
|
||||
} = getActions();
|
||||
|
||||
const ref = useRef<HTMLDivElement>();
|
||||
@ -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 (
|
||||
<SuggestedPostBalanceTooLow
|
||||
message={message}
|
||||
action={action}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return action.isRejected ? (
|
||||
<SuggestedPostRejected
|
||||
message={message}
|
||||
action={action}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
) : (
|
||||
<SuggestedPostApproval
|
||||
message={message}
|
||||
action={action}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
@ -421,13 +474,13 @@ const ActionMessage = ({
|
||||
{!isTextHidden && (
|
||||
<>
|
||||
{isFluidMultiline && (
|
||||
<div className={styles.inlineWrapper}>
|
||||
<div className={buildClassName(styles.inlineWrapper, isClickableText && styles.hoverable)}>
|
||||
<span className={styles.fluidBackground} style={fluidBackgroundStyle}>
|
||||
<ActionMessageText message={message} isInsideTopic={isInsideTopic} />
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.inlineWrapper}>
|
||||
<div className={buildClassName(styles.inlineWrapper, isClickableText && styles.hoverable)}>
|
||||
<span className={styles.textContent} onClick={handleClick}>
|
||||
<ActionMessageText message={message} isInsideTopic={isInsideTopic} />
|
||||
</span>
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -59,6 +59,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.left-icon {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.inline-button-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@ -43,6 +43,17 @@ const InlineButtons: FC<OwnProps> = ({ message, onClick }) => {
|
||||
return <Icon className="corner-icon" name="webapp" />;
|
||||
case 'copy':
|
||||
return <Icon className="corner-icon" name="copy" />;
|
||||
case 'suggestedMessage':
|
||||
if (button.buttonType === 'suggestChanges') {
|
||||
return <Icon className="left-icon" name="edit" />;
|
||||
}
|
||||
if (button.buttonType === 'approve') {
|
||||
return <Icon className="left-icon" name="check" />;
|
||||
}
|
||||
if (button.buttonType === 'decline') {
|
||||
return <Icon className="left-icon" name="close" />;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
@ -63,14 +74,14 @@ const InlineButtons: FC<OwnProps> = ({ message, onClick }) => {
|
||||
<Button
|
||||
size="tiny"
|
||||
ripple
|
||||
disabled={button.type === 'unsupported'}
|
||||
disabled={button.type === 'unsupported' || (button.type === 'suggestedMessage' && button.disabled)}
|
||||
|
||||
onClick={() => onClick({ chatId: message.chatId, messageId: message.id, button })}
|
||||
>
|
||||
{renderIcon(button)}
|
||||
<span className="inline-button-text">
|
||||
{buttonTexts[i][j]}
|
||||
</span>
|
||||
{renderIcon(button)}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -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<OwnProps & StateProps> = ({
|
||||
message,
|
||||
@ -434,10 +441,14 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
paidMessageStars,
|
||||
isChatWithUser,
|
||||
isAccountFrozen,
|
||||
minFutureTime,
|
||||
}) => {
|
||||
const {
|
||||
toggleMessageSelection,
|
||||
clickBotInlineButton,
|
||||
clickSuggestedMessageButton,
|
||||
rejectSuggestedPost,
|
||||
openSuggestedPostApprovalModal,
|
||||
disableContextMenuHint,
|
||||
animateUnreadReaction,
|
||||
focusLastMessage,
|
||||
@ -448,12 +459,15 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
const bottomMarkerRef = useRef<HTMLDivElement>();
|
||||
const quickReactionRef = useRef<HTMLDivElement>();
|
||||
|
||||
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<OwnProps & StateProps> = ({
|
||||
handleTopicChipClick,
|
||||
handleStoryClick,
|
||||
} = useInnerHandlers({
|
||||
lang,
|
||||
lang: oldLang,
|
||||
selectMessage,
|
||||
message,
|
||||
chatId,
|
||||
@ -857,7 +871,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
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<OwnProps & StateProps> = ({
|
||||
)}
|
||||
dir="auto"
|
||||
>
|
||||
{(isTranscriptionError ? lang('NoWordsRecognized') : (
|
||||
{(isTranscriptionError ? oldLang('NoWordsRecognized') : (
|
||||
isTranscribing && transcribedText ? <DotAnimation content={transcribedText} /> : transcribedText
|
||||
))}
|
||||
</p>
|
||||
@ -1482,24 +1496,59 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
)}
|
||||
{asForwarded && (
|
||||
<span className="forward-title">
|
||||
{lang('ForwardedFrom')}
|
||||
{oldLang('ForwardedFrom')}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
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<HTMLInputElement>) => {
|
||||
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<OwnProps & StateProps> = ({
|
||||
) : undefined}
|
||||
{botSender?.hasUsername && (
|
||||
<span className="interactive">
|
||||
<span className="via">{lang('ViaBot')}</span>
|
||||
<span className="via">{oldLang('ViaBot')}</span>
|
||||
<span
|
||||
className="sender-title"
|
||||
onClick={handleViaBotClick}
|
||||
@ -1562,12 +1611,12 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
)}
|
||||
<div className="title-spacer" />
|
||||
{!shouldSkipRenderAdminTitle && !hasBotSenderUsername ? (forwardInfo?.isLinkedChannelPost ? (
|
||||
<span className="admin-title" dir="auto">{lang('DiscussChannel')}</span>
|
||||
<span className="admin-title" dir="auto">{oldLang('DiscussChannel')}</span>
|
||||
) : message.postAuthorTitle && isGroup && !asForwarded ? (
|
||||
<span className="admin-title" dir="auto">{message.postAuthorTitle}</span>
|
||||
) : senderAdminMember && !asForwarded && !viaBotId ? (
|
||||
<span className="admin-title" dir="auto">
|
||||
{senderAdminMember.customTitle || lang(
|
||||
{senderAdminMember.customTitle || oldLang(
|
||||
senderAdminMember.isOwner ? 'GroupInfo.LabelOwner' : 'GroupInfo.LabelAdmin',
|
||||
)}
|
||||
</span>
|
||||
@ -1583,6 +1632,14 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
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 (
|
||||
<div
|
||||
@ -1666,7 +1723,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
color="translucent-white"
|
||||
round
|
||||
size="tiny"
|
||||
ariaLabel={lang('lng_context_forward_msg')}
|
||||
ariaLabel={oldLang('lng_context_forward_msg')}
|
||||
onClick={isLastInDocumentGroup ? handleGroupForward : handleForward}
|
||||
>
|
||||
<Icon name="share-filled" />
|
||||
@ -1699,6 +1756,36 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
{message.inlineButtons && (
|
||||
<InlineButtons message={message} onClick={clickBotInlineButton} />
|
||||
)}
|
||||
{shouldRenderSuggestedPostButtons && (
|
||||
<InlineButtons
|
||||
message={{
|
||||
...message,
|
||||
inlineButtons: [
|
||||
[
|
||||
{
|
||||
type: 'suggestedMessage',
|
||||
buttonType: 'decline',
|
||||
text: lang('SuggestedPostDecline'),
|
||||
},
|
||||
{
|
||||
type: 'suggestedMessage',
|
||||
buttonType: 'approve',
|
||||
text: lang('SuggestedPostApprove'),
|
||||
disabled: isSuggestedPostExpired,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
type: 'suggestedMessage',
|
||||
buttonType: 'suggestChanges',
|
||||
text: lang('SuggestedPostSuggestChanges'),
|
||||
},
|
||||
],
|
||||
],
|
||||
}}
|
||||
onClick={handleSuggestedMessageButton}
|
||||
/>
|
||||
)}
|
||||
{reactionsPosition === 'outside' && !isStoryMention && (
|
||||
<Reactions
|
||||
message={reactionMessage!}
|
||||
@ -1728,6 +1815,28 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
detectedLanguage={detectedLanguage}
|
||||
/>
|
||||
)}
|
||||
{isDeclineDialogOpen && (
|
||||
<ConfirmDialog
|
||||
isOpen={isDeclineDialogOpen}
|
||||
onClose={closeDeclineDialog}
|
||||
title={lang('SuggestedPostDecline')}
|
||||
confirmLabel={lang('SuggestedPostDecline')}
|
||||
confirmHandler={handleDeclineConfirm}
|
||||
confirmIsDestructive
|
||||
>
|
||||
<div className="decline-dialog-question">
|
||||
{renderText(lang('DeclinePostDialogQuestion', {
|
||||
sender: sender ? getPeerFullTitle(oldLang, sender) : '',
|
||||
}, { withNodes: true, withMarkdown: true }))}
|
||||
</div>
|
||||
<InputText
|
||||
placeholder={lang('DeclineReasonPlaceholder')}
|
||||
value={declineReason}
|
||||
onChange={handleDeclineReasonChange}
|
||||
maxLength={MAX_REASON_LENGTH}
|
||||
/>
|
||||
</ConfirmDialog>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -1871,6 +1980,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
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<OwnProps>(
|
||||
tags: global.savedReactionTags?.byKey,
|
||||
canTranscribeVoice,
|
||||
viaBusinessBot,
|
||||
minFutureTime,
|
||||
effect,
|
||||
poll,
|
||||
maxTimestamp,
|
||||
|
||||
@ -20,6 +20,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.decline-dialog-question {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
position: relative;
|
||||
max-width: var(--max-width);
|
||||
|
||||
148
src/components/middle/message/actions/SuggestedPostApproval.tsx
Normal file
148
src/components/middle/message/actions/SuggestedPostApproval.tsx
Normal file
@ -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 (
|
||||
<div
|
||||
className={buildClassName(styles.contentBox, styles.suggestedPostContentBox)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className={styles.suggestedPostApprovalTitle}>
|
||||
{renderText(lang('SuggestedPostAgreementReached'))}
|
||||
</div>
|
||||
|
||||
<div className={styles.suggestedPostApprovalSection}>
|
||||
{translateWithYou(
|
||||
lang,
|
||||
isPostPublished ? 'SuggestedPostPublished' : 'SuggestedPostPublishSchedule',
|
||||
!isAdmin,
|
||||
{ peer: renderChatLink(), date: publishDate },
|
||||
{ withMarkdown: true },
|
||||
)}
|
||||
</div>
|
||||
|
||||
{starsText && (
|
||||
<div className={styles.suggestedPostApprovalSection}>
|
||||
{translateWithYou(lang,
|
||||
'SuggestedPostCharged',
|
||||
!isAdmin,
|
||||
{
|
||||
user: originalSenderLink,
|
||||
amount: starsText,
|
||||
},
|
||||
{ withMarkdown: true },
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isPostPublished && starsText && (
|
||||
<>
|
||||
<div className={styles.suggestedPostApprovalSection}>
|
||||
{translateWithYou(lang, 'SuggestedPostReceiveAmount', !isAdmin, {
|
||||
peer: renderChatLink(), duration, currency: lang('CurrencyStars'),
|
||||
}, { withMarkdown: true })}
|
||||
</div>
|
||||
|
||||
<div className={styles.suggestedPostApprovalSection}>
|
||||
{translateWithYou(lang, 'SuggestedPostRefund', !isAdmin, {
|
||||
peer: renderChatLink(), duration, currency: lang('CurrencyStars'),
|
||||
}, { withMarkdown: true })}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(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));
|
||||
@ -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 (
|
||||
<div
|
||||
className={buildClassName(styles.contentBox, styles.suggestedPostBalanceTooLowBox)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className={styles.suggestedPostBalanceTooLowTitle}>
|
||||
{lang('SuggestedPostBalanceTooLow', {
|
||||
peer: peerLink,
|
||||
currency: lang('CurrencyStars'),
|
||||
}, { withNodes: true, withMarkdown: true })}
|
||||
</div>
|
||||
|
||||
{!message.isOutgoing && (
|
||||
<div className={styles.actionButton} onClick={handleGetMoreStars}>
|
||||
<Sparkles preset="button" />
|
||||
{lang('ButtonBuyStars')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(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));
|
||||
@ -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 (
|
||||
<div
|
||||
className={buildClassName(styles.contentBox, styles.suggestedPostRejectedContentBox)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className={styles.suggestedPostRejectedTitle}>
|
||||
<Icon className={styles.rejectedIcon} name="close" />
|
||||
{translateWithYou(
|
||||
lang,
|
||||
rejectComment ? 'SuggestedPostRejectedWithReason' : 'SuggestedPostRejected',
|
||||
isOutgoing,
|
||||
{ peer: senderLink },
|
||||
{ withMarkdown: true },
|
||||
)}
|
||||
</div>
|
||||
|
||||
{rejectComment && (
|
||||
<div className={styles.suggestedPostRejectedComment}>
|
||||
{lang('SuggestedPostRejectedComment', { comment: rejectComment })}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { message }): StateProps => {
|
||||
const sender = selectSender(global, message);
|
||||
|
||||
return {
|
||||
sender,
|
||||
};
|
||||
},
|
||||
)(SuggestedPostRejected));
|
||||
@ -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<TabState,
|
||||
'starsPayment' |
|
||||
'starsTransactionModal' |
|
||||
'paidReactionModal' |
|
||||
'suggestMessageModal' |
|
||||
'suggestedPostApprovalModal' |
|
||||
'webApps' |
|
||||
'chatInviteModal' |
|
||||
'starsSubscriptionModal' |
|
||||
@ -118,6 +122,8 @@ const MODALS: ModalRegistry = {
|
||||
starsTransactionModal: StarsTransactionInfoModal,
|
||||
chatInviteModal: ChatInviteModal,
|
||||
paidReactionModal: PaidReactionModal,
|
||||
suggestMessageModal: SuggestMessageModal,
|
||||
suggestedPostApprovalModal: SuggestedPostApprovalModal,
|
||||
starsSubscriptionModal: StarsSubscriptionModal,
|
||||
starsGiftModal: StarsGiftModal,
|
||||
giftModal: PremiumGiftModal,
|
||||
|
||||
@ -11,6 +11,23 @@
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.subtitleBadge {
|
||||
margin-top: 0.25rem;
|
||||
padding: 0.75rem;
|
||||
padding-block: 0.125rem;
|
||||
border-radius: 1rem;
|
||||
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(50px);
|
||||
|
||||
transition: color 150ms ease-in, background-color 0.15s !important;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.radialPattern {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import type { TeactNode } from '../../../lib/teact/teact';
|
||||
import { memo, useMemo } from '../../../lib/teact/teact';
|
||||
import { getActions } from '../../../global';
|
||||
|
||||
import type {
|
||||
ApiPeer,
|
||||
ApiStarGiftAttributeBackdrop, ApiStarGiftAttributeModel, ApiStarGiftAttributePattern,
|
||||
ApiStarsAmount,
|
||||
} from '../../../api/types';
|
||||
ApiStarsAmount } from '../../../api/types';
|
||||
|
||||
import {
|
||||
formatStarsTransactionAmount,
|
||||
@ -26,7 +28,8 @@ type OwnProps = {
|
||||
backdropAttribute: ApiStarGiftAttributeBackdrop;
|
||||
patternAttribute: ApiStarGiftAttributePattern;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
subtitle?: TeactNode;
|
||||
subtitlePeer?: ApiPeer;
|
||||
className?: string;
|
||||
resellPrice?: ApiStarsAmount;
|
||||
};
|
||||
@ -39,9 +42,14 @@ const UniqueGiftHeader = ({
|
||||
patternAttribute,
|
||||
title,
|
||||
subtitle,
|
||||
subtitlePeer,
|
||||
className,
|
||||
resellPrice,
|
||||
}: OwnProps) => {
|
||||
const {
|
||||
openChat,
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
const activeKey = useTransitionActiveKey([modelAttribute, backdropAttribute, patternAttribute]);
|
||||
const subtitleColor = backdropAttribute?.textColor;
|
||||
@ -77,10 +85,18 @@ const UniqueGiftHeader = ({
|
||||
/>
|
||||
</Transition>
|
||||
{title && <h1 className={styles.title}>{title}</h1>}
|
||||
{subtitle && (
|
||||
<p className={styles.subtitle} style={buildStyle(subtitleColor && `color: ${subtitleColor}`)}>
|
||||
{Boolean(subtitle) && (
|
||||
<div
|
||||
className={buildClassName(styles.subtitle, subtitlePeer && styles.subtitleBadge)}
|
||||
style={buildStyle(subtitleColor && `color: ${subtitleColor}`)}
|
||||
onClick={() => {
|
||||
if (subtitlePeer) {
|
||||
openChat({ id: subtitlePeer.id });
|
||||
}
|
||||
}}
|
||||
>
|
||||
{subtitle}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{resellPrice && (
|
||||
<p className={styles.amount}>
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@ -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 = ({
|
||||
<Icon className={styles.copyIcon} name="copy" />
|
||||
</span>,
|
||||
]);
|
||||
} 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<OwnProps>(
|
||||
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,
|
||||
|
||||
@ -40,6 +40,7 @@ const ChatRefundModal = ({ modal, user }: OwnProps & StateProps) => {
|
||||
const handleConfirmRemoveFee = useLastCallback(() => {
|
||||
closeChatRefundModal();
|
||||
if (!userId) return;
|
||||
|
||||
toggleNoPaidMessagesException ({ userId, shouldRefundCharged: shouldRefundStars });
|
||||
});
|
||||
|
||||
|
||||
@ -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<OwnProps> = (props) => {
|
||||
const { modal } = props;
|
||||
const SuggestMessageModal = useModuleLoader(Bundles.Extra, 'SuggestMessageModal', !modal);
|
||||
|
||||
return SuggestMessageModal ? <SuggestMessageModal {...props} /> : <Loading />;
|
||||
};
|
||||
|
||||
export default SuggestMessageModalAsync;
|
||||
@ -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);
|
||||
}
|
||||
256
src/components/modals/suggestMessage/SuggestMessageModal.tsx
Normal file
256
src/components/modals/suggestMessage/SuggestMessageModal.tsx
Normal file
@ -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<number | undefined>(
|
||||
currentSuggestedPostInfo?.price?.amount || undefined,
|
||||
);
|
||||
const [scheduleDate, setScheduleDate] = useState<number | undefined>(
|
||||
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<HTMLInputElement>) => {
|
||||
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 (
|
||||
<Modal
|
||||
headerClassName={styles.modalHeader}
|
||||
isOpen={Boolean(modal)}
|
||||
onClose={closeSuggestMessageModal}
|
||||
isSlim
|
||||
isLowStackPriority
|
||||
hasCloseButton
|
||||
contentClassName={styles.content}
|
||||
title={isInSuggestChangesMode ? lang('TitleSuggestedChanges') : lang('TitleSuggestMessage')}
|
||||
>
|
||||
<div className={styles.form}>
|
||||
<div className={styles.section}>
|
||||
<InputText
|
||||
label={lang('InputPlaceholderPrice')}
|
||||
className={buildClassName(styles.input)}
|
||||
value={starsAmount?.toString()}
|
||||
onChange={handleAmountChange}
|
||||
inputMode="numeric"
|
||||
tabIndex={0}
|
||||
teactExperimentControlled
|
||||
/>
|
||||
<div className={styles.description}>
|
||||
{starsAmount !== undefined && starsAmount > 0 && starsAmount < minAmount
|
||||
? lang('DescriptionSuggestedPostMinimumOffer', {
|
||||
amount: formatStarsAsText(lang, minAmount) },
|
||||
{ withNodes: true, withMarkdown: true })
|
||||
: lang('SuggestMessagePriceDescription', {
|
||||
currency: lang('CurrencyStars'),
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.section}>
|
||||
<div className={buildClassName('input-group', 'touched')}>
|
||||
<input
|
||||
type="text"
|
||||
className={buildClassName('form-control', isCalendarOpened && 'focus')}
|
||||
value={scheduleDate ? formatScheduledDateTime(scheduleDate / 1000, lang, oldLang) : lang('TitleAnytime')}
|
||||
autoComplete="off"
|
||||
onClick={openCalendar}
|
||||
onFocus={openCalendar}
|
||||
readOnly
|
||||
/>
|
||||
<label>{lang('InputTitleSuggestMessageTime')}</label>
|
||||
<Icon name="down" className={styles.timeInputIcon} />
|
||||
</div>
|
||||
<div className={styles.description}>
|
||||
{lang('SuggestMessageTimeDescription', {
|
||||
hint: lang('SuggestMessageDateTimeHint'),
|
||||
duration: formatShortDuration(lang, ageMinSeconds, true),
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CalendarModal
|
||||
isOpen={isCalendarOpened}
|
||||
isFutureMode
|
||||
withTimePicker
|
||||
minAt={minAt}
|
||||
maxAt={maxAt}
|
||||
onClose={closeCalendar}
|
||||
onSubmit={handleExpireDateChange}
|
||||
selectedAt={scheduleDate || defaultSelectedTime}
|
||||
submitButtonLabel={lang('Save')}
|
||||
secondButtonLabel={lang('TitleAnytime')}
|
||||
onSecondButtonClick={handleAnytimeClick}
|
||||
description={lang('SuggestMessageDateTimeHint')}
|
||||
/>
|
||||
|
||||
<Button
|
||||
className={styles.offerButton}
|
||||
onClick={handleOffer}
|
||||
size="smaller"
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{isInSuggestChangesMode ? lang('ButtonUpdateTerms')
|
||||
: starsAmount ? lang('ButtonOfferAmount', {
|
||||
amount: formatStarsAsIcon(lang, starsAmount, { asFont: true }),
|
||||
}, {
|
||||
withNodes: true,
|
||||
withMarkdown: true,
|
||||
}) : lang('ButtonOfferFree')}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(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));
|
||||
@ -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<OwnProps> = (props) => {
|
||||
const { modal } = props;
|
||||
const SuggestedPostApprovalModal = useModuleLoader(Bundles.Extra, 'SuggestedPostApprovalModal', !modal);
|
||||
|
||||
return SuggestedPostApprovalModal ? <SuggestedPostApprovalModal {...props} /> : <Loading />;
|
||||
};
|
||||
|
||||
export default SuggestedPostApprovalModalAsync;
|
||||
@ -0,0 +1,3 @@
|
||||
.details {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
@ -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<number>(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 (
|
||||
<>
|
||||
<div>
|
||||
{questionText}
|
||||
</div>
|
||||
<div className={styles.details}>
|
||||
{renderText(lang(key, {
|
||||
amount: starsText,
|
||||
commission,
|
||||
duration,
|
||||
time,
|
||||
}, { withNodes: true, withMarkdown: true }))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const key = isAdmin ? 'SuggestedPostConfirmDetailsAdmin' : 'SuggestedPostConfirmDetailsUser';
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
{questionText}
|
||||
</div>
|
||||
<div className={styles.details}>
|
||||
{renderText(lang(key, {
|
||||
amount: starsText,
|
||||
commission,
|
||||
duration,
|
||||
}, { withNodes: true, withMarkdown: true }))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConfirmDialog
|
||||
isOpen={Boolean(modal) && !isCalendarOpened}
|
||||
onClose={handleClose}
|
||||
title={lang('SuggestedPostConfirmTitle')}
|
||||
confirmHandler={handleNext}
|
||||
confirmLabel={scheduleDate ? lang('ButtonPublish') : lang('Next')}
|
||||
>
|
||||
{renderContent()}
|
||||
</ConfirmDialog>
|
||||
|
||||
<CalendarModal
|
||||
isOpen={isCalendarOpened}
|
||||
isFutureMode
|
||||
withTimePicker
|
||||
minAt={minAt}
|
||||
maxAt={maxAt}
|
||||
onClose={closeCalendar}
|
||||
onSubmit={handleCalendarSubmit}
|
||||
onDateChange={handleCalendarDateChange}
|
||||
selectedAt={selectedScheduleDate * 1000}
|
||||
submitButtonLabel={lang('ButtonPublishAtTime', {
|
||||
time: formatScheduledDateTime(selectedScheduleDate, lang, oldLang),
|
||||
})}
|
||||
secondButtonLabel={lang('PublishNow')}
|
||||
onSecondButtonClick={handlePublishNow}
|
||||
description={lang('SuggestMessageDateTimeHint')}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(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));
|
||||
@ -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;
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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<void>
|
||||
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<void>
|
||||
|
||||
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<T extends GlobalState>(
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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<T extends GlobalState>(global: T, chatId: string, offsetId?: number) {
|
||||
const chat = selectChat(global, chatId);
|
||||
if (!chat) return;
|
||||
|
||||
@ -99,7 +99,9 @@ addActionHandler('loadStarGifts', async (global): Promise<void> => {
|
||||
return;
|
||||
}
|
||||
|
||||
const byId = buildCollectionByKey(result, 'id');
|
||||
global = getGlobal();
|
||||
|
||||
const byId = buildCollectionByKey(result.gifts, 'id');
|
||||
|
||||
const idsByCategoryName: Record<StarGiftCategory, string[]> = {
|
||||
all: [],
|
||||
@ -134,7 +136,6 @@ addActionHandler('loadStarGifts', async (global): Promise<void> => {
|
||||
idsByCategoryName[starsCategory].push(gift.id);
|
||||
});
|
||||
|
||||
global = getGlobal();
|
||||
global = {
|
||||
...global,
|
||||
starGifts: {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@ -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<ApiInputMessageReplyInfo> & WithTabId;
|
||||
resetDraftReplyInfo: WithTabId | undefined;
|
||||
updateDraftSuggestedPostInfo: Partial<ApiInputSuggestedPostInfo> & 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[];
|
||||
|
||||
@ -758,6 +758,16 @@ export type TabState = {
|
||||
messageId: number;
|
||||
};
|
||||
|
||||
suggestMessageModal?: {
|
||||
chatId: string;
|
||||
messageId?: number;
|
||||
};
|
||||
|
||||
suggestedPostApprovalModal?: {
|
||||
chatId: string;
|
||||
messageId: number;
|
||||
};
|
||||
|
||||
inviteViaLinkModal?: {
|
||||
missingUsers: ApiMissingInvitedUser[];
|
||||
chatId: string;
|
||||
|
||||
@ -12,5 +12,6 @@ for (const tl of Object.values(Api)) {
|
||||
}
|
||||
}
|
||||
|
||||
export const LAYER = 205;
|
||||
export const LAYER = 207;
|
||||
|
||||
export { tlobjects };
|
||||
|
||||
363
src/lib/gramjs/tl/api.d.ts
vendored
363
src/lib/gramjs/tl/api.d.ts
vendored
File diff suppressed because one or more lines are too long
@ -93,7 +93,7 @@ chatParticipants#3cbc93f8 chat_id:long participants:Vector<ChatParticipant> 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<MessageEntity> 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<RestrictionReason> 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<MessageEntity> 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<RestrictionReason> 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<Peer> = MessageAction;
|
||||
messageActionTodoCompletions#cc7c5c89 completed:Vector<int> incompleted:Vector<int> = MessageAction;
|
||||
messageActionTodoAppendTasks#c7edbc83 list:Vector<TodoItem> = 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<int> 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<bytes> 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<Message> new_encrypted_messages:Vector<EncryptedMessage> other_updates:Vector<Update> chats:Vector<Chat> users:Vector<User> 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<PhotoSize> 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<StickerPack> keywords:Vector<StickerKeyword> documents:Vector<Document> = messages.StickerSet;
|
||||
messages.stickerSetNotModified#d3f924eb = messages.StickerSet;
|
||||
@ -689,7 +694,7 @@ contacts.topPeersNotModified#de266ef5 = contacts.TopPeers;
|
||||
contacts.topPeers#70b772a8 categories:Vector<TopPeerCategoryPeers> chats:Vector<Chat> users:Vector<User> = 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<MessageEntity> 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<MessageEntity> 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<StickerSetCovered> unread:Vector<long> = 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<PollAnswerVoters> total_voters:flags.2?int recent_voters:flags.3?Vector<Peer> solution:flags.4?string solution_entities:flags.4?Vector<MessageEntity> = 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<SponsoredMessageReportOption> = 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<BroadcastRevenueTransaction> = 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<AvailableEffect> documents:Vector<Document> = 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<MessageMedia> 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<MessageMedia> 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<StarsSubscription> subscriptions_next_offset:flags.2?string subscriptions_missing_balance:flags.4?long history:flags.3?Vector<StarsTransaction> next_offset:flags.0?string chats:Vector<Chat> users:Vector<User> = payments.StarsStatus;
|
||||
foundStory#e87acbc0 peer:Peer story:StoryItem = FoundStory;
|
||||
stories.foundStories#e2de7737 flags:# count:int stories:Vector<FoundStory> next_offset:flags.0?string chats:Vector<Chat> users:Vector<User> = 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<StarsGiveawayWinnersOption> = 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<StarGiftAttribute> 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<StarGiftAttribute> 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<StarGift> = payments.StarGifts;
|
||||
payments.starGifts#2ed82995 hash:int gifts:Vector<StarGift> chats:Vector<Chat> users:Vector<User> = payments.StarGifts;
|
||||
messageReportOption#7903e3d9 text:string option:bytes = MessageReportOption;
|
||||
reportResultChooseOption#f0e4e0b6 title:string options:Vector<MessageReportOption> = 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<ConnectedBotStarRef> users:Vector<User> = payments.ConnectedStarRefBots;
|
||||
payments.suggestedStarRefBots#b4d5d859 flags:# count:int suggested_bots:Vector<StarRefProgram> users:Vector<User> 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<Document> = 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<TodoItem> = 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<InputUser> = Vector<User>;
|
||||
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<int> = messages.AffectedMessages;
|
||||
messages.receivedMessages#5a954c0 max_id:int = Vector<ReceivedNotifyMessage>;
|
||||
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<MessageEntity> 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<MessageEntity> 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<int> random_id:Vector<long> 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<MessageEntity> 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<MessageEntity> 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<int> random_id:Vector<long> 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<int> 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<MessageEntity> 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<InputDialogPeer> = 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<MessageEntity> 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<MessageEntity> 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<long> = 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<int> = Bool;
|
||||
messages.toggleTodoCompleted#d3e03124 peer:InputPeer msg_id:int completed:Vector<int> incompleted:Vector<int> = Updates;
|
||||
messages.appendTodoList#21a61057 peer:InputPeer msg_id:int list:Vector<TodoItem> = 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<StarsTopupOption>;
|
||||
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<InputStarsTransaction> = payments.StarsStatus;
|
||||
payments.getStarsTransactionsByID#2dca16b8 flags:# ton:flags.0?true peer:InputPeer id:Vector<InputStarsTransaction> = payments.StarsStatus;
|
||||
payments.getStarsGiftOptions#d3c96bc8 flags:# user_id:flags.0?InputUser = Vector<StarsGiftOption>;
|
||||
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<InputPeer> = 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<InputPeer> = ExportedChatlistInvite;
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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<MessageEntity> 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<RestrictionReason> 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<MessageEntity> 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<RestrictionReason> 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<Peer> = MessageAction;
|
||||
messageActionTodoCompletions#cc7c5c89 completed:Vector<int> incompleted:Vector<int> = MessageAction;
|
||||
messageActionTodoAppendTasks#c7edbc83 list:Vector<TodoItem> = 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<int> 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<bytes> 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<PhotoSize> 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<TopPeerCategoryPeers> 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<MessageEntity> 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<MessageEntity> 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<StickerSetCovered> unread:Vector<long> = 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<BroadcastRevenueTransaction> = 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<MessageMedia> 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<MessageMedia> 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<StarsSubscription> subscriptions_next_offset:flags.2?string subscriptions_missing_balance:flags.4?long history:flags.3?Vector<StarsTransaction> next_offset:flags.0?string chats:Vector<Chat> users:Vector<User> = 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<StarGiftAttribute> 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<StarGiftAttribute> 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<StarGift> = payments.StarGifts;
|
||||
payments.starGifts#2ed82995 hash:int gifts:Vector<StarGift> chats:Vector<Chat> users:Vector<User> = payments.StarGifts;
|
||||
|
||||
messageReportOption#7903e3d9 text:string option:bytes = MessageReportOption;
|
||||
|
||||
@ -1924,6 +1917,7 @@ payments.connectedStarRefBots#98d5ea1d count:int connected_bots:Vector<Connected
|
||||
payments.suggestedStarRefBots#b4d5d859 flags:# count:int suggested_bots:Vector<StarRefProgram> users:Vector<User> 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<Document> = 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<int> = messages.AffectedMessages;
|
||||
messages.receivedMessages#5a954c0 max_id:int = Vector<ReceivedNotifyMessage>;
|
||||
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<MessageEntity> 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<MessageEntity> 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<int> random_id:Vector<long> 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<MessageEntity> 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<MessageEntity> 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<int> random_id:Vector<long> 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<int> 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<InputDialogPeer> = 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<MessageEntity> 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<MessageEntity> 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<long> = 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<int> incompleted:Vector<int> = Updates;
|
||||
messages.appendTodoList#21a61057 peer:InputPeer msg_id:int list:Vector<TodoItem> = 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<StarsTopupOption>;
|
||||
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<InputStarsTransaction> = payments.StarsStatus;
|
||||
payments.getStarsTransactionsByID#2dca16b8 flags:# ton:flags.0?true peer:InputPeer id:Vector<InputStarsTransaction> = payments.StarsStatus;
|
||||
payments.getStarsGiftOptions#d3c96bc8 flags:# user_id:flags.0?InputUser = Vector<StarsGiftOption>;
|
||||
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<InputPeer> = chatlists.ExportedChatlistInvite;
|
||||
chatlists.deleteExportedInvite#719c5c5e chatlist:InputChatlist slug:string = Bool;
|
||||
|
||||
@ -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 = {
|
||||
|
||||
162
src/types/language.d.ts
vendored
162
src/types/language.d.ts
vendored
@ -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<V = LangVariable> {
|
||||
'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<V = LangVariable> {
|
||||
'HintTodoListTasksCount': {
|
||||
'count': V;
|
||||
};
|
||||
'GiftInfoCollectibleBy': {
|
||||
'number': V;
|
||||
'owner': V;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LangPairPlural {
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user