Support Channel direct messages (#5990)

This commit is contained in:
zubiden 2025-06-18 17:41:05 +02:00 committed by Alexander Zinchuk
parent 8f9310c7d5
commit a85fec6c65
59 changed files with 803 additions and 303 deletions

View File

@ -98,6 +98,7 @@ export interface GramJsAppConfig extends LimitsConfig {
stars_stargift_resale_amount_max?: number;
stars_stargift_resale_amount_min?: number;
stars_stargift_resale_commission_permille?: number;
poll_answers_max?: number;
}
function buildEmojiSounds(appConfig: GramJsAppConfig) {
@ -198,5 +199,6 @@ 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,
pollMaxAnswers: appConfig.poll_answers_max,
};
}

View File

@ -110,6 +110,9 @@ function buildApiChatFieldsFromPeerEntity(
isJoinToSend: channel?.joinToSend,
isJoinRequest: channel?.joinRequest,
isForum: channel?.forum,
isMonoforum: channel?.monoforum,
linkedMonoforumId: channel?.linkedMonoforumId && buildApiPeerId(channel.linkedMonoforumId, 'channel'),
areChannelMessagesAllowed: channel?.broadcastMessagesAllowed,
areStoriesHidden,
maxStoryId,
hasStories: Boolean(maxStoryId) && !storiesUnavailable,

View File

@ -426,11 +426,12 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
}
if (action instanceof GramJs.MessageActionPaidMessagesPrice) {
const {
stars,
stars, broadcastMessagesAllowed,
} = action;
return {
mediaType: 'action',
type: 'paidMessagesPrice',
isAllowedInChannel: broadcastMessagesAllowed,
stars: stars.toJSNumber(),
};
}

View File

@ -287,6 +287,7 @@ export function buildMessageDraft(draft: GramJs.TypeDraftMessage): ApiDraft | un
replyToMsgId: replyTo.replyToMsgId,
replyToTopId: replyTo.topMsgId,
replyToPeerId: replyTo.replyToPeerId && getApiChatIdFromMtpPeer(replyTo.replyToPeerId),
monoforumPeerId: replyTo.monoforumPeerId && getApiChatIdFromMtpPeer(replyTo.monoforumPeerId),
quoteText: replyTo.quoteText ? buildMessageTextContent(replyTo.quoteText, replyTo.quoteEntities) : undefined,
quoteOffset: replyTo.quoteOffset,
} satisfies ApiInputMessageReplyInfo : undefined;

View File

@ -3,7 +3,7 @@ import { Api as GramJs } from '../../../lib/gramjs';
import type { ApiEmojiStatusType, ApiPeerColor } from '../../types';
import { CHANNEL_ID_LENGTH } from '../../../config';
import { CHANNEL_ID_BASE } from '../../../config';
import { numberToHexColor } from '../../../util/colors';
type TypePeerOrInput = GramJs.TypePeer | GramJs.TypeInputPeer | GramJs.TypeInputUser | GramJs.TypeInputChannel;
@ -26,13 +26,10 @@ export function buildApiPeerId(id: BigInt.BigInteger, type: 'user' | 'chat' | 'c
}
if (type === 'channel') {
// Simulates TDLib https://github.com/tdlib/td/blob/d7203eb719304866a7eb7033ef03d421459335b8/td/telegram/DialogId.cpp#L54
// But using only string operations. Should be fine until channel ids reach 10^12
// Example: 12345678 -> -1000012345678
return `-1${id.toString().padStart(CHANNEL_ID_LENGTH - 2, '0')}`;
return id.add(CHANNEL_ID_BASE).negate().toString();
}
return `-${id.toString()}`;
return id.negate().toString();
}
export function getApiChatIdFromMtpPeer(peer: TypePeerOrInput) {

View File

@ -37,23 +37,22 @@ import {
ApiMessageEntityTypes,
} from '../../types';
import { CHANNEL_ID_LENGTH, DEFAULT_STATUS_ICON_ID } from '../../../config';
import { CHANNEL_ID_BASE, DEFAULT_STATUS_ICON_ID } from '../../../config';
import { pick } from '../../../util/iteratees';
import { deserializeBytes } from '../helpers/misc';
import localDb from '../localDb';
function checkIfChannelId(id: string) {
return id.length === CHANNEL_ID_LENGTH && id.startsWith('-1');
}
export function getEntityTypeById(chatOrUserId: string) {
if (!chatOrUserId.startsWith('-')) {
export function getEntityTypeById(peerId: string) {
const n = Number(peerId);
if (n > 0) {
return 'user';
} else if (checkIfChannelId(chatOrUserId)) {
return 'channel';
} else {
return 'chat';
}
if (n < -CHANNEL_ID_BASE) {
return 'channel';
}
return 'chat';
}
export function buildPeer(chatOrUserId: string): GramJs.TypePeer {
@ -569,11 +568,13 @@ export function buildMtpPeerId(id: string, type: 'user' | 'chat' | 'channel') {
return BigInt(id);
}
const n = Number(id);
if (type === 'channel') {
return BigInt(id.slice(2)); // Slice "-1", zeroes are trimmed when converting to BigInt
return BigInt(-n - CHANNEL_ID_BASE);
}
return BigInt(id.slice(1));
return BigInt(n * -1);
}
export function buildInputGroupCall(groupCall: Partial<ApiGroupCall>) {
@ -843,12 +844,13 @@ export function buildInputReplyTo(replyInfo: ApiInputReplyInfo) {
if (replyInfo.type === 'message') {
const {
replyToMsgId, replyToTopId, replyToPeerId, quoteText, quoteOffset,
replyToMsgId, replyToTopId, replyToPeerId, quoteText, quoteOffset, monoforumPeerId,
} = replyInfo;
return new GramJs.InputReplyToMessage({
replyToMsgId,
topMsgId: replyToTopId,
replyToPeerId: replyToPeerId ? buildInputPeerFromLocalDb(replyToPeerId)! : undefined,
monoforumPeerId: monoforumPeerId ? buildInputPeerFromLocalDb(monoforumPeerId)! : undefined,
quoteText: quoteText?.text,
quoteEntities: quoteText?.entities?.map(buildMtpMessageEntity),
quoteOffset,

View File

@ -299,6 +299,10 @@ export async function fetchSavedChats({
const chats: ApiChat[] = [];
dialogs.forEach((dialog) => {
if (dialog instanceof GramJs.MonoForumDialog) {
return;
}
const peerEntity = peersByKey[getPeerKey(dialog.peer)];
const chat = buildApiChatFromSavedDialog(dialog, peerEntity);
const chatId = getApiChatIdFromMtpPeer(dialog.peer);
@ -596,7 +600,7 @@ async function getFullChatInfo(chatId: string): Promise<FullChatData | undefined
async function getFullChannelInfo(
chat: ApiChat,
): Promise<FullChatData | undefined> {
const { id, adminRights } = chat;
const { id, adminRights, isMonoforum } = chat;
const accessHash = chat.accessHash!;
const result = await invokeRequest(new GramJs.channels.GetFullChannel({
channel: buildInputChannel(id, accessHash),
@ -653,12 +657,14 @@ async function getFullChannelInfo(
? exportedInvite.link
: undefined;
const { members, userStatusesById } = (canViewParticipants && await fetchMembers({ chat })) || {};
const canLoadParticipants = canViewParticipants && !isMonoforum;
const { members, userStatusesById } = (canLoadParticipants && await fetchMembers({ chat })) || {};
const { members: kickedMembers, userStatusesById: bannedStatusesById } = (
canViewParticipants && adminRights && await fetchMembers({ chat, memberFilter: 'kicked' })
canLoadParticipants && adminRights && await fetchMembers({ chat, memberFilter: 'kicked' })
) || {};
const { members: adminMembers, userStatusesById: adminStatusesById } = (
canViewParticipants && await fetchMembers({ chat, memberFilter: 'admin' })
canLoadParticipants && await fetchMembers({ chat, memberFilter: 'admin' })
) || {};
const botCommands = botInfo ? buildApiChatBotCommands(botInfo) : undefined;
const memberInfoRequest = !chat.isNotJoined && chat.type === 'chatTypeChannel'
@ -710,7 +716,7 @@ async function getFullChannelInfo(
chatId: buildApiPeerId(migratedFromChatId, 'chat'),
maxMessageId: migratedFromMaxId,
} : undefined,
canViewMembers: canViewParticipants,
canViewMembers: canLoadParticipants,
canViewStatistics: canViewStats,
canViewMonetization,
isPreHistoryHidden: hiddenPrehistory,

View File

@ -980,8 +980,8 @@ export async function sendMessageAction({
}: {
peer: ApiPeer; threadId?: ThreadId; action: ApiSendMessageAction;
}) {
const gramAction = buildSendMessageAction(action);
if (!gramAction) {
const mtpAction = buildSendMessageAction(action);
if (!mtpAction) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.warn('Unsupported message action', action);
@ -993,7 +993,7 @@ export async function sendMessageAction({
const result = await invokeRequest(new GramJs.messages.SetTyping({
peer: buildInputPeer(peer.id, peer.accessHash),
topMsgId: Number(threadId),
action: gramAction,
action: mtpAction,
}), {
shouldThrow: true,
abortControllerChatId: peer.id,

View File

@ -47,6 +47,9 @@ export interface ApiChat {
emojiStatus?: ApiEmojiStatusType;
isForum?: boolean;
isForumAsMessages?: true;
isMonoforum?: boolean;
linkedMonoforumId?: string;
areChannelMessagesAllowed?: boolean;
boostLevel?: number;
botVerificationIconId?: string;
hasAutoTranslation?: true;

View File

@ -278,6 +278,7 @@ export interface ApiMessageActionPaidMessagesRefunded extends ActionMediaType {
export interface ApiMessageActionPaidMessagesPrice extends ActionMediaType {
type: 'paidMessagesPrice';
stars: number;
isAllowedInChannel?: boolean;
}
export interface ApiMessageActionUnsupported extends ActionMediaType {

View File

@ -379,6 +379,7 @@ export interface ApiInputMessageReplyInfo {
replyToMsgId: number;
replyToTopId?: number;
replyToPeerId?: string;
monoforumPeerId?: string;
quoteText?: ApiFormattedText;
quoteOffset?: number;
}

View File

@ -250,6 +250,7 @@ export interface ApiAppConfig {
starsStargiftResaleAmountMin?: number;
starsStargiftResaleAmountMax?: number;
starsStargiftResaleCommissionPermille?: number;
pollMaxAnswers?: number;
}
export interface ApiConfig {

View File

@ -1918,7 +1918,8 @@
"SectionDescriptionStarsForForMessages" = "You will receive {percent} of the selected fee (~{amount}) for each incoming message.";
"SubtitlePrivacyAddUsers" = "Add Users";
"PrivacyPaidMessagesValue" = "Paid";
"FirstMessageInPaidMessagesChat" = "**{user}** charges {amount} for each message.";
"MessagesPlaceholderPaidUser" = "**{peer}** charges {amount} for each message.";
"MessagesPlaceholderPaidChannel" = "**{peer}** charges {amount} per message to its admin.";
"ButtonBuyStars" = "Buy Stars";
"ComposerPlaceholderPaidMessage" = "Message for {amount}";
"ComposerPlaceholderPaidReply" = "Reply for {amount}";
@ -1965,8 +1966,12 @@
"FrozenAccountAppealSubtitle" = "Appeal via {botLink} before {date} or your account will be deleted.";
"ButtonAppeal" = "Submit an Appeal";
"ButtonUnderstood" = "Understood";
"ActionPaidMessageGroupPrice" = "Messages now cost **{stars}** in this group";
"ActionPaidMessageGroupPriceFree" = "Messages are now free in this group";
"ActionPaidMessagePrice" = "{peer} changed price to **{amount}** per message";
"ActionPaidMessagePriceYou" = "You changed price to **{amount}** per message";
"ActionPaidMessagePriceFree" = "{peer} made messages free";
"ActionPaidMessagePriceFreeYou" = "You made messages free";
"ActionMessageChannelFree" = "{peer} now accepts private messages";
"ActionMessageChannelDisabled" = "{peer} no longer accepts private messages";
"ApiMessageActionPaidMessagesRefundedOutgoing" = "You refunded **{stars}** to {user}";
"ApiMessageActionPaidMessagesRefundedIncoming" = "{user} refunded **{stars}** to you";
"NotificationTitleNotSupportedInFrozenAccount" = "Your account is frozen";
@ -2012,4 +2017,8 @@
"ValueGiftSortByNumber" = "Number";
"ResellGiftsNoFound" = "No gifts found";
"ResellGiftsClearFilters" = "Clear Filters";
"MonoforumBadge" = "DIRECT";
"MonoforumStatus" = "Channel messages";
"MonoforumComposerPlaceholder" = "Choose a message to reply";
"ChannelSendMessage" = "Direct Messages";
"AutomaticTranslation" = "Automatic Translation";

View File

@ -150,4 +150,13 @@
&.premium-gradient-bg > .inner {
background-image: var(--premium-gradient);
}
&.message-bubble {
--radius: 0;
mask-image: url("../../assets/icons/forumTopic/blue.svg");
mask-position: center;
mask-repeat: no-repeat;
mask-size: 100%;
}
}

View File

@ -85,6 +85,7 @@ type OwnProps = {
storyViewerMode?: 'full' | 'single-peer' | 'disabled';
loopIndefinitely?: boolean;
noPersonalPhoto?: boolean;
asMessageBubble?: boolean;
observeIntersection?: ObserveFn;
onClick?: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => void;
onContextMenu?: (e: React.MouseEvent) => void;
@ -111,6 +112,7 @@ const Avatar: FC<OwnProps> = ({
storyViewerMode = 'single-peer',
loopIndefinitely,
noPersonalPhoto,
asMessageBubble,
onClick,
onContextMenu,
}) => {
@ -261,6 +263,7 @@ const Avatar: FC<OwnProps> = ({
isReplies && 'replies-bot-account',
isPremiumGradient && 'premium-gradient-bg',
isRoundedRect && 'forum',
asMessageBubble && 'message-bubble',
(photo || webPhoto) && 'force-fit',
((withStory && realPeer?.hasStories) || forPremiumPromo) && 'with-story-circle',
withStorySolid && realPeer?.hasStories && 'with-story-solid',

View File

@ -300,6 +300,7 @@ type StateProps =
isAccountFrozen?: boolean;
isAppConfigLoaded?: boolean;
insertingPeerIdMention?: string;
pollMaxAnswers?: number;
};
enum MainButtonState {
@ -413,10 +414,6 @@ const Composer: FC<OwnProps & StateProps> = ({
shouldPlayEffect,
maxMessageLength,
isSilentPosting,
onDropHide,
onFocus,
onBlur,
onForward,
isPaymentMessageConfirmDialogOpen,
starsBalance,
isStarsBalanceModalOpen,
@ -424,6 +421,11 @@ const Composer: FC<OwnProps & StateProps> = ({
isAccountFrozen,
isAppConfigLoaded,
insertingPeerIdMention,
pollMaxAnswers,
onDropHide,
onFocus,
onBlur,
onForward,
}) => {
const {
sendMessage,
@ -481,9 +483,12 @@ const Composer: FC<OwnProps & StateProps> = ({
const canMediaBeReplaced = editingMessage && canEditMedia(editingMessage);
const isMonoforum = chat?.isMonoforum;
const { emojiSet, members: groupChatMembers, botCommands: chatBotCommands } = chatFullInfo || {};
const chatEmojiSetId = emojiSet?.id;
const canSchedule = !paidMessagesStars && !isMonoforum;
const isSentStoryReactionHeart = sentStoryReaction && isSameReaction(sentStoryReaction, HEART_REACTION);
useEffect(processMessageInputForCustomEmoji, [getHtml]);
@ -510,10 +515,10 @@ const Composer: FC<OwnProps & StateProps> = ({
}, [chatId]);
useEffect(() => {
if (isAppConfigLoaded && chatId && isReady && !isInStoryViewer) {
if (isAppConfigLoaded && chatId && isReady && !isInStoryViewer && !isMonoforum) {
loadScheduledHistory({ chatId });
}
}, [isReady, chatId, threadId, isInStoryViewer, isAppConfigLoaded]);
}, [isReady, chatId, threadId, isInStoryViewer, isAppConfigLoaded, isMonoforum]);
useEffect(() => {
const isChannelWithProfiles = isChannel && chat?.areProfilesShown;
@ -541,10 +546,11 @@ const Composer: FC<OwnProps & StateProps> = ({
() => getAllowedAttachmentOptions(chat,
chatFullInfo,
isChatWithBot,
isChatWithSelf,
isInStoryViewer,
paidMessagesStars,
isInScheduledList),
[chat, chatFullInfo, isChatWithBot, isInStoryViewer, paidMessagesStars, isInScheduledList],
[chat, chatFullInfo, isChatWithBot, isChatWithSelf, isInStoryViewer, paidMessagesStars, isInScheduledList],
);
const isNeedPremium = isContactRequirePremium && isInStoryViewer;
@ -804,7 +810,7 @@ const Composer: FC<OwnProps & StateProps> = ({
getHtml,
setHtml,
editedMessage: editingMessage,
isDisabled: isInStoryViewer || Boolean(requestedDraft),
isDisabled: isInStoryViewer || Boolean(requestedDraft) || isMonoforum,
});
const resetComposer = useLastCallback((shouldPreserveInput = false) => {
@ -1847,8 +1853,8 @@ const Composer: FC<OwnProps & StateProps> = ({
shouldForceAsFile={shouldForceAsFile}
isForCurrentMessageList={isForCurrentMessageList}
isForMessage={isInMessageList}
shouldSchedule={!paidMessagesStars && isInScheduledList}
canSchedule={!paidMessagesStars}
shouldSchedule={canSchedule && isInScheduledList}
canSchedule={canSchedule}
forceDarkTheme={isInStoryViewer}
onCaptionUpdate={onCaptionUpdate}
onSendSilent={handleSendSilentAttachments}
@ -1869,6 +1875,7 @@ const Composer: FC<OwnProps & StateProps> = ({
isOpen={pollModal.isOpen}
isQuiz={pollModal.isQuiz}
shouldBeAnonymous={isChannel}
maxOptionsCount={pollMaxAnswers}
onClear={closePollModal}
onSend={handlePollSend}
/>
@ -2304,7 +2311,7 @@ const Composer: FC<OwnProps & StateProps> = ({
{canShowCustomSendMenu && (
<CustomSendMenu
isOpen={isCustomSendMenuOpen}
canSchedule={!paidMessagesStars && isInMessageList && !isViewOnceEnabled}
canSchedule={canSchedule && isInMessageList && !isViewOnceEnabled}
canScheduleUntilOnline={canScheduleUntilOnline && !isViewOnceEnabled}
onSendSilent={!isChatWithSelf ? handleSendSilent : undefined}
onSendSchedule={!isInScheduledList ? handleSendScheduled : undefined}
@ -2344,6 +2351,7 @@ export default memo(withGlobal<OwnProps>(
(global, {
chatId, threadId, storyId, messageListType, isMobile, type,
}): StateProps => {
const appConfig = global.appConfig;
const chat = selectChat(global, chatId);
const chatBot = !isSystemBot(chatId) ? selectBot(global, chatId) : undefined;
const isChatWithBot = Boolean(chatBot);
@ -2514,6 +2522,7 @@ export default memo(withGlobal<OwnProps>(
isAccountFrozen,
isAppConfigLoaded,
insertingPeerIdMention,
pollMaxAnswers: appConfig?.pollMaxAnswers,
};
},
)(Composer));

View File

@ -90,11 +90,11 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
contactName,
willDeleteForCurrentUserOnly,
willDeleteForAll,
onConfirm,
chatBot,
adminMembersById,
canBanUsers,
linkedChatId,
onConfirm,
}) => {
const {
closeDeleteMessageModal,
@ -130,7 +130,11 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
const global = getGlobal();
const senderArray = getSendersFromSelectedMessages(global, chat.id, messageIds);
return senderArray ? unique(senderArray)
.filter((peer) => peer?.id !== chat?.id && peer?.id !== linkedChatId) : MEMO_EMPTY_ARRAY;
.filter((peer) => (
peer?.id !== chat?.id
&& peer?.id !== linkedChatId
&& peer?.id !== chat?.linkedMonoforumId
)) : MEMO_EMPTY_ARRAY;
}, [chat, isChannel, linkedChatId, messageIds]);
const buildNestedOptionListWithAvatars = useLastCallback(() => {
@ -144,16 +148,24 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
});
const peerListToDeleteAll = useMemo(() => {
return peerList.filter((peer) => peer.id !== linkedChatId && peer.id !== currentUserId);
}, [peerList, currentUserId, linkedChatId]);
return peerList.filter((peer) => (
peer.id !== linkedChatId
&& peer.id !== chat?.linkedMonoforumId
&& peer.id !== currentUserId
));
}, [peerList, currentUserId, linkedChatId, chat?.linkedMonoforumId]);
const peerListToReportSpam = useMemo(() => {
return peerList.filter((peer) => peer.id !== currentUserId && peer.id !== linkedChatId);
}, [peerList, currentUserId, linkedChatId]);
return peerList.filter((peer) => (
peer.id !== currentUserId
&& peer.id !== linkedChatId
&& peer.id !== chat?.linkedMonoforumId
));
}, [peerList, currentUserId, linkedChatId, chat?.linkedMonoforumId]);
const peerListToBan = useMemo(() => {
const isCurrentUserInList = peerList.some((peer) => peer.id === currentUserId);
const shouldReturnEmpty = !canBanUsers || isCurrentUserInList;
const shouldReturnEmpty = !canBanUsers || isCurrentUserInList || chat?.isMonoforum;
if (shouldReturnEmpty) {
return MEMO_EMPTY_ARRAY;
@ -163,7 +175,7 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
const isAdmin = adminMembersById?.[peer.id];
return isCreator || !isAdmin;
});
}, [peerList, isCreator, currentUserId, canBanUsers, adminMembersById]);
}, [peerList, isCreator, currentUserId, canBanUsers, adminMembersById, chat?.isMonoforum]);
const shouldShowAdditionalOptions = useMemo(() => {
return Boolean(peerListToDeleteAll.length || peerListToReportSpam.length || peerListToBan.length);
@ -184,11 +196,12 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
label: oldLang('ReportSpamTitle'),
nestedOptions: messageIds && peerList.length >= 2 ? [
...buildNestedOptionListWithAvatars().filter((opt) => opt.value !== linkedChatId
&& opt.value !== chat?.linkedMonoforumId
&& opt.value !== currentUserId),
] : undefined,
},
];
}, [messageIds, peerList, oldLang, linkedChatId, currentUserId]);
}, [messageIds, peerList, oldLang, linkedChatId, chat?.linkedMonoforumId, currentUserId]);
const ACTION_DELETE_OPTION: IRadioOption[] = useMemo(() => {
return [
@ -199,11 +212,12 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
: oldLang('DeleteAllFrom', Object.values(peerNames)[0]),
nestedOptions: messageIds && peerList.length >= 2 ? [
...buildNestedOptionListWithAvatars().filter((opt) => opt.value !== linkedChatId
&& opt.value !== chat?.linkedMonoforumId
&& opt.value !== currentUserId),
] : undefined,
},
];
}, [messageIds, peerList, oldLang, peerNames, linkedChatId, currentUserId]);
}, [messageIds, peerList, oldLang, peerNames, linkedChatId, chat?.linkedMonoforumId, currentUserId]);
const ACTION_BAN_OPTION: IRadioOption[] = useMemo(() => {
return [
@ -279,7 +293,7 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
if (isSchedule) {
deleteScheduledMessages({ messageIds });
} else if (shouldShowOption) {
if (peerIdsToReportSpam) {
if (peerIdsToReportSpam?.length) {
const global = getGlobal();
const peerIdList = peerIdsToReportSpam.filter((option) => !Number.isNaN(Number(option)));
const messageList = messageIds.reduce<Record<string, number[]>>((acc, msgId) => {
@ -296,24 +310,24 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
handleReportSpam(messageList);
}
if (peerIdsToDeleteAll) {
if (peerIdsToDeleteAll?.length) {
const peerIdList = peerIdsToDeleteAll.filter((option) => !Number.isNaN(Number(option)));
handleDeleteAllPeerMessages(peerIdList);
}
if (peerIdsToBan && !havePermissionChanged) {
if (peerIdsToBan?.length && !havePermissionChanged) {
const peerIdList = peerIdsToBan.filter((option) => !Number.isNaN(Number(option)));
handleDeleteMember(peerIdList);
const filteredMessageIdList = filterMessageIdByPeerId(peerIdList, messageIds);
handleDeleteMessages(filteredMessageIdList);
}
if (peerIdsToBan && havePermissionChanged) {
if (peerIdsToBan?.length && havePermissionChanged) {
const peerIdList = peerIdsToBan.filter((option) => !Number.isNaN(Number(option)));
handleUpdateChatMemberBannedRights(peerIdList);
}
if (!peerIdsToReportSpam || !peerIdsToDeleteAll || !peerIdsToBan) {
if (!peerIdsToReportSpam?.length || !peerIdsToDeleteAll?.length || !peerIdsToBan?.length) {
deleteMessages({ messageIds, shouldDeleteForAll });
}
} else {
@ -426,21 +440,19 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
<p className={styles.actionTitle}>{oldLang('DeleteAdditionalActions')}</p>
{renderAdditionalActionOptions()}
{renderPartiallyRestrictedUser()}
{
peerIdsToBan && canBanUsers ? (
<ListItem
narrow
buttonClassName={styles.button}
onClick={toggleAdditionalOptions}
>
{oldLang(isAdditionalOptionsVisible ? 'DeleteToggleBanUsers' : 'DeleteToggleRestrictUsers')}
<Icon
name={isAdditionalOptionsVisible ? 'up' : 'down'}
className={buildClassName(styles.button, 'ml-2')}
/>
</ListItem>
) : setIsAdditionalOptionsVisible(false)
}
{peerIdsToBan?.length && canBanUsers ? (
<ListItem
narrow
buttonClassName={styles.button}
onClick={toggleAdditionalOptions}
>
{oldLang(isAdditionalOptionsVisible ? 'DeleteToggleBanUsers' : 'DeleteToggleRestrictUsers')}
<Icon
name={isAdditionalOptionsVisible ? 'up' : 'down'}
className={buildClassName(styles.button, 'ml-2')}
/>
</ListItem>
) : setIsAdditionalOptionsVisible(false)}
</>
)}
{(canDeleteForAll || chatBot || !shouldShowOption) && (

View File

@ -38,3 +38,15 @@
display: flex;
align-items: center;
}
.monoforumBadge {
padding: 0.125rem 0.25rem;
border-radius: 0.25rem;
font-size: 0.625rem;
font-weight: 500;
line-height: 1;
color: var(--color-white);
background-color: var(--color-gray);
}

View File

@ -24,6 +24,7 @@ import { copyTextToClipboard } from '../../util/clipboard';
import stopEvent from '../../util/stopEvent';
import renderText from './helpers/renderText';
import useLang from '../../hooks/useLang';
import useLastCallback from '../../hooks/useLastCallback';
import useOldLang from '../../hooks/useOldLang';
@ -44,12 +45,14 @@ type OwnProps = {
emojiStatusSize?: number;
isSavedMessages?: boolean;
isSavedDialog?: boolean;
isMonoforum?: boolean;
monoforumBadgeClassName?: string;
noLoopLimit?: boolean;
canCopyTitle?: boolean;
iconElement?: React.ReactNode;
statusSparklesColor?: string;
onEmojiStatusClick?: NoneToVoidFunction;
observeIntersection?: ObserveFn;
statusSparklesColor?: string;
};
const FullNameTitle: FC<OwnProps> = ({
@ -65,15 +68,20 @@ const FullNameTitle: FC<OwnProps> = ({
canCopyTitle,
iconElement,
statusSparklesColor,
isMonoforum,
monoforumBadgeClassName,
onEmojiStatusClick,
observeIntersection,
}) => {
const lang = useOldLang();
const { showNotification } = getActions();
const oldLang = useOldLang();
const lang = useLang();
const realPeer = 'id' in peer ? peer : undefined;
const customPeer = 'isCustomPeer' in peer ? peer : undefined;
const isUser = realPeer && isApiPeerUser(realPeer);
const title = realPeer && (isUser ? getUserFullName(realPeer) : getChatTitle(lang, realPeer));
const title = realPeer && (isUser ? getUserFullName(realPeer) : getChatTitle(oldLang, realPeer));
const isPremium = (isUser && realPeer.isPremium) || customPeer?.isPremium;
const canShowEmojiStatus = withEmojiStatus && !isSavedMessages;
const emojiStatus = realPeer?.emojiStatus
@ -91,27 +99,27 @@ const FullNameTitle: FC<OwnProps> = ({
const specialTitle = useMemo(() => {
if (customPeer) {
return renderText(customPeer.title || lang(customPeer.titleKey!));
return renderText(customPeer.title || oldLang(customPeer.titleKey!));
}
if (isSavedMessages) {
return lang(isSavedDialog ? 'MyNotes' : 'SavedMessages');
return oldLang(isSavedDialog ? 'MyNotes' : 'SavedMessages');
}
if (isAnonymousForwardsChat(realPeer!.id)) {
return lang('AnonymousForward');
return oldLang('AnonymousForward');
}
if (isChatWithRepliesBot(realPeer!.id)) {
return lang('RepliesTitle');
return oldLang('RepliesTitle');
}
if (isChatWithVerificationCodesBot(realPeer!.id)) {
return lang('VerifyCodesNotifications');
return oldLang('VerifyCodesNotifications');
}
return undefined;
}, [customPeer, isSavedDialog, isSavedMessages, lang, realPeer]);
}, [customPeer, isSavedDialog, isSavedMessages, oldLang, realPeer]);
const botVerificationIconId = realPeer?.botVerificationIconId;
return (
@ -164,6 +172,11 @@ const FullNameTitle: FC<OwnProps> = ({
</Transition>
)}
{canShowEmojiStatus && !emojiStatus && isPremium && <StarIcon />}
{isMonoforum && (
<div className={buildClassName(styles.monoforumBadge, monoforumBadgeClassName)}>
{lang('MonoforumBadge')}
</div>
)}
</>
)}
{iconElement}

View File

@ -19,6 +19,7 @@ import {
selectChat,
selectChatMessages,
selectChatOnlineCount,
selectMonoforumChannel,
selectThreadInfo,
selectThreadMessagesCount,
selectTopic,
@ -28,6 +29,7 @@ import buildClassName from '../../util/buildClassName';
import { REM } from './helpers/mediaDimensions';
import renderText from './helpers/renderText';
import useLang from '../../hooks/useLang';
import useLastCallback from '../../hooks/useLastCallback';
import useOldLang from '../../hooks/useOldLang';
@ -63,20 +65,21 @@ type OwnProps = {
withStory?: boolean;
storyViewerOrigin?: StoryViewerOrigin;
isSavedDialog?: boolean;
withMonoforumStatus?: boolean;
onClick?: VoidFunction;
onEmojiStatusClick?: NoneToVoidFunction;
};
type StateProps =
{
chat?: ApiChat;
threadInfo?: ApiThreadInfo;
topic?: ApiTopic;
onlineCount?: number;
areMessagesLoaded: boolean;
messagesCount?: number;
self?: ApiUser;
};
type StateProps = {
chat?: ApiChat;
threadInfo?: ApiThreadInfo;
topic?: ApiTopic;
onlineCount?: number;
areMessagesLoaded: boolean;
messagesCount?: number;
self?: ApiUser;
monoforumChannel?: ApiChat;
};
const GroupChatInfo: FC<OwnProps & StateProps> = ({
typingStatus,
@ -93,7 +96,7 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
withChatType,
threadInfo,
noRtl,
chat,
chat: realChat,
onlineCount,
areMessagesLoaded,
topic,
@ -105,6 +108,8 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
emojiStatusSize,
isSavedDialog,
self,
withMonoforumStatus,
monoforumChannel,
onClick,
onEmojiStatusClick,
}) => {
@ -114,7 +119,10 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
loadMoreProfilePhotos,
} = getActions();
const lang = useOldLang();
const chat = !withMonoforumStatus && monoforumChannel ? monoforumChannel : realChat;
const oldLang = useOldLang();
const lang = useLang();
const isSuperGroup = chat && isChatSuperGroup(chat);
const isTopic = Boolean(chat?.isForum && threadInfo && topic);
@ -148,6 +156,24 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
}
function renderStatusOrTyping() {
if (withUpdatingStatus && !areMessagesLoaded && !isRestricted) {
return (
<DotAnimation className="status" content={oldLang('Updating')} />
);
}
if (withMonoforumStatus) {
return (
<span className="status" dir="auto">
{lang('MonoforumStatus')}
</span>
);
}
if (realChat?.isMonoforum) {
return undefined;
}
if (status) {
return withDots ? (
<DotAnimation className="status" content={status} />
@ -159,12 +185,6 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
);
}
if (withUpdatingStatus && !areMessagesLoaded && !isRestricted) {
return (
<DotAnimation className="status" content={lang('Updating')} />
);
}
if (!chat) {
return undefined;
}
@ -182,7 +202,7 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
activeKey={messagesCount !== undefined ? 1 : 2}
className="message-count-transition"
>
{messagesCount !== undefined && lang('messages', messagesCount, 'i')}
{messagesCount !== undefined && oldLang('messages', messagesCount, 'i')}
</Transition>
</span>
);
@ -190,12 +210,12 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
if (withChatType) {
return (
<span className="status" dir="auto">{lang(getChatTypeString(chat))}</span>
<span className="status" dir="auto">{oldLang(getChatTypeString(chat))}</span>
);
}
const groupStatus = getGroupStatus(lang, chat);
const onlineStatus = onlineCount ? `, ${lang('OnlineCount', onlineCount, 'i')}` : undefined;
const groupStatus = getGroupStatus(oldLang, chat);
const onlineStatus = onlineCount ? `, ${oldLang('OnlineCount', onlineCount, 'i')}` : undefined;
return (
<span className="status">
@ -211,7 +231,7 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
className={
buildClassName('ChatInfo', className)
}
dir={!noRtl && lang.isRtl ? 'rtl' : undefined}
dir={!noRtl && oldLang.isRtl ? 'rtl' : undefined}
onClick={onClick}
>
{!noAvatar && !isTopic && (
@ -231,6 +251,7 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
size={avatarSize}
peer={chat}
withStory={withStory}
asMessageBubble={Boolean(monoforumChannel)}
storyViewerOrigin={storyViewerOrigin}
storyViewerMode="single-peer"
isSavedDialog={isSavedDialog}
@ -251,6 +272,7 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
: (
<FullNameTitle
peer={chat}
isMonoforum={!withMonoforumStatus && Boolean(monoforumChannel)}
emojiStatusSize={emojiStatusSize}
withEmojiStatus={!noEmojiStatus}
isSavedDialog={isSavedDialog}
@ -272,6 +294,7 @@ export default memo(withGlobal<OwnProps>(
const topic = threadId ? selectTopic(global, chatId, threadId) : undefined;
const messagesCount = topic && selectThreadMessagesCount(global, chatId, threadId!);
const self = selectUser(global, global.currentUserId!);
const monoforumChannel = selectMonoforumChannel(global, chatId);
return {
chat,
@ -281,6 +304,7 @@ export default memo(withGlobal<OwnProps>(
areMessagesLoaded,
messagesCount,
self,
monoforumChannel,
};
},
)(GroupChatInfo));

View File

@ -27,6 +27,7 @@ import { MEMO_EMPTY_ARRAY } from '../../util/memo';
import renderText from './helpers/renderText';
import useIntervalForceUpdate from '../../hooks/schedulers/useIntervalForceUpdate';
import useLang from '../../hooks/useLang';
import useLastCallback from '../../hooks/useLastCallback';
import useOldLang from '../../hooks/useOldLang';
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
@ -45,6 +46,7 @@ type OwnProps = {
peerId: string;
forceShowSelf?: boolean;
canPlayVideo: boolean;
isForMonoforum?: boolean;
};
type StateProps =
@ -81,6 +83,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
emojiStatusSlug,
profilePhotos,
peerId,
isForMonoforum,
}) => {
const {
openMediaViewer,
@ -91,7 +94,8 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
openUniqueGiftBySlug,
} = getActions();
const lang = useOldLang();
const oldLang = useOldLang();
const lang = useLang();
useIntervalForceUpdate(user ? STATUS_UPDATE_INTERVAL : undefined);
@ -100,7 +104,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
const prevAvatarOwnerId = usePreviousDeprecated(avatarOwnerId);
const [hasSlideAnimation, setHasSlideAnimation] = useState(true);
// slideOptimized doesn't work well when animation is dynamically disabled
const slideAnimation = hasSlideAnimation ? (lang.isRtl ? 'slideRtl' : 'slide') : 'none';
const slideAnimation = hasSlideAnimation ? (oldLang.isRtl ? 'slideRtl' : 'slide') : 'none';
const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0);
const isFirst = photos.length <= 1 || currentPhotoIndex === 0;
@ -217,9 +221,9 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
letterClassName={styles.topicIconTitle}
noLoopLimit
/>
<h3 className={styles.topicTitle} dir={lang.isRtl ? 'rtl' : undefined}>{renderText(topic!.title)}</h3>
<h3 className={styles.topicTitle} dir={oldLang.isRtl ? 'rtl' : undefined}>{renderText(topic!.title)}</h3>
<p className={styles.topicMessagesCounter}>
{messagesCount ? lang('Chat.Title.Topic', messagesCount, 'i') : lang('lng_forum_no_messages')}
{messagesCount ? oldLang('Chat.Title.Topic', messagesCount, 'i') : oldLang('lng_forum_no_messages')}
</p>
</div>
);
@ -265,6 +269,14 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
const isSystemBotChat = isSystemBot(peerId);
if (isAnonymousForwards || isSystemBotChat) return undefined;
if (isForMonoforum) {
return (
<span className={buildClassName(styles.status, 'status')} dir="auto">
{lang('MonoforumStatus')}
</span>
);
}
if (user) {
return (
<div
@ -275,11 +287,11 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
)}
>
<span className={styles.userStatus} dir="auto">
{getUserStatus(lang, user, userStatus)}
{getUserStatus(oldLang, user, userStatus)}
</span>
{userStatus?.isReadDateRestrictedByMe && (
<span className={styles.getStatus} onClick={handleOpenGetReadDateModal}>
<span>{lang('StatusHiddenShow')}</span>
<span>{oldLang('StatusHiddenShow')}</span>
</span>
)}
</div>
@ -290,8 +302,8 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
<span className={buildClassName(styles.status, 'status')} dir="auto">
{
isChatChannel(chat!)
? lang('Subscribers', chat!.membersCount ?? 0, 'i')
: lang('Members', chat!.membersCount ?? 0, 'i')
? oldLang('Subscribers', chat!.membersCount ?? 0, 'i')
: oldLang('Members', chat!.membersCount ?? 0, 'i')
}
</span>
);
@ -304,7 +316,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
return (
<div
className={buildClassName('ProfileInfo')}
dir={lang.isRtl ? 'rtl' : undefined}
dir={oldLang.isRtl ? 'rtl' : undefined}
>
<div className={styles.photoWrapper}>
{renderPhotoTabs()}
@ -315,7 +327,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
)}
>
<div className={styles.fallbackPhotoContents}>
{lang(profilePhotos.personalPhoto.isVideo ? 'UserInfo.CustomVideo' : 'UserInfo.CustomPhoto')}
{oldLang(profilePhotos.personalPhoto.isVideo ? 'UserInfo.CustomVideo' : 'UserInfo.CustomPhoto')}
</div>
</div>
)}
@ -333,7 +345,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
size="mini"
/>
)}
{lang(profilePhotos.fallbackPhoto.isVideo ? 'UserInfo.PublicVideo' : 'UserInfo.PublicPhoto')}
{oldLang(profilePhotos.fallbackPhoto.isVideo ? 'UserInfo.PublicVideo' : 'UserInfo.PublicPhoto')}
</div>
</div>
)}
@ -344,24 +356,24 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
{!isFirst && (
<button
type="button"
dir={lang.isRtl ? 'rtl' : undefined}
dir={oldLang.isRtl ? 'rtl' : undefined}
className={buildClassName(styles.navigation, styles.navigation_prev)}
aria-label={lang('AccDescrPrevious')}
aria-label={oldLang('AccDescrPrevious')}
onClick={selectPreviousMedia}
/>
)}
{!isLast && (
<button
type="button"
dir={lang.isRtl ? 'rtl' : undefined}
dir={oldLang.isRtl ? 'rtl' : undefined}
className={buildClassName(styles.navigation, styles.navigation_next)}
aria-label={lang('Next')}
aria-label={oldLang('Next')}
onClick={selectNextMedia}
/>
)}
</div>
<div className={styles.info} dir={lang.isRtl ? 'rtl' : 'auto'}>
<div className={styles.info} dir={oldLang.isRtl ? 'rtl' : 'auto'}>
{(user || chat) && (
<FullNameTitle
peer={(user || chat)!}

View File

@ -12,7 +12,7 @@ import {
} from '../../global/helpers';
import { filterPeersByQuery } from '../../global/helpers/peers';
import {
filterChatIdsByType, selectChat, selectChatFullInfo, selectUser,
filterChatIdsByType, selectChat, selectChatFullInfo, selectIsMonoforumAdmin, selectUser,
} from '../../global/selectors';
import { unique } from '../../util/iteratees';
import sortChatIds from './helpers/sortChatIds';
@ -77,9 +77,15 @@ const RecipientPicker: FC<OwnProps & StateProps> = ({
const user = selectUser(global, id);
if (user && !isDeletedUser(user)) return true;
const chatFullInfo = selectChatFullInfo(global, id);
if (!chat) return false;
return chat && (!chatFullInfo || getCanPostInChat(chat, undefined, undefined, chatFullInfo));
if (chat.isMonoforum && selectIsMonoforumAdmin(global, id)) {
return false;
}
const chatFullInfo = selectChatFullInfo(global, id);
// TODO: Handle bulk check with API call
return !chatFullInfo || getCanPostInChat(chat, undefined, undefined, chatFullInfo);
});
const sorted = sortChatIds(

View File

@ -10,6 +10,7 @@ import type { MessageList } from '../../types';
import { EMOJI_SIZE_MODAL, STICKER_SIZE_MODAL, TME_LINK_PREFIX } from '../../config';
import { getAllowedAttachmentOptions, getCanPostInChat } from '../../global/helpers';
import {
selectBot,
selectCanScheduleUntilOnline,
selectChat,
selectChatFullInfo,
@ -261,7 +262,12 @@ export default memo(withGlobal<OwnProps>(
const { chatId, threadId } = currentMessageList || {};
const chat = chatId && selectChat(global, chatId);
const chatFullInfo = chatId ? selectChatFullInfo(global, chatId) : undefined;
const sendOptions = chat ? getAllowedAttachmentOptions(chat, chatFullInfo) : undefined;
const chatBot = chatId && selectBot(global, chatId);
const isSavedMessages = chatId ? selectIsChatWithSelf(global, chatId) : undefined;
const sendOptions = chat
? getAllowedAttachmentOptions(chat, chatFullInfo, Boolean(chatBot), isSavedMessages)
: undefined;
const threadInfo = chatId && threadId ? selectThreadInfo(global, chatId, threadId) : undefined;
const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId);
const topic = chatId && threadId ? selectTopic(global, chatId, threadId) : undefined;
@ -269,7 +275,6 @@ export default memo(withGlobal<OwnProps>(
chat && threadId && getCanPostInChat(chat, topic, isMessageThread, chatFullInfo)
&& sendOptions?.canSendStickers,
);
const isSavedMessages = Boolean(chatId) && selectIsChatWithSelf(global, chatId);
const stickerSetInfo = fromSticker ? fromSticker.stickerSetInfo
: stickerSetShortName ? { shortName: stickerSetShortName } : undefined;

View File

@ -14,7 +14,7 @@ import {
getCanPostInChat, getGroupStatus, getUserStatus, isUserOnline,
} from '../../../global/helpers';
import { isApiPeerChat } from '../../../global/helpers/peers';
import { selectPeer, selectTopics, selectUserStatus } from '../../../global/selectors';
import { selectMonoforumChannel, selectPeer, selectTopics, selectUserStatus } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName';
import { REM } from '../helpers/mediaDimensions';
import renderText from '../helpers/renderText';
@ -47,12 +47,12 @@ export type OwnProps = {
searchPlaceholder: string;
search: string;
className?: string;
isLowStackPriority?: boolean;
loadMore?: NoneToVoidFunction;
onSearchChange: (search: string) => void;
onSelectChatOrUser: (chatOrUserId: string, threadId?: ThreadId) => void;
onClose: NoneToVoidFunction;
onCloseAnimationEnd?: NoneToVoidFunction;
isLowStackPriority?: boolean;
};
const CHAT_LIST_SLIDE = 0;
@ -77,7 +77,7 @@ const ChatOrUserPicker: FC<OwnProps> = ({
}) => {
const { loadTopics } = getActions();
const lang = useOldLang();
const oldLang = useOldLang();
const containerRef = useRef<HTMLDivElement>();
const topicContainerRef = useRef<HTMLDivElement>();
const searchRef = useRef<HTMLInputElement>();
@ -181,23 +181,28 @@ const ChatOrUserPicker: FC<OwnProps> = ({
const renderChatItem = useCallback((id: string, index: number) => {
const global = getGlobal();
const peer = selectPeer(global, id);
let peer = selectPeer(global, id);
if (!peer) {
return undefined;
}
const monoforumChannel = selectMonoforumChannel(global, id);
if (monoforumChannel) {
peer = monoforumChannel;
}
const isSelf = peer && !isApiPeerChat(peer) ? peer.isSelf : undefined;
function getSubtitle() {
if (!peer) return undefined;
if (peer.id === currentUserId) return [lang('SavedMessagesInfo')];
if (peer.id === currentUserId) return [oldLang('SavedMessagesInfo')];
if (isApiPeerChat(peer)) {
return [getGroupStatus(lang, peer)];
return [getGroupStatus(oldLang, peer)];
}
const userStatus = selectUserStatus(global, peer.id);
return [
getUserStatus(lang, peer, userStatus),
getUserStatus(oldLang, peer, userStatus),
buildClassName(isUserOnline(peer, userStatus, true) && 'online'),
];
}
@ -208,10 +213,20 @@ const ChatOrUserPicker: FC<OwnProps> = ({
<PickerItem
key={id}
className={ITEM_CLASS_NAME}
title={<FullNameTitle peer={peer} isSavedMessages={isSelf} />}
title={(
<div className="title-wrapper">
<FullNameTitle
className="item-title"
peer={peer}
isMonoforum={Boolean(monoforumChannel)}
isSavedMessages={isSelf}
/>
</div>
)}
avatarElement={(
<Avatar
peer={peer}
asMessageBubble={Boolean(monoforumChannel)}
isSavedMessages={isSelf}
size="medium"
/>
@ -224,13 +239,13 @@ const ChatOrUserPicker: FC<OwnProps> = ({
onClick={() => handleClick(id)}
/>
);
}, [currentUserId, lang, viewportOffset]);
}, [currentUserId, oldLang, viewportOffset]);
function renderTopicList() {
return (
<>
<div className="modal-header" dir={lang.isRtl ? 'rtl' : undefined}>
<Button round color="translucent" size="smaller" ariaLabel={lang('Back')} onClick={handleHeaderBackClick}>
<div className="modal-header" dir={oldLang.isRtl ? 'rtl' : undefined}>
<Button round color="translucent" size="smaller" ariaLabel={oldLang('Back')} onClick={handleHeaderBackClick}>
<Icon name="arrow-left" />
</Button>
<InputText
@ -276,12 +291,12 @@ const ChatOrUserPicker: FC<OwnProps> = ({
function renderChatList() {
return (
<>
<div className="modal-header" dir={lang.isRtl ? 'rtl' : undefined}>
<div className="modal-header" dir={oldLang.isRtl ? 'rtl' : undefined}>
<Button
round
color="translucent"
size="smaller"
ariaLabel={lang('Close')}
ariaLabel={oldLang('Close')}
onClick={onClose}
>
<Icon name="close" />
@ -308,7 +323,7 @@ const ChatOrUserPicker: FC<OwnProps> = ({
{viewportIds.map(renderChatItem)}
</InfiniteScroll>
) : viewportIds && !viewportIds.length ? (
<p className="no-results">{lang('lng_blocked_list_not_found')}</p>
<p className="no-results">{oldLang('lng_blocked_list_not_found')}</p>
) : (
<Loading />
)}

View File

@ -438,6 +438,11 @@
.avatar-badge-wrapper .ChatBadge:not(.pinned).muted {
background: var(--color-gray);
}
.monoforum-badge {
color: var(--color-chat-active);
background-color: #fff;
}
}
}

View File

@ -35,6 +35,7 @@ import {
selectIsCurrentUserFrozen,
selectIsForumPanelClosed,
selectIsForumPanelOpen,
selectMonoforumChannel,
selectNotifyDefaults,
selectNotifyException,
selectOutgoingStatus,
@ -94,6 +95,7 @@ type OwnProps = {
type StateProps = {
chat?: ApiChat;
monoforumChannel?: ApiChat;
lastMessageStory?: ApiTypeStory;
listedTopicIds?: number[];
topics?: Record<number, ApiTopic>;
@ -128,6 +130,7 @@ const Chat: FC<OwnProps & StateProps> = ({
topics,
observeIntersection,
chat,
monoforumChannel,
lastMessageStory,
isMuted,
user,
@ -177,7 +180,7 @@ const Chat: FC<OwnProps & StateProps> = ({
const [shouldRenderMuteModal, markRenderMuteModal, unmarkRenderMuteModal] = useFlag();
const [shouldRenderChatFolderModal, markRenderChatFolderModal, unmarkRenderChatFolderModal] = useFlag();
const { isForum, isForumAsMessages } = chat || {};
const { isForum, isForumAsMessages, isMonoforum } = chat || {};
useEnsureMessage(isSavedDialog ? currentUserId : chatId, lastMessageId, lastMessage);
@ -355,11 +358,12 @@ const Chat: FC<OwnProps & StateProps> = ({
>
<div className={buildClassName('status', 'status-clickable')}>
<Avatar
peer={peer}
peer={isMonoforum ? monoforumChannel : peer}
isSavedMessages={user?.isSelf}
isSavedDialog={isSavedDialog}
size={isPreview ? 'medium' : 'large'}
withStory={!user?.isSelf}
asMessageBubble={isMonoforum}
withStory={!user?.isSelf && !isMonoforum}
withStoryGap={isAvatarOnlineShown || Boolean(chat.subscriptionUntil)}
storyViewerOrigin={StoryViewerOrigin.ChatList}
storyViewerMode="single-peer"
@ -387,7 +391,9 @@ const Chat: FC<OwnProps & StateProps> = ({
<div className="info">
<div className="info-row">
<FullNameTitle
peer={peer}
peer={isMonoforum ? monoforumChannel! : peer}
isMonoforum={isMonoforum}
monoforumBadgeClassName="monoforum-badge"
withEmojiStatus
isSavedMessages={chatId === user?.id && user?.isSelf}
isSavedDialog={isSavedDialog}
@ -489,6 +495,8 @@ export default memo(withGlobal<OwnProps>(
const lastMessageStory = storyData && selectPeerStory(global, storyData.peerId, storyData.id);
const isAccountFrozen = selectIsCurrentUserFrozen(global);
const monoforumChannel = selectMonoforumChannel(global, chatId);
return {
chat,
isMuted: getIsChatMuted(chat, selectNotifyDefaults(global), selectNotifyException(global, chat.id)),
@ -515,6 +523,7 @@ export default memo(withGlobal<OwnProps>(
isSynced: global.isSynced,
lastMessageStory,
isAccountFrozen,
monoforumChannel,
};
},
)(Chat));

View File

@ -74,6 +74,7 @@ interface StateProps {
canLeave?: boolean;
canEnterVoiceChat?: boolean;
canCreateVoiceChat?: boolean;
channelMonoforumId?: string;
pendingJoinRequests?: number;
shouldJoinToSend?: boolean;
shouldSendJoinRequest?: boolean;
@ -110,6 +111,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
canLeave,
canEnterVoiceChat,
canCreateVoiceChat,
channelMonoforumId,
pendingJoinRequests,
isRightColumnShown,
isForForum,
@ -451,6 +453,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
pendingJoinRequests={pendingJoinRequests}
onJoinRequestsClick={handleJoinRequestsClick}
withForumActions={isForForum}
channelMonoforumId={channelMonoforumId}
onSubscribeChannel={handleSubscribeClick}
onSearchClick={handleSearchClick}
onAsMessagesClick={handleAsMessagesClick}
@ -499,7 +502,7 @@ export default memo(withGlobal<OwnProps>(
const canStartBot = !canRestartBot && Boolean(selectIsChatBotNotStarted(global, chatId));
const canUnblock = isUserBlocked && !bot;
const canSubscribe = Boolean(
(isMainThread || chat.isForum) && (isChannel || isSuperGroup) && chat.isNotJoined,
(isMainThread || chat.isForum) && (isChannel || isSuperGroup) && chat.isNotJoined && !chat.isMonoforum,
);
const canSearch = isMainThread || isDiscussionThread;
const canCall = ARE_CALLS_SUPPORTED && isUserId(chat.id) && !isChatWithSelf && !bot && !chat.isSupport
@ -508,12 +511,12 @@ export default memo(withGlobal<OwnProps>(
const canLeave = isSavedDialog || (isMainThread && !canSubscribe);
const canEnterVoiceChat = ARE_CALLS_SUPPORTED && isMainThread && chat.isCallActive;
const canCreateVoiceChat = ARE_CALLS_SUPPORTED && isMainThread && !chat.isCallActive
&& (chat.adminRights?.manageCall || (chat.isCreator && isChatBasicGroup(chat)));
&& (chat.adminRights?.manageCall || (chat.isCreator && isChatBasicGroup(chat))) && !chat.isMonoforum;
const canViewStatistics = isMainThread && chatFullInfo?.canViewStatistics;
const canViewMonetization = isMainThread && chatFullInfo?.canViewMonetization;
const canViewBoosts = isMainThread
const canViewBoosts = isMainThread && !chat.isMonoforum
&& (isSuperGroup || isChannel) && (canViewStatistics || getHasAdminRight(chat, 'postStories'));
const canShowBoostModal = !canViewBoosts && (isSuperGroup || isChannel);
const canShowBoostModal = !canViewBoosts && (isSuperGroup || isChannel) && !chat.isMonoforum;
const pendingJoinRequests = isMainThread ? chatFullInfo?.requestsPending : undefined;
const shouldJoinToSend = Boolean(chat?.isNotJoined && chat.isJoinToSend);
const shouldSendJoinRequest = Boolean(chat?.isNotJoined && chat.isJoinRequest);
@ -523,6 +526,8 @@ export default memo(withGlobal<OwnProps>(
const canTranslate = selectCanTranslateChat(global, chatId) && !fullInfo?.isTranslationDisabled;
const isAccountFrozen = selectIsCurrentUserFrozen(global);
const channelMonoforumId = isChatChannel(chat) ? chat.linkedMonoforumId : undefined;
return {
noMenu: false,
isChannel,
@ -552,6 +557,7 @@ export default memo(withGlobal<OwnProps>(
detectedChatLanguage: chat.detectedLanguage,
canUnblock,
isAccountFrozen,
channelMonoforumId,
};
},
)(HeaderActions));

View File

@ -17,6 +17,7 @@ import {
getCanManageTopic,
getHasAdminRight,
getIsSavedDialog,
isChatAdmin,
isChatChannel,
isChatGroup,
isSystemBot,
@ -94,6 +95,7 @@ export type OwnProps = {
canCreateVoiceChat?: boolean;
pendingJoinRequests?: number;
canTranslate?: boolean;
channelMonoforumId?: string;
onSubscribeChannel: () => void;
onSearchClick: () => void;
onAsMessagesClick: () => void;
@ -176,14 +178,15 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
isChatWithSelf,
savedDialog,
canShowBoostModal,
disallowedGifts,
isAccountFrozen,
channelMonoforumId,
onJoinRequestsClick,
onSubscribeChannel,
onSearchClick,
onAsMessagesClick,
onClose,
onCloseAnimationEnd,
disallowedGifts,
isAccountFrozen,
}) => {
const {
updateChatMutedState,
@ -488,6 +491,11 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
closeMenu();
});
const handleSendChannelMessage = useLastCallback(() => {
openChat({ id: channelMonoforumId });
closeMenu();
});
useEffect(disableScrolling, []);
const botButtons = useMemo(() => {
@ -585,6 +593,14 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
<MenuSeparator />
</>
)}
{channelMonoforumId && (
<MenuItem
icon="message"
onClick={handleSendChannelMessage}
>
{lang('ChannelSendMessage')}
</MenuItem>
)}
{isViewGroupInfoShown && (
<MenuItem
icon="info"
@ -835,7 +851,7 @@ export default memo(withGlobal<OwnProps>(
const isMainThread = threadId === MAIN_THREAD_ID;
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
const { chatId: currentChatId, threadId: currentThreadId } = selectCurrentMessageList(global) || {};
const canReportChat = isMainThread && !user && (isChatChannel(chat) || isChatGroup(chat));
const canReportChat = isMainThread && !user && (isChatChannel(chat) || isChatGroup(chat)) && !isChatAdmin(chat);
const chatBot = !isSystemBot(chatId) ? selectBot(global, chatId) : undefined;
const userFullInfo = isPrivate ? selectUserFullInfo(global, chatId) : undefined;

View File

@ -28,6 +28,7 @@ import {
isAnonymousForwardsChat,
isChatChannel,
isChatGroup,
isChatMonoforum,
isSystemBot,
} from '../../global/helpers';
import {
@ -48,6 +49,7 @@ import {
selectIsInSelectMode,
selectIsViewportNewest,
selectLastScrollOffset,
selectMonoforumChannel,
selectPerformanceSettingsValue,
selectScrollOffset,
selectTabState,
@ -107,6 +109,7 @@ type StateProps = {
isChatLoaded?: boolean;
isChannelChat?: boolean;
isGroupChat?: boolean;
isChatMonoforum?: boolean;
isChatWithSelf?: boolean;
isSystemBotChat?: boolean;
isAnonymousForwards?: boolean;
@ -139,6 +142,7 @@ type StateProps = {
isChatProtected?: boolean;
hasCustomGreeting?: boolean;
isAppConfigLoaded?: boolean;
monoforumChannelId?: string;
canTranslate?: boolean;
translationLanguage?: string;
shouldAutoTranslate?: boolean;
@ -169,6 +173,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
isChannelWithAvatars,
canPost,
isSynced,
isChatMonoforum,
isReady,
isChatWithSelf,
isSystemBotChat,
@ -203,6 +208,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
isChatProtected,
isAccountFrozen,
hasCustomGreeting,
monoforumChannelId,
isAppConfigLoaded,
canTranslate,
translationLanguage,
@ -695,7 +701,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
isChatProtected && 'hide-on-print',
);
const hasMessages = (messageIds && messageGroups) || lastMessage;
const hasMessages = Boolean((messageIds && messageGroups) || lastMessage);
useEffect(() => {
if (hasMessages) return;
@ -716,12 +722,12 @@ const MessageList: FC<OwnProps & StateProps> = ({
{restrictionReason ? restrictionReason.text : `This is a private ${isChannelChat ? 'channel' : 'chat'}`}
</span>
</div>
) : paidMessagesStars && isPrivate && !hasMessages && !hasCustomGreeting ? (
<RequirementToContactMessage paidMessagesStars={paidMessagesStars} userId={chatId} />
) : paidMessagesStars && !hasMessages && !hasCustomGreeting ? (
<RequirementToContactMessage paidMessagesStars={paidMessagesStars} peerId={monoforumChannelId || chatId} />
) : isContactRequirePremium && !hasMessages ? (
<RequirementToContactMessage userId={chatId} />
<RequirementToContactMessage peerId={chatId} />
) : (isBot || isNonContact) && !hasMessages ? (
<MessageListAccountInfo chatId={chatId} />
<MessageListAccountInfo chatId={chatId} hasMessages={hasMessages} />
) : shouldRenderGreeting ? (
<ContactGreeting key={chatId} userId={chatId} />
) : messageIds && (!messageGroups || isGroupChatJustCreated || isEmptyTopic) ? (
@ -738,6 +744,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
chatId={chatId}
isComments={isComments}
isChannelChat={isChannelChat}
isChatMonoforum={isChatMonoforum}
isSavedDialog={isSavedDialog}
messageIds={messageIds || [lastMessage!.id]}
messageGroups={messageGroups || groupMessages([lastMessage!])}
@ -820,6 +827,7 @@ export default memo(withGlobal<OwnProps>(
const hasCustomGreeting = Boolean(userFullInfo?.businessIntro);
const isAppConfigLoaded = global.isAppConfigLoaded;
const monoforumChannelId = selectMonoforumChannel(global, chatId)?.id;
const canTranslate = selectCanTranslateChat(global, chatId) && !chatFullInfo?.isTranslationDisabled;
const shouldAutoTranslate = chat?.hasAutoTranslation;
const translationLanguage = selectTranslationLanguage(global);
@ -830,6 +838,7 @@ export default memo(withGlobal<OwnProps>(
isRestricted,
restrictionReason,
isChannelChat: isChatChannel(chat),
isChatMonoforum: isChatMonoforum(chat),
isGroupChat: isChatGroup(chat),
isChannelWithAvatars: chat.areProfilesShown,
isCreator: chat.isCreator,
@ -860,6 +869,7 @@ export default memo(withGlobal<OwnProps>(
isAccountFrozen,
hasCustomGreeting,
isAppConfigLoaded,
monoforumChannelId,
canTranslate,
translationLanguage,
shouldAutoTranslate,

View File

@ -46,7 +46,7 @@ import styles from './MessageListAccountInfo.module.scss';
type OwnProps = {
chatId: string;
isInMessageList?: boolean;
hasMessages?: boolean;
};
type StateProps = {
@ -66,6 +66,7 @@ const MessageListAccountInfo: FC<OwnProps & StateProps> = ({
phoneCodeList,
commonChats,
userFullInfo,
hasMessages,
}) => {
const { loadCommonChats, openChatWithInfo } = getActions();
const oldLang = useOldLang();
@ -170,7 +171,9 @@ const MessageListAccountInfo: FC<OwnProps & StateProps> = ({
return (
<div className={buildClassName(styles.root, 'empty')}>
{isLoadingFullUser && isChatInfoEmpty && <span>{oldLang('Loading')}</span>}
{(isBotInfoEmpty && isChatInfoEmpty) && !isLoadingFullUser && <span>{oldLang('NoMessages')}</span>}
{(isBotInfoEmpty && isChatInfoEmpty) && !isLoadingFullUser && !hasMessages && (
<span>{oldLang('NoMessages')}</span>
)}
{botInfo && (
<div
className={buildClassName(styles.chatInfo, styles.botBackground)}

View File

@ -50,6 +50,7 @@ interface OwnProps {
isUnread: boolean;
withUsers: boolean;
isChannelChat: boolean | undefined;
isChatMonoforum?: boolean;
isEmptyThread?: boolean;
isComments?: boolean;
noAvatars: boolean;
@ -87,6 +88,7 @@ const MessageListContent: FC<OwnProps> = ({
isEmptyThread,
withUsers,
isChannelChat,
isChatMonoforum,
noAvatars,
containerRef,
anchorIdRef,
@ -261,7 +263,7 @@ const MessageListContent: FC<OwnProps> = ({
// Service notifications saved in cache in previous versions may share the same `previousLocalId`
const key = isServiceNotificationMessage(message) ? `${message.date}_${originalId}` : originalId;
const noComments = hasLinkedChat === false || !isChannelChat;
const noComments = hasLinkedChat === false || !isChannelChat || Boolean(isChatMonoforum);
return compact([
message.id === memoUnreadDividerBeforeIdRef.current && unreadDivider,
@ -377,7 +379,7 @@ const MessageListContent: FC<OwnProps> = ({
<div className="messages-container" teactFastList>
{withHistoryTriggers && <div ref={backwardsTriggerRef} key="backwards-trigger" className="backwards-trigger" />}
{shouldRenderAccountInfo
&& <MessageListAccountInfo key={`account_info_${chatId}`} chatId={chatId} />}
&& <MessageListAccountInfo key={`account_info_${chatId}`} chatId={chatId} hasMessages />}
{dateGroups.flat()}
{withHistoryTriggers && (
<div

View File

@ -51,6 +51,7 @@ import {
selectIsChatBotNotStarted,
selectIsCurrentUserFrozen,
selectIsInSelectMode,
selectIsMonoforumAdmin,
selectIsRightColumnShown,
selectIsUserBlocked,
selectPeerPaidMessagesStars,
@ -76,6 +77,7 @@ import useAppLayout from '../../hooks/useAppLayout';
import useCustomBackground from '../../hooks/useCustomBackground';
import useForceUpdate from '../../hooks/useForceUpdate';
import useHistoryBack from '../../hooks/useHistoryBack';
import useLang from '../../hooks/useLang';
import useLastCallback from '../../hooks/useLastCallback';
import useOldLang from '../../hooks/useOldLang';
import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation';
@ -163,6 +165,7 @@ type StateProps = {
paidMessagesStars?: number;
isAccountFrozen?: boolean;
freezeAppealChat?: ApiChat;
shouldBlockSendInMonoforum?: boolean;
};
function isImage(item: DataTransferItem) {
@ -226,6 +229,7 @@ function MiddleColumn({
paidMessagesStars,
isAccountFrozen,
freezeAppealChat,
shouldBlockSendInMonoforum,
}: OwnProps & StateProps) {
const {
openChat,
@ -247,7 +251,8 @@ function MiddleColumn({
const { width: windowWidth } = useWindowSize();
const { isTablet, isDesktop } = useAppLayout();
const lang = useOldLang();
const oldLang = useOldLang();
const lang = useLang();
const [dropAreaState, setDropAreaState] = useState(DropAreaState.None);
const [isScrollDownNeeded, setIsScrollDownShown] = useState(false);
const isScrollDownShown = isScrollDownNeeded && (!isMobile || !hasActiveMiddleSearch);
@ -412,7 +417,8 @@ function MiddleColumn({
joinChannel({ chatId: chatId! });
if (renderingShouldSendJoinRequest) {
showNotification({
message: isChannel ? lang('RequestToJoinChannelSentDescription') : lang('RequestToJoinGroupSentDescription'),
message: isChannel
? oldLang('RequestToJoinChannelSentDescription') : oldLang('RequestToJoinGroupSentDescription'),
});
}
});
@ -451,14 +457,17 @@ function MiddleColumn({
);
const messageSendingRestrictionReason = getMessageSendingRestrictionReason(
lang, currentUserBannedRights, defaultBannedRights,
oldLang, currentUserBannedRights, defaultBannedRights,
);
const forumComposerPlaceholder = getForumComposerPlaceholder(
oldLang, chat, threadId, topics, Boolean(draftReplyInfo),
);
const forumComposerPlaceholder = getForumComposerPlaceholder(lang, chat, threadId, topics, Boolean(draftReplyInfo));
const composerRestrictionMessage = messageSendingRestrictionReason
?? forumComposerPlaceholder
?? (isContactRequirePremium ? <PremiumRequiredPlaceholder userId={chatId!} /> : undefined)
?? (isAccountFrozen && freezeAppealChat?.id !== chatId ? <FrozenAccountPlaceholder /> : undefined);
|| forumComposerPlaceholder
|| (shouldBlockSendInMonoforum ? lang('MonoforumComposerPlaceholder') : undefined)
|| (isContactRequirePremium ? <PremiumRequiredPlaceholder userId={chatId!} /> : undefined)
|| (isAccountFrozen && freezeAppealChat?.id !== chatId ? <FrozenAccountPlaceholder /> : undefined);
// CSS Variables calculation doesn't work properly with transforms, so we calculate transform values in JS
const {
@ -587,7 +596,7 @@ function MiddleColumn({
/>
)}
{isPinnedMessageList && canUnpin && (
<div className="middle-column-footer-button-container" dir={lang.isRtl ? 'rtl' : undefined}>
<div className="middle-column-footer-button-container" dir={oldLang.isRtl ? 'rtl' : undefined}>
<Button
size="tiny"
fluid
@ -596,12 +605,12 @@ function MiddleColumn({
onClick={handleOpenUnpinModal}
>
<Icon name="unpin" />
<span>{lang('Chat.Pinned.UnpinAll', pinnedMessagesCount, 'i')}</span>
<span>{oldLang('Chat.Pinned.UnpinAll', pinnedMessagesCount, 'i')}</span>
</Button>
</div>
)}
{canShowOpenChatButton && (
<div className="middle-column-footer-button-container" dir={lang.isRtl ? 'rtl' : undefined}>
<div className="middle-column-footer-button-container" dir={oldLang.isRtl ? 'rtl' : undefined}>
<Button
size="tiny"
fluid
@ -609,7 +618,7 @@ function MiddleColumn({
className="composer-button open-chat-button"
onClick={handleOpenChatFromSaved}
>
<span>{lang('SavedOpenChat')}</span>
<span>{oldLang('SavedOpenChat')}</span>
</Button>
</div>
)}
@ -625,7 +634,7 @@ function MiddleColumn({
{(
isMobile && (renderingCanSubscribe || (renderingShouldJoinToSend && !renderingShouldSendJoinRequest))
) && (
<div className="middle-column-footer-button-container" dir={lang.isRtl ? 'rtl' : undefined}>
<div className="middle-column-footer-button-container" dir={oldLang.isRtl ? 'rtl' : undefined}>
<Button
size="tiny"
fluid
@ -633,12 +642,12 @@ function MiddleColumn({
className="composer-button join-subscribe-button"
onClick={handleSubscribeClick}
>
{lang(renderingIsChannel ? 'ProfileJoinChannel' : 'ProfileJoinGroup')}
{oldLang(renderingIsChannel ? 'ProfileJoinChannel' : 'ProfileJoinGroup')}
</Button>
</div>
)}
{isMobile && renderingShouldSendJoinRequest && (
<div className="middle-column-footer-button-container" dir={lang.isRtl ? 'rtl' : undefined}>
<div className="middle-column-footer-button-container" dir={oldLang.isRtl ? 'rtl' : undefined}>
<Button
size="tiny"
fluid
@ -646,12 +655,12 @@ function MiddleColumn({
className="composer-button join-subscribe-button"
onClick={handleSubscribeClick}
>
{lang('ChannelJoinRequest')}
{oldLang('ChannelJoinRequest')}
</Button>
</div>
)}
{isMobile && renderingCanStartBot && (
<div className="middle-column-footer-button-container" dir={lang.isRtl ? 'rtl' : undefined}>
<div className="middle-column-footer-button-container" dir={oldLang.isRtl ? 'rtl' : undefined}>
<Button
size="tiny"
fluid
@ -659,12 +668,12 @@ function MiddleColumn({
className="composer-button join-subscribe-button"
onClick={handleStartBot}
>
{lang('BotStart')}
{oldLang('BotStart')}
</Button>
</div>
)}
{isMobile && renderingCanRestartBot && (
<div className="middle-column-footer-button-container" dir={lang.isRtl ? 'rtl' : undefined}>
<div className="middle-column-footer-button-container" dir={oldLang.isRtl ? 'rtl' : undefined}>
<Button
size="tiny"
fluid
@ -672,12 +681,12 @@ function MiddleColumn({
className="composer-button join-subscribe-button"
onClick={handleRestartBot}
>
{lang('BotRestart')}
{oldLang('BotRestart')}
</Button>
</div>
)}
{isMobile && renderingCanUnblock && (
<div className="middle-column-footer-button-container" dir={lang.isRtl ? 'rtl' : undefined}>
<div className="middle-column-footer-button-container" dir={oldLang.isRtl ? 'rtl' : undefined}>
<Button
size="tiny"
fluid
@ -685,7 +694,7 @@ function MiddleColumn({
className="composer-button join-subscribe-button"
onClick={handleUnblock}
>
{lang('Unblock')}
{oldLang('Unblock')}
</Button>
</div>
)}
@ -788,7 +797,8 @@ export default memo(withGlobal<OwnProps>(
const isMainThread = messageListType === 'thread' && threadId === MAIN_THREAD_ID;
const isChannel = Boolean(chat && isChatChannel(chat));
const canSubscribe = Boolean(
chat && isMainThread && (isChannel || isChatSuperGroup(chat)) && chat.isNotJoined && !chat.joinRequests,
chat && isMainThread && (isChannel || isChatSuperGroup(chat)) && chat.isNotJoined && !chat.joinRequests
&& !chat.isMonoforum,
);
const shouldJoinToSend = Boolean(chat?.isNotJoined && chat.isJoinToSend);
const shouldSendJoinRequest = Boolean(chat?.isNotJoined && chat.isJoinRequest);
@ -803,6 +813,8 @@ export default memo(withGlobal<OwnProps>(
const shouldBlockSendInForum = chat?.isForum
? threadId === MAIN_THREAD_ID && !draftReplyInfo && (selectTopic(global, chatId, GENERAL_TOPIC_ID)?.isClosed)
: false;
const isMonoforumAdmin = selectIsMonoforumAdmin(global, chatId);
const shouldBlockSendInMonoforum = Boolean(chat?.isMonoforum && !draftReplyInfo && isMonoforumAdmin);
const topics = selectTopics(global, chatId);
const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);
@ -840,6 +852,7 @@ export default memo(withGlobal<OwnProps>(
&& !isBotNotStarted
&& !(shouldJoinToSend && chat?.isNotJoined)
&& !shouldBlockSendInForum
&& !shouldBlockSendInMonoforum
&& !isSavedDialog
&& (!isAccountFrozen || freezeAppealChat?.id === chatId),
isPinnedMessageList,
@ -864,6 +877,7 @@ export default memo(withGlobal<OwnProps>(
paidMessagesStars,
isAccountFrozen,
freezeAppealChat,
shouldBlockSendInMonoforum,
};
},
)(MiddleColumn));

View File

@ -261,6 +261,8 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
const savedMessagesStatus = isSavedDialog ? lang('SavedMessages') : undefined;
const realChatId = isSavedDialog ? String(threadId) : chatId;
const displayChatId = chat?.isMonoforum ? chat.linkedMonoforumId! : realChatId;
return (
<>
{(isLeftColumnHideable || currentTransitionKey > 0) && renderBackButton(shouldShowCloseButton, !isSavedDialog)}
@ -272,10 +274,10 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
onTouchStart={handleLongPressTouchStart}
onTouchEnd={handleLongPressTouchEnd}
>
{isUserId(realChatId) ? (
{isUserId(displayChatId) ? (
<PrivateChatInfo
key={realChatId}
userId={realChatId}
key={displayChatId}
userId={displayChatId}
typingStatus={typingStatus}
status={connectionStatusText || savedMessagesStatus}
withDots={Boolean(connectionStatusText)}
@ -291,10 +293,11 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
/>
) : (
<GroupChatInfo
key={realChatId}
chatId={realChatId}
key={displayChatId}
chatId={displayChatId}
threadId={!isSavedDialog ? threadId : undefined}
typingStatus={typingStatus}
withMonoforumStatus={chat?.isMonoforum}
status={connectionStatusText || savedMessagesStatus}
withDots={Boolean(connectionStatusText)}
withMediaViewer={threadId === MAIN_THREAD_ID}

View File

@ -1,8 +1,10 @@
import { memo } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import { getUserFirstOrLastName } from '../../global/helpers';
import { selectTheme, selectThemeValues, selectUser } from '../../global/selectors';
import type { ApiPeer } from '../../api/types';
import { getPeerTitle, isApiPeerUser } from '../../global/helpers/peers';
import { selectPeer, selectTheme, selectThemeValues } from '../../global/selectors';
import { formatStarsAsIcon } from '../../util/localization/format';
import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets';
import renderText from '../common/helpers/renderText';
@ -19,17 +21,18 @@ import Button from '../ui/Button';
import styles from './RequirementToContactMessage.module.scss';
type OwnProps = {
userId: string;
peerId: string;
paidMessagesStars?: number;
};
type StateProps = {
patternColor?: string;
userName?: string;
peer?: ApiPeer;
};
function RequirementToContactMessage({ patternColor, userName, paidMessagesStars }: OwnProps & StateProps) {
function RequirementToContactMessage({
patternColor, peer, paidMessagesStars,
}: OwnProps & StateProps) {
const oldLang = useOldLang();
const lang = useLang();
const { openPremiumModal, openStarsBalanceModal } = getActions();
@ -40,6 +43,8 @@ function RequirementToContactMessage({ patternColor, userName, paidMessagesStars
openStarsBalanceModal({});
});
if (!peer) return undefined;
return (
<div className={styles.root}>
<div className={styles.inner}>
@ -55,8 +60,8 @@ function RequirementToContactMessage({ patternColor, userName, paidMessagesStars
<span className={styles.description}>
{
paidMessagesStars
? lang('FirstMessageInPaidMessagesChat', {
user: userName,
? lang(isApiPeerUser(peer) ? 'MessagesPlaceholderPaidUser' : 'MessagesPlaceholderPaidChannel', {
peer: getPeerTitle(lang, peer),
amount: formatStarsAsIcon(lang,
paidMessagesStars,
{
@ -68,7 +73,7 @@ function RequirementToContactMessage({ patternColor, userName, paidMessagesStars
withNodes: true,
withMarkdown: true,
})
: renderText(oldLang('MessageLockedPremium', userName), ['simple_markdown'])
: renderText(oldLang('MessageLockedPremium', getPeerTitle(lang, peer)), ['simple_markdown'])
}
</span>
<Button
@ -95,14 +100,14 @@ function RequirementToContactMessage({ patternColor, userName, paidMessagesStars
}
export default memo(
withGlobal<OwnProps>((global, { userId }): StateProps => {
withGlobal<OwnProps>((global, { peerId: userId }): StateProps => {
const theme = selectTheme(global);
const { patternColor } = selectThemeValues(global, theme) || {};
const user = selectUser(global, userId);
const peer = selectPeer(global, userId);
return {
patternColor,
userName: getUserFirstOrLastName(user),
peer,
};
})(RequirementToContactMessage),
);

View File

@ -1,6 +1,5 @@
import type { ChangeEvent } from 'react';
import type { ElementRef, FC } from '../../../lib/teact/teact';
import type React from '../../../lib/teact/teact';
import type { ElementRef } from '../../../lib/teact/teact';
import {
memo, useEffect, useRef, useState,
} from '../../../lib/teact/teact';
@ -28,19 +27,25 @@ export type OwnProps = {
isOpen: boolean;
shouldBeAnonymous?: boolean;
isQuiz?: boolean;
maxOptionsCount?: number;
onSend: (pollSummary: ApiNewPoll) => void;
onClear: () => void;
};
const MAX_LIST_HEIGHT = 320;
const MAX_OPTIONS_COUNT = 10;
const FALLBACK_MAX_OPTIONS_COUNT = 12;
const MAX_OPTION_LENGTH = 100;
const MAX_QUESTION_LENGTH = 255;
const MAX_SOLUTION_LENGTH = 200;
const PollModal: FC<OwnProps> = ({
isOpen, isQuiz, shouldBeAnonymous, onSend, onClear,
}) => {
const PollModal = ({
isOpen,
isQuiz,
shouldBeAnonymous,
maxOptionsCount = FALLBACK_MAX_OPTIONS_COUNT,
onSend,
onClear,
}: OwnProps) => {
const questionInputRef = useRef<HTMLInputElement>();
const optionsListRef = useRef<HTMLDivElement>();
@ -170,7 +175,7 @@ const PollModal: FC<OwnProps> = ({
const updateOption = useLastCallback((index: number, text: string) => {
const newOptions = [...options];
newOptions[index] = text;
if (newOptions[newOptions.length - 1].trim().length && newOptions.length < MAX_OPTIONS_COUNT) {
if (newOptions[newOptions.length - 1].trim().length && newOptions.length < maxOptionsCount) {
addNewOption(newOptions);
} else {
setOptions(newOptions);
@ -265,7 +270,7 @@ const PollModal: FC<OwnProps> = ({
<div className="option-wrapper">
<InputText
maxLength={MAX_OPTION_LENGTH}
label={index !== options.length - 1 || options.length === MAX_OPTIONS_COUNT
label={index !== options.length - 1 || options.length === maxOptionsCount
? lang('OptionHint')
: lang('CreatePoll.AddOption')}
error={getOptionsError(index)}

View File

@ -717,13 +717,21 @@ const ActionMessageText = ({
return action.message;
case 'paidMessagesPrice': {
const { stars } = action;
const { stars, isAllowedInChannel } = action;
if (stars === 0) {
return lang('ActionPaidMessageGroupPriceFree');
if (isChannel) {
return lang(
isAllowedInChannel ? 'ActionMessageChannelFree' : 'ActionMessageChannelDisabled',
{ peer: chatLink },
{ withNodes: true },
);
}
return translateWithYou(lang, 'ActionPaidMessagePriceFree', isOutgoing, { peer: senderLink });
}
return lang('ActionPaidMessageGroupPrice', {
stars: formatStarsAsText(lang, stars),
}, { withNodes: true, withMarkdown: true });
return translateWithYou(lang, 'ActionPaidMessagePrice', isOutgoing, {
peer: senderLink,
amount: formatStarsAsText(lang, stars),
}, { withMarkdown: true });
}
case 'paidMessagesRefunded': {

View File

@ -65,6 +65,7 @@ import {
selectPollFromMessage,
selectRequestedChatTranslationLanguage,
selectRequestedMessageTranslationLanguage,
selectSavedDialogIdFromMessage,
selectStickerSet,
selectThreadInfo,
selectTopic,
@ -156,6 +157,7 @@ type StateProps = {
isWithPaidReaction?: boolean;
userFullName?: string;
canGift?: boolean;
savedDialogId?: string;
};
const selection = window.getSelection();
@ -223,6 +225,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
userFullName,
canGift,
className,
savedDialogId,
onClose,
onCloseAnimationEnd,
}) => {
@ -416,6 +419,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
replyToMsgId: message.id,
quoteText,
quoteOffset: selectionQuoteOffset,
monoforumPeerId: savedDialogId,
replyToPeerId: undefined,
});
}
@ -813,6 +817,7 @@ export default memo(withGlobal<OwnProps>(
const hasTtl = hasMessageTtl(message);
const canShowSeenBy = Boolean(!isLocal
&& chat
&& !chat.isMonoforum
&& !isMessageUnread
&& seenByMaxChatMembers
&& seenByExpiresAt
@ -849,6 +854,8 @@ export default memo(withGlobal<OwnProps>(
const canGift = selectCanGift(global, message.chatId);
const savedDialogId = selectSavedDialogIdFromMessage(global, message);
return {
threadId,
chat,
@ -904,6 +911,7 @@ export default memo(withGlobal<OwnProps>(
story,
userFullName,
canGift,
savedDialogId,
};
},
)(ContextMenuContainer));

View File

@ -38,11 +38,11 @@ export function translateWithYou<K extends LangKey>(
options?: {
pluralValue?: number;
asText?: boolean;
isMarkdown?: boolean;
withMarkdown?: boolean;
renderTextFilters?: string[];
},
): TeactNode {
const { pluralValue, asText, isMarkdown, renderTextFilters } = options || {};
const { pluralValue, asText, withMarkdown, renderTextFilters } = options || {};
const translationKey = isYou ? (`${key}You` as LangKey) : key;
return lang(
@ -50,7 +50,7 @@ export function translateWithYou<K extends LangKey>(
translationKey,
variables,
{
withNodes: !asText, isMarkdown, pluralValue, renderTextFilters,
withNodes: !asText, withMarkdown, pluralValue, renderTextFilters,
},
);
}

View File

@ -26,6 +26,7 @@ import {
selectForwardedSender,
selectIsChatWithSelf,
selectIsCurrentUserPremium,
selectMonoforumChannel,
selectSender,
selectTabState,
} from '../../../global/selectors';
@ -67,6 +68,7 @@ export type OwnProps = {
type StateProps = {
isActive?: boolean;
chat?: ApiChat;
monoforumChat?: ApiChat;
threadId?: ThreadId;
requestedQuery?: string;
savedTags?: Record<ApiReactionKey, ApiSavedReactionTag>;
@ -97,6 +99,7 @@ const runDebouncedForSearch = debounce((cb) => cb(), 200, false);
const MiddleSearch: FC<StateProps> = ({
isActive,
chat,
monoforumChat,
threadId,
requestedQuery,
savedTags,
@ -645,7 +648,7 @@ const MiddleSearch: FC<StateProps> = ({
{!isMobile && (
<Avatar
className={styles.avatar}
peer={chat}
peer={monoforumChat || chat}
size="medium"
isSavedMessages={isSavedMessages}
/>
@ -793,8 +796,11 @@ export default memo(withGlobal<OwnProps>(
const savedTags = isSavedMessages && !isSavedDialog ? global.savedReactionTags?.byKey : undefined;
const monoforumChat = selectMonoforumChannel(global, chatId);
return {
chat,
monoforumChat,
requestedQuery,
totalCount,
threadId,

View File

@ -77,7 +77,8 @@ const GifSearch: FC<OwnProps & StateProps> = ({
observe: observeIntersection,
} = useIntersectionObserver({ rootRef: containerRef, debounceMs: INTERSECTION_DEBOUNCE });
const canSendGifs = canPostInChat && getAllowedAttachmentOptions(chat, chatFullInfo, isChatWithBot).canSendGifs;
const canSendGifs = canPostInChat
&& getAllowedAttachmentOptions(chat, chatFullInfo, isChatWithBot, isSavedMessages).canSendGifs;
const handleGifClick = useCallback((gif: ApiVideo, isSilent?: boolean, shouldSchedule?: boolean) => {
if (canSendGifs) {

View File

@ -49,6 +49,7 @@ import {
selectCurrentSharedMediaSearch,
selectIsCurrentUserPremium,
selectIsRightColumnShown,
selectMonoforumChannel,
selectPeerStories,
selectSimilarBotsIds,
selectSimilarChannelIds,
@ -114,11 +115,12 @@ type OwnProps = {
threadId?: ThreadId;
profileState: ProfileState;
isMobile?: boolean;
onProfileStateChange: (state: ProfileState) => void;
isActive: boolean;
onProfileStateChange: (state: ProfileState) => void;
};
type StateProps = {
monoforumChannel?: ApiChat;
theme: ThemeKey;
isChannel?: boolean;
isBot?: boolean;
@ -183,6 +185,7 @@ const Profile: FC<OwnProps & StateProps> = ({
threadId,
profileState,
theme,
monoforumChannel,
isChannel,
isBot,
currentUserId,
@ -376,6 +379,9 @@ const Profile: FC<OwnProps & StateProps> = ({
const handleLoadGifts = useCallback(() => {
loadPeerSavedGifts({ peerId: chatId });
}, [chatId]);
const handleLoadMoreMembers = useCallback(() => {
loadMoreMembers({ chatId });
}, [chatId, loadMoreMembers]);
useEffectWithPrevDeps(([prevGifts]) => {
if (!gifts || !prevGifts) {
@ -397,7 +403,7 @@ const Profile: FC<OwnProps & StateProps> = ({
}, [gifts, startViewTransition]);
const [resultType, viewportIds, getMore, noProfileInfo] = useProfileViewportIds({
loadMoreMembers,
loadMoreMembers: handleLoadMoreMembers,
searchMessages: searchSharedMediaMessages,
loadStories: handleLoadPeerStories,
loadStoriesArchive: handleLoadStoriesArchive,
@ -872,7 +878,12 @@ const Profile: FC<OwnProps & StateProps> = ({
onScroll={handleScroll}
>
{!noProfileInfo && !isSavedMessages && (
renderProfileInfo(profileId, isRightColumnShown && canRenderContent, isSavedDialog)
renderProfileInfo(
monoforumChannel?.id || profileId,
isRightColumnShown && canRenderContent,
isSavedDialog,
Boolean(monoforumChannel),
)
)}
{!isRestricted && (
<div
@ -915,10 +926,10 @@ const Profile: FC<OwnProps & StateProps> = ({
);
};
function renderProfileInfo(profileId: string, isReady: boolean, isSavedDialog?: boolean) {
function renderProfileInfo(profileId: string, isReady: boolean, isSavedDialog?: boolean, isForMonoforum?: boolean) {
return (
<div className="profile-info">
<ProfileInfo peerId={profileId} canPlayVideo={isReady} />
<ProfileInfo peerId={profileId} canPlayVideo={isReady} isForMonoforum={isForMonoforum} />
<ChatExtra chatOrUserId={profileId} isSavedDialog={isSavedDialog} />
</div>
);
@ -949,7 +960,8 @@ export default memo(withGlobal<OwnProps>(
const isGroup = chat && isChatGroup(chat);
const isChannel = chat && isChatChannel(chat);
const isBot = user && isUserBot(user);
const hasMembersTab = !isTopicInfo && !isSavedDialog && (isGroup || (isChannel && isChatAdmin(chat)));
const hasMembersTab = !isTopicInfo && !isSavedDialog
&& (isGroup || (isChannel && isChatAdmin(chat))) && !chat?.isMonoforum;
const members = chatFullInfo?.members;
const adminMembersById = chatFullInfo?.adminMembersById;
const areMembersHidden = hasMembersTab && chat
@ -984,6 +996,8 @@ export default memo(withGlobal<OwnProps>(
const hasGiftsTab = Boolean(peerFullInfo?.starGiftCount) && !isSavedDialog;
const peerGifts = selectTabState(global).savedGifts.giftsByPeerId[chatId];
const monoforumChannel = selectMonoforumChannel(global, chatId);
return {
theme: selectTheme(global),
isChannel,
@ -1025,6 +1039,7 @@ export default memo(withGlobal<OwnProps>(
limitSimilarPeers: selectPremiumLimit(global, 'recommendedChannels'),
...(hasMembersTab && members && { members, adminMembersById }),
...(hasCommonChatsTab && user && { commonChatIds: commonChats?.ids }),
monoforumChannel,
};
},
)(Profile));

View File

@ -423,6 +423,7 @@ const RightColumn: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>(
(global, { isMobile }): StateProps => {
const { chatId, threadId } = selectCurrentMessageList(global) || {};
const areActiveChatsLoaded = selectAreActiveChatsLoaded(global);
const {
management, shouldSkipHistoryAnimations, nextProfileTab, shouldCloseRightColumn,

View File

@ -173,12 +173,12 @@ const RightHeader: FC<OwnProps & StateProps> = ({
isInsideTopic,
canEditTopic,
isSavedMessages,
onClose,
onScreenSelect,
canEditBot,
giftProfileFilter,
canUseGiftFilter,
canUseGiftAdminFilter,
onClose,
onScreenSelect,
}) => {
const {
setStickerSearchQuery,

View File

@ -1,7 +1,6 @@
import type { FC } from '../../../lib/teact/teact';
import {
memo, useCallback,
useMemo, useState,
memo, useMemo, useState,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
@ -10,6 +9,7 @@ import type { ApiChat, ApiChatMember } from '../../../api/types';
import { filterPeersByQuery } from '../../../global/helpers/peers';
import { selectChatFullInfo } from '../../../global/selectors';
import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang';
import ChatOrUserPicker from '../../common/pickers/ChatOrUserPicker';
@ -52,10 +52,14 @@ const RemoveGroupUserModal: FC<OwnProps & StateProps> = ({
return filterPeersByQuery({ ids: availableMemberIds, query: search, type: 'user' });
}, [chatMembers, currentUserId, search]);
const handleRemoveUser = useCallback((userId: string) => {
const handleLoadMore = useLastCallback(() => {
loadMoreMembers({ chatId: chat.id });
});
const handleRemoveUser = useLastCallback((userId: string) => {
deleteChatMember({ chatId: chat.id, userId });
onClose();
}, [chat.id, deleteChatMember, onClose]);
});
return (
<ChatOrUserPicker
@ -64,7 +68,7 @@ const RemoveGroupUserModal: FC<OwnProps & StateProps> = ({
searchPlaceholder={lang('ChannelBlockUser')}
search={search}
onSearchChange={setSearch}
loadMore={loadMoreMembers}
loadMore={handleLoadMore}
onSelectChatOrUser={handleRemoveUser}
onClose={onClose}
/>

View File

@ -343,7 +343,7 @@ export const REPLIES_USER_ID = '1271266957'; // TODO For Test connection ID must
export const VERIFICATION_CODES_USER_ID = '489000';
export const ANONYMOUS_USER_ID = '2666000';
export const RESTRICTED_EMOJI_SET_ID = '7173162320003080';
export const CHANNEL_ID_LENGTH = 14; // 14 symbols, based on TDLib's `ZERO_CHANNEL_ID = -1000000000000`
export const CHANNEL_ID_BASE = 10 ** 12;
export const DEFAULT_GIF_SEARCH_BOT_USERNAME = 'gif';
export const ALL_FOLDER_ID = 0;
export const ARCHIVED_FOLDER_ID = 1;

View File

@ -52,6 +52,7 @@ import {
isChatArchived,
isChatBasicGroup,
isChatChannel,
isChatMonoforum,
isChatSuperGroup,
isUserBot,
} from '../../helpers';
@ -2130,11 +2131,11 @@ addActionHandler('resetOpenChatWithDraft', (global, actions, payload): ActionRet
});
addActionHandler('loadMoreMembers', async (global, actions, payload): Promise<void> => {
const { chatId } = payload || {};
if (selectIsCurrentUserFrozen(global)) return;
const { tabId = getCurrentTabId() } = payload || {};
const { chatId } = selectCurrentMessageList(global, tabId) || {};
const chat = chatId ? selectChat(global, chatId) : undefined;
if (!chat || isChatBasicGroup(chat)) {
const chat = selectChat(global, chatId);
if (!chat || isChatBasicGroup(chat) || isChatMonoforum(chat)) {
return;
}

View File

@ -581,12 +581,17 @@ addActionHandler('saveDraft', (global, actions, payload): ActionReturnType => {
const {
chatId, threadId, text,
} = payload;
if (!text) {
const chat = selectChat(global, chatId);
if (!text || !chat) {
return;
}
const currentDraft = selectDraft(global, chatId, threadId);
if (chat.isMonoforum && !currentDraft?.replyInfo) {
return; // Monoforum doesn't support drafts outside threads
}
const newDraft: ApiDraft = {
text,
replyInfo: currentDraft?.replyInfo,
@ -654,8 +659,12 @@ addActionHandler('resetDraftReplyInfo', (global, actions, payload): ActionReturn
return;
}
const { chatId, threadId } = currentMessageList;
const chat = selectChat(global, chatId);
const currentDraft = selectDraft(global, chatId, threadId);
if (chat?.isMonoforum && !currentDraft?.replyInfo) {
return; // Monoforum doesn't support drafts outside threads
}
const newDraft: ApiDraft | undefined = !currentDraft?.text ? undefined : {
...currentDraft,
replyInfo: undefined,
@ -671,7 +680,11 @@ addActionHandler('saveEffectInDraft', (global, actions, payload): ActionReturnTy
chatId, threadId, effectId,
} = payload;
const chat = selectChat(global, chatId);
const currentDraft = selectDraft(global, chatId, threadId);
if (chat?.isMonoforum && !currentDraft?.replyInfo) {
return; // Monoforum doesn't support drafts outside threads
}
const newDraft = {
...currentDraft,
@ -986,7 +999,7 @@ addActionHandler('sendMessageAction', async (global, actions, payload): Promise<
if (selectIsChatWithSelf(global, chatId)) return;
const chat = selectChat(global, chatId)!;
if (!chat) return;
if (!chat || chat.isMonoforum) return;
const user = selectUser(global, chatId);
if (user && (isUserBot(user) || isDeletedUser(user))) return;
@ -2140,6 +2153,7 @@ addActionHandler('openChatOrTopicWithReplyInDraft', (global, actions, payload):
replyToMsgId: replyingInfo.messageId,
replyToTopId: replyingInfo.toThreadId,
replyToPeerId: currentChatId,
monoforumPeerId: replyingInfo.toThreadId,
quoteText: replyingInfo.quoteText,
quoteOffset: replyingInfo.quoteOffset,
} as ApiInputMessageReplyInfo;

View File

@ -30,6 +30,8 @@ import {
selectChatMessage,
selectCurrentChat,
selectCurrentMessageList,
selectIsChatWithBot,
selectIsChatWithSelf,
selectIsCurrentUserPremium,
selectIsTrustedBot,
selectPeerPaidMessagesStars,
@ -344,11 +346,13 @@ addActionHandler('showAllowedMessageTypesNotification', (global, actions, payloa
const chat = selectChat(global, chatId);
if (!chat) return;
const chatFullInfo = selectChatFullInfo(global, chatId);
const isSavedMessages = chatId ? selectIsChatWithSelf(global, chatId) : undefined;
const isChatWithBot = chatId ? selectIsChatWithBot(global, chat) : undefined;
const {
canSendPlainText, canSendPhotos, canSendVideos, canSendDocuments, canSendAudios,
canSendStickers, canSendRoundVideos, canSendVoices,
} = getAllowedAttachmentOptions(chat, chatFullInfo);
} = getAllowedAttachmentOptions(chat, chatFullInfo, isChatWithBot, isSavedMessages);
const allowedContent = compact([
canSendPlainText ? 'Chat.SendAllowedContentTypeText' : undefined,
canSendPhotos ? 'Chat.SendAllowedContentTypePhoto' : undefined,

View File

@ -47,6 +47,10 @@ export function isChatChannel(chat: ApiChat) {
return chat.type === 'chatTypeChannel';
}
export function isChatMonoforum(chat: ApiChat) {
return chat.isMonoforum;
}
export function isCommonBoxChat(chat: ApiChat) {
return chat.type === 'chatTypePrivate' || chat.type === 'chatTypeBasicGroup';
}
@ -152,7 +156,8 @@ export function getCanPostInChat(
}
if (chat.isRestricted || chat.isForbidden || chat.migratedTo
|| (!isMessageThread && chat.isNotJoined) || isSystemBot(chat.id) || isAnonymousForwardsChat(chat.id)) {
|| (chat.isNotJoined && !isChatMonoforum(chat) && !isMessageThread)
|| isSystemBot(chat.id) || isAnonymousForwardsChat(chat.id)) {
return false;
}
@ -190,6 +195,7 @@ export function getAllowedAttachmentOptions(
chat?: ApiChat,
chatFullInfo?: ApiChatFullInfo,
isChatWithBot = false,
isSavedMessages = false,
isStoryReply = false,
paidMessagesStars?: number,
isInScheduledList = false,
@ -215,9 +221,9 @@ export function getAllowedAttachmentOptions(
return {
canAttachMedia: isAdmin || isStoryReply || !isUserRightBanned(chat, 'sendMedia', chatFullInfo),
canAttachPolls: !isStoryReply
canAttachPolls: !isStoryReply && !chat.isMonoforum
&& (isAdmin || !isUserRightBanned(chat, 'sendPolls', chatFullInfo))
&& (!isUserId(chat.id) || isChatWithBot),
&& (!isUserId(chat.id) || isChatWithBot || isSavedMessages),
canSendStickers: isAdmin || isStoryReply || !isUserRightBanned(chat, 'sendStickers', chatFullInfo),
canSendGifs: isAdmin || isStoryReply || !isUserRightBanned(chat, 'sendGifs', chatFullInfo),
canAttachEmbedLinks: !isStoryReply && (isAdmin || !isUserRightBanned(chat, 'embedLinks', chatFullInfo)),

View File

@ -349,3 +349,28 @@ export function selectChatLastMessage<T extends GlobalState>(
const realChatId = listType === 'saved' ? global.currentUserId! : chatId;
return global.messages.byChatId[realChatId]?.byId[id];
}
export function selectIsMonoforumAdmin<T extends GlobalState>(
global: T, chatId: string,
) {
const chat = selectChat(global, chatId);
if (!chat?.isMonoforum) return;
const channel = selectMonoforumChannel(global, chatId);
if (!channel) return;
return Boolean(chat.isCreator || chat.adminRights || channel.isCreator || channel.adminRights);
}
/**
* Only selects monoforum channel for monoforum chats.
* Returns `undefined` for other chats, including channels that have linked monoforum.
*/
export function selectMonoforumChannel<T extends GlobalState>(
global: T, chatId: string,
) {
const chat = selectChat(global, chatId);
if (!chat) return;
return chat.isMonoforum ? selectChat(global, chat.linkedMonoforumId!) : undefined;
}

View File

@ -72,7 +72,7 @@ export function selectCanManage<T extends GlobalState>(
chatId: string,
) {
const chat = selectChat(global, chatId);
if (!chat || chat.isRestricted) return false;
if (!chat || chat.isRestricted || chat.isMonoforum) return false;
const isPrivate = isUserId(chat.id);
const user = isPrivate ? selectUser(global, chatId) : undefined;

View File

@ -72,6 +72,7 @@ import {
selectChat,
selectChatFullInfo,
selectChatLastMessageId,
selectIsChatWithBot,
selectIsChatWithSelf,
selectRequestedChatTranslationLanguage,
} from './chats';
@ -651,6 +652,7 @@ export function selectAllowedMessageActionsSlow<T extends GlobalState>(
const { content } = message;
const isDocumentSticker = isMessageDocumentSticker(message);
const isBoostMessage = message.content.action?.type === 'boostApply';
const isMonoforum = chat.isMonoforum;
const hasChatPinPermission = (chat.isCreator
|| (!isChannel && !isUserRightBanned(chat, 'pinMessages'))
@ -726,7 +728,7 @@ export function selectAllowedMessageActionsSlow<T extends GlobalState>(
const canFaveSticker = !isAction && hasSticker && !hasFavoriteSticker;
const canUnfaveSticker = !isAction && hasFavoriteSticker;
const canCopy = !isAction;
const canCopyLink = !isLocal && !isAction && (isChannel || isSuperGroup);
const canCopyLink = !isLocal && !isAction && (isChannel || isSuperGroup) && !isMonoforum;
const canSelect = !isLocal && !isAction;
const canDownload = Boolean(content.webPage?.document || content.webPage?.video || content.webPage?.photo
@ -1462,7 +1464,10 @@ export function selectForwardsCanBeSentToChat<T extends GlobalState>(
const chatFullInfo = selectChatFullInfo(global, toChatId);
const chatMessages = selectChatMessages(global, fromChatId!);
const options = getAllowedAttachmentOptions(chat, chatFullInfo);
const isSavedMessages = toChatId ? selectIsChatWithSelf(global, toChatId) : undefined;
const isChatWithBot = toChatId ? selectIsChatWithBot(global, chat) : undefined;
const options = getAllowedAttachmentOptions(chat, chatFullInfo, isChatWithBot, isSavedMessages);
return !messageIds!.some((messageId) => сheckMessageSendingDenied(chatMessages[messageId], options));
}
function сheckMessageSendingDenied(message: ApiMessage, options: IAllowedAttachmentOptions) {

View File

@ -354,7 +354,9 @@ export interface ActionPayloads {
} & WithTabId;
openThreadWithInfo: ActionPayloads['openThread'] & WithTabId;
openLinkedChat: { id: string } & WithTabId;
loadMoreMembers: WithTabId | undefined;
loadMoreMembers: {
chatId: string;
};
setActiveChatFolder: {
activeChatFolder: number;
} & WithTabId;

View File

@ -12,5 +12,5 @@ for (const tl of Object.values(Api)) {
}
}
export const LAYER = 203;
export const LAYER = 204;
export { tlobjects };

File diff suppressed because one or more lines are too long

View File

@ -80,7 +80,7 @@ userStatusLastMonth#65899777 flags:# by_me:flags.0?true = UserStatus;
chatEmpty#29562865 id:long = Chat;
chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat;
chatForbidden#6592a1a7 id:long title:string = Chat;
channel#7482147e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?true signature_profiles:flags2.12?true autotranslation:flags2.15?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector<Username> stories_max_id:flags2.4?int color:flags2.7?PeerColor profile_color:flags2.8?PeerColor emoji_status:flags2.9?EmojiStatus level:flags2.10?int subscription_until_date:flags2.11?int bot_verification_icon:flags2.13?long send_paid_messages_stars:flags2.14?long = Chat;
channel#fe685355 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?true signature_profiles:flags2.12?true autotranslation:flags2.15?true broadcast_messages_allowed:flags2.16?true monoforum:flags2.17?true forum_tabs:flags2.19?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector<Username> stories_max_id:flags2.4?int color:flags2.7?PeerColor profile_color:flags2.8?PeerColor emoji_status:flags2.9?EmojiStatus level:flags2.10?int subscription_until_date:flags2.11?int bot_verification_icon:flags2.13?long send_paid_messages_stars:flags2.14?long linked_monoforum_id:flags2.18?long = Chat;
channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat;
chatFull#2633421b flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector<long> available_reactions:flags.18?ChatReactions reactions_limit:flags.20?int = ChatFull;
channelFull#52d6806b flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true paid_reactions_available:flags2.16?true stargifts_available:flags2.19?true paid_messages_available:flags2.20?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet bot_verification:flags2.17?BotVerification stargifts_count:flags2.18?int = ChatFull;
@ -160,7 +160,7 @@ messageActionPrizeStars#b00c47a2 flags:# unclaimed:flags.0?true stars:long trans
messageActionStarGift#4717e8a4 flags:# name_hidden:flags.0?true saved:flags.2?true converted:flags.3?true upgraded:flags.5?true refunded:flags.9?true can_upgrade:flags.10?true gift:StarGift message:flags.1?TextWithEntities convert_stars:flags.4?long upgrade_msg_id:flags.5?int upgrade_stars:flags.8?long from_id:flags.11?Peer peer:flags.12?Peer saved_id:flags.12?long = MessageAction;
messageActionStarGiftUnique#2e3ae60e flags:# upgrade:flags.0?true transferred:flags.1?true saved:flags.2?true refunded:flags.5?true gift:StarGift can_export_at:flags.3?int transfer_stars:flags.4?long from_id:flags.6?Peer peer:flags.7?Peer saved_id:flags.7?long resale_stars:flags.8?long can_transfer_at:flags.9?int can_resell_at:flags.10?int = MessageAction;
messageActionPaidMessagesRefunded#ac1f1fcd count:int stars:long = MessageAction;
messageActionPaidMessagesPrice#bcd71419 stars:long = MessageAction;
messageActionPaidMessagesPrice#84b88578 flags:# broadcast_messages_allowed:flags.0?true stars:long = MessageAction;
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;
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;
@ -279,7 +279,7 @@ updateBotCallbackQuery#b9cfc48d flags:# query_id:long user_id:long peer:Peer msg
updateEditMessage#e40370a3 message:Message pts:int pts_count:int = Update;
updateInlineBotCallbackQuery#691e9052 flags:# query_id:long user_id:long msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update;
updateReadChannelOutbox#b75f99a9 channel_id:long max_id:int = Update;
updateDraftMessage#1b49ec6d flags:# peer:Peer top_msg_id:flags.0?int draft:DraftMessage = Update;
updateDraftMessage#edfc111e flags:# peer:Peer top_msg_id:flags.0?int saved_peer_id:flags.1?Peer draft:DraftMessage = Update;
updateReadFeaturedStickers#571d2742 = Update;
updateRecentStickers#9a422c20 = Update;
updateConfig#a229dd06 = Update;
@ -295,10 +295,10 @@ updatePhoneCall#ab0f6b1e phone_call:PhoneCall = Update;
updateLangPackTooLong#46560264 lang_code:string = Update;
updateLangPack#56022f4d difference:LangPackDifference = Update;
updateFavedStickers#e511996d = Update;
updateChannelReadMessagesContents#ea29055d flags:# channel_id:long top_msg_id:flags.0?int messages:Vector<int> = Update;
updateChannelReadMessagesContents#25f324f7 flags:# channel_id:long top_msg_id:flags.0?int saved_peer_id:flags.1?Peer messages:Vector<int> = Update;
updateContactsReset#7084a7be = Update;
updateChannelAvailableMessages#b23fc698 channel_id:long available_min_id:int = Update;
updateDialogUnreadMark#e16459c3 flags:# unread:flags.0?true peer:DialogPeer = Update;
updateDialogUnreadMark#b658f23e flags:# unread:flags.0?true peer:DialogPeer saved_peer_id:flags.1?Peer = Update;
updateMessagePoll#aca1657b flags:# poll_id:long poll:flags.0?Poll results:PollResults = Update;
updateChatDefaultBannedRights#54c01850 peer:Peer default_banned_rights:ChatBannedRights version:int = Update;
updateFolderPeers#19360dc0 folder_peers:Vector<FolderPeer> pts:int pts_count:int = Update;
@ -332,7 +332,7 @@ updateGroupCallConnection#b783982 flags:# presentation:flags.0?true params:DataJ
updateBotCommands#4d712f2e peer:Peer bot_id:long commands:Vector<BotCommand> = Update;
updatePendingJoinRequests#7063c3db peer:Peer requests_pending:int recent_requesters:Vector<long> = Update;
updateBotChatInviteRequester#11dfa986 peer:Peer date:int user_id:long about:string invite:ExportedChatInvite qts:int = Update;
updateMessageReactions#5e1b3cb8 flags:# peer:Peer msg_id:int top_msg_id:flags.0?int reactions:MessageReactions = Update;
updateMessageReactions#1e297bfa flags:# peer:Peer msg_id:int top_msg_id:flags.0?int saved_peer_id:flags.1?Peer reactions:MessageReactions = Update;
updateAttachMenuBots#17b7a20b = Update;
updateWebViewResultSent#1592b79d query_id:long = Update;
updateBotMenuButton#14b85813 bot_id:long button:BotMenuButton = Update;
@ -380,6 +380,8 @@ updateBotPurchasedPaidMedia#283bd312 user_id:long payload:string qts:int = Updat
updatePaidReactionPrivacy#8b725fce private:PaidReactionPrivacy = Update;
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;
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;
@ -1234,8 +1236,9 @@ storyViewPublicForward#9083670b flags:# blocked:flags.0?true blocked_my_stories_
storyViewPublicRepost#bd74cf49 flags:# blocked:flags.0?true blocked_my_stories_from:flags.1?true peer_id:Peer story:StoryItem = StoryView;
stories.storyViewsList#59d78fc5 flags:# count:int views_count:int forwards_count:int reactions_count:int views:Vector<StoryView> chats:Vector<Chat> users:Vector<User> next_offset:flags.0?string = stories.StoryViewsList;
stories.storyViews#de9eed1d views:Vector<StoryViews> users:Vector<User> = stories.StoryViews;
inputReplyToMessage#22c0f6d5 flags:# reply_to_msg_id:int top_msg_id:flags.0?int reply_to_peer_id:flags.1?InputPeer quote_text:flags.2?string quote_entities:flags.3?Vector<MessageEntity> quote_offset:flags.4?int = InputReplyTo;
inputReplyToMessage#b07038b0 flags:# reply_to_msg_id:int top_msg_id:flags.0?int reply_to_peer_id:flags.1?InputPeer quote_text:flags.2?string quote_entities:flags.3?Vector<MessageEntity> quote_offset:flags.4?int monoforum_peer_id:flags.5?InputPeer = InputReplyTo;
inputReplyToStory#5881323a peer:InputPeer story_id:int = InputReplyTo;
inputReplyToMonoForum#69d66c45 monoforum_peer_id:InputPeer = InputReplyTo;
exportedStoryLink#3fc9053b link:string = ExportedStoryLink;
storiesStealthMode#712e27fd flags:# active_until_date:flags.0?int cooldown_until_date:flags.1?int = StoriesStealthMode;
mediaAreaCoordinates#cfc9e002 flags:# x:double y:double w:double h:double rotation:double radius:flags.0?double = MediaAreaCoordinates;
@ -1280,6 +1283,7 @@ storyReactionPublicForward#bbab2643 message:Message = StoryReaction;
storyReactionPublicRepost#cfcd0f13 peer_id:Peer story:StoryItem = StoryReaction;
stories.storyReactionsList#aa5f789c flags:# count:int reactions:Vector<StoryReaction> chats:Vector<Chat> users:Vector<User> next_offset:flags.0?string = stories.StoryReactionsList;
savedDialog#bd87cb6c flags:# pinned:flags.2?true peer:Peer top_message:int = SavedDialog;
monoForumDialog#64407ea7 flags:# unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_reactions_count:int draft:flags.1?DraftMessage = SavedDialog;
messages.savedDialogs#f83ae221 dialogs:Vector<SavedDialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.SavedDialogs;
messages.savedDialogsSlice#44ba9dd9 count:int dialogs:Vector<SavedDialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.SavedDialogs;
messages.savedDialogsNotModified#c01f6fe8 count:int = messages.SavedDialogs;
@ -1527,7 +1531,7 @@ 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#bb9fa475 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 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.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.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;
@ -1578,7 +1582,7 @@ messages.getUnreadMentions#f107e790 flags:# peer:InputPeer top_msg_id:flags.0?in
messages.readMentions#36e5bf4d flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory;
messages.sendMultiMedia#1bf89d74 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 multi_media:Vector<InputSingleMedia> 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.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets;
messages.markDialogUnread#c286d98f flags:# unread:flags.0?true peer:InputDialogPeer = Bool;
messages.markDialogUnread#8c5006f8 flags:# unread:flags.0?true parent_peer:flags.1?InputPeer peer:InputDialogPeer = Bool;
messages.updatePinnedMessage#d2aaf7ec flags:# silent:flags.0?true unpin:flags.1?true pm_oneside:flags.2?true peer:InputPeer id:int = Updates;
messages.sendVote#10ea6184 peer:InputPeer msg_id:int options:Vector<bytes> = Updates;
messages.getOnlines#6e2be050 peer:InputPeer = ChatOnlines;
@ -1599,7 +1603,7 @@ messages.updateDialogFiltersOrder#c563c1e4 order:Vector<int> = Bool;
messages.getReplies#22ddd30c peer:InputPeer msg_id:int offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.DiscussionMessage;
messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Bool;
messages.unpinAllMessages#ee22b9a8 flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory;
messages.unpinAllMessages#62dd747 flags:# peer:InputPeer top_msg_id:flags.0?int saved_peer_id:flags.1?InputPeer = messages.AffectedHistory;
messages.deleteChat#5bd0ee50 chat_id:long = Bool;
messages.getExportedChatInvites#a2b5a3f6 flags:# revoked:flags.3?true peer:InputPeer admin_id:InputUser offset_date:flags.2?int offset_link:flags.2?string limit:int = messages.ExportedChatInvites;
messages.editExportedChatInvite#bdca2f75 flags:# revoked:flags.2?true peer:InputPeer link:string expire_date:flags.0?int usage_limit:flags.1?int request_needed:flags.3?Bool title:flags.4?string = messages.ExportedChatInvite;
@ -1618,8 +1622,8 @@ messages.setChatAvailableReactions#864b2581 flags:# peer:InputPeer available_rea
messages.getAvailableReactions#18dea0ac hash:int = messages.AvailableReactions;
messages.setDefaultReaction#4f47a016 reaction:Reaction = Bool;
messages.translateText#63183030 flags:# peer:flags.0?InputPeer id:flags.0?Vector<int> text:flags.1?Vector<TextWithEntities> to_lang:string = messages.TranslatedText;
messages.getUnreadReactions#3223495b flags:# peer:InputPeer top_msg_id:flags.0?int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
messages.readReactions#54aa7f8e flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory;
messages.getUnreadReactions#bd7f90ac flags:# peer:InputPeer top_msg_id:flags.0?int saved_peer_id:flags.1?InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
messages.readReactions#9ec44f93 flags:# peer:InputPeer top_msg_id:flags.0?int saved_peer_id:flags.1?InputPeer = messages.AffectedHistory;
messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots;
messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot;
messages.toggleBotInAttachMenu#69f59d69 flags:# write_allowed:flags.0?true bot:InputUser enabled:Bool = Bool;
@ -1639,9 +1643,9 @@ messages.getExtendedMedia#84f80814 peer:InputPeer id:Vector<int> = Updates;
messages.togglePeerTranslations#e47cb579 flags:# disabled:flags.0?true peer:InputPeer = Bool;
messages.getBotApp#34fdc5c3 app:InputBotApp hash:long = messages.BotApp;
messages.requestAppWebView#53618bce flags:# write_allowed:flags.0?true compact:flags.7?true fullscreen:flags.8?true peer:InputPeer app:InputBotApp start_param:flags.1?string theme_params:flags.2?DataJSON platform:string = WebViewResult;
messages.getSavedDialogs#5381d21a flags:# exclude_pinned:flags.0?true offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.SavedDialogs;
messages.getSavedHistory#3d9a414d peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
messages.deleteSavedHistory#6e98102b flags:# peer:InputPeer max_id:int min_date:flags.2?int max_date:flags.3?int = messages.AffectedHistory;
messages.getSavedDialogs#1e91fc99 flags:# exclude_pinned:flags.0?true parent_peer:flags.1?InputPeer offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.SavedDialogs;
messages.getSavedHistory#998ab009 flags:# parent_peer:flags.0?InputPeer peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
messages.deleteSavedHistory#4dc5085f flags:# parent_peer:flags.0?InputPeer peer:InputPeer max_id:int min_date:flags.2?int max_date:flags.3?int = messages.AffectedHistory;
messages.getPinnedSavedDialogs#d63d94e0 = messages.SavedDialogs;
messages.toggleSavedDialogPin#ac81bbde flags:# pinned:flags.0?true peer:InputDialogPeer = Bool;
messages.getSavedReactionTags#3637e05b flags:# peer:flags.0?InputPeer hash:long = messages.SavedReactionTags;
@ -1715,7 +1719,7 @@ channels.toggleJoinRequest#4c2985b6 channel:InputChannel enabled:Bool = Updates;
channels.reorderUsernames#b45ced1d channel:InputChannel order:Vector<string> = Bool;
channels.toggleUsername#50f24105 channel:InputChannel username:string active:Bool = Bool;
channels.deactivateAllUsernames#a245dd3 channel:InputChannel = Bool;
channels.toggleForum#a4298b29 channel:InputChannel enabled:Bool = Updates;
channels.toggleForum#3ff75734 channel:InputChannel enabled:Bool tabs:Bool = Updates;
channels.createForumTopic#f40c0224 flags:# channel:InputChannel title:string icon_color:flags.0?int icon_emoji_id:flags.3?long random_id:long send_as:flags.2?InputPeer = Updates;
channels.getForumTopics#de560d1 flags:# channel:InputChannel q:flags.0?string offset_date:int offset_id:int offset_topic:int limit:int = messages.ForumTopics;
channels.getForumTopicsByID#b0831eb9 channel:InputChannel topics:Vector<int> = messages.ForumTopics;
@ -1726,7 +1730,7 @@ channels.toggleParticipantsHidden#6a6e7854 channel:InputChannel enabled:Bool = U
channels.toggleViewForumAsMessages#9738bb15 channel:InputChannel enabled:Bool = Updates;
channels.getChannelRecommendations#25a71742 flags:# channel:flags.0?InputChannel = messages.Chats;
channels.searchPosts#d19f987b hashtag:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
channels.updatePaidMessagesPrice#fc84653f channel:InputChannel send_paid_messages_stars:long = Updates;
channels.updatePaidMessagesPrice#4b12327b flags:# broadcast_messages_allowed:flags.0?true channel:InputChannel send_paid_messages_stars:long = Updates;
channels.toggleAutotranslation#167fc0a1 channel:InputChannel enabled:Bool = Updates;
bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:flags.3?string about:flags.0?string description:flags.1?string = Bool;
bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
@ -1835,4 +1839,4 @@ premium.getBoostsList#60f67660 flags:# gifts:flags.0?true peer:InputPeer offset:
premium.getMyBoosts#be77b4a = premium.MyBoosts;
premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector<int> peer:InputPeer = premium.MyBoosts;
premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus;
fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;`;
fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;`;

View File

@ -99,7 +99,7 @@ userStatusLastMonth#65899777 flags:# by_me:flags.0?true = UserStatus;
chatEmpty#29562865 id:long = Chat;
chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat;
chatForbidden#6592a1a7 id:long title:string = Chat;
channel#7482147e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?true signature_profiles:flags2.12?true autotranslation:flags2.15?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector<Username> stories_max_id:flags2.4?int color:flags2.7?PeerColor profile_color:flags2.8?PeerColor emoji_status:flags2.9?EmojiStatus level:flags2.10?int subscription_until_date:flags2.11?int bot_verification_icon:flags2.13?long send_paid_messages_stars:flags2.14?long = Chat;
channel#fe685355 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?true signature_profiles:flags2.12?true autotranslation:flags2.15?true broadcast_messages_allowed:flags2.16?true monoforum:flags2.17?true forum_tabs:flags2.19?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector<Username> stories_max_id:flags2.4?int color:flags2.7?PeerColor profile_color:flags2.8?PeerColor emoji_status:flags2.9?EmojiStatus level:flags2.10?int subscription_until_date:flags2.11?int bot_verification_icon:flags2.13?long send_paid_messages_stars:flags2.14?long linked_monoforum_id:flags2.18?long = Chat;
channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat;
chatFull#2633421b flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector<long> available_reactions:flags.18?ChatReactions reactions_limit:flags.20?int = ChatFull;
@ -186,7 +186,7 @@ messageActionPrizeStars#b00c47a2 flags:# unclaimed:flags.0?true stars:long trans
messageActionStarGift#4717e8a4 flags:# name_hidden:flags.0?true saved:flags.2?true converted:flags.3?true upgraded:flags.5?true refunded:flags.9?true can_upgrade:flags.10?true gift:StarGift message:flags.1?TextWithEntities convert_stars:flags.4?long upgrade_msg_id:flags.5?int upgrade_stars:flags.8?long from_id:flags.11?Peer peer:flags.12?Peer saved_id:flags.12?long = MessageAction;
messageActionStarGiftUnique#2e3ae60e flags:# upgrade:flags.0?true transferred:flags.1?true saved:flags.2?true refunded:flags.5?true gift:StarGift can_export_at:flags.3?int transfer_stars:flags.4?long from_id:flags.6?Peer peer:flags.7?Peer saved_id:flags.7?long resale_stars:flags.8?long can_transfer_at:flags.9?int can_resell_at:flags.10?int = MessageAction;
messageActionPaidMessagesRefunded#ac1f1fcd count:int stars:long = MessageAction;
messageActionPaidMessagesPrice#bcd71419 stars:long = MessageAction;
messageActionPaidMessagesPrice#84b88578 flags:# broadcast_messages_allowed:flags.0?true stars:long = MessageAction;
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;
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;
@ -332,7 +332,7 @@ updateBotCallbackQuery#b9cfc48d flags:# query_id:long user_id:long peer:Peer msg
updateEditMessage#e40370a3 message:Message pts:int pts_count:int = Update;
updateInlineBotCallbackQuery#691e9052 flags:# query_id:long user_id:long msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update;
updateReadChannelOutbox#b75f99a9 channel_id:long max_id:int = Update;
updateDraftMessage#1b49ec6d flags:# peer:Peer top_msg_id:flags.0?int draft:DraftMessage = Update;
updateDraftMessage#edfc111e flags:# peer:Peer top_msg_id:flags.0?int saved_peer_id:flags.1?Peer draft:DraftMessage = Update;
updateReadFeaturedStickers#571d2742 = Update;
updateRecentStickers#9a422c20 = Update;
updateConfig#a229dd06 = Update;
@ -348,10 +348,10 @@ updatePhoneCall#ab0f6b1e phone_call:PhoneCall = Update;
updateLangPackTooLong#46560264 lang_code:string = Update;
updateLangPack#56022f4d difference:LangPackDifference = Update;
updateFavedStickers#e511996d = Update;
updateChannelReadMessagesContents#ea29055d flags:# channel_id:long top_msg_id:flags.0?int messages:Vector<int> = Update;
updateChannelReadMessagesContents#25f324f7 flags:# channel_id:long top_msg_id:flags.0?int saved_peer_id:flags.1?Peer messages:Vector<int> = Update;
updateContactsReset#7084a7be = Update;
updateChannelAvailableMessages#b23fc698 channel_id:long available_min_id:int = Update;
updateDialogUnreadMark#e16459c3 flags:# unread:flags.0?true peer:DialogPeer = Update;
updateDialogUnreadMark#b658f23e flags:# unread:flags.0?true peer:DialogPeer saved_peer_id:flags.1?Peer = Update;
updateMessagePoll#aca1657b flags:# poll_id:long poll:flags.0?Poll results:PollResults = Update;
updateChatDefaultBannedRights#54c01850 peer:Peer default_banned_rights:ChatBannedRights version:int = Update;
updateFolderPeers#19360dc0 folder_peers:Vector<FolderPeer> pts:int pts_count:int = Update;
@ -385,7 +385,7 @@ updateGroupCallConnection#b783982 flags:# presentation:flags.0?true params:DataJ
updateBotCommands#4d712f2e peer:Peer bot_id:long commands:Vector<BotCommand> = Update;
updatePendingJoinRequests#7063c3db peer:Peer requests_pending:int recent_requesters:Vector<long> = Update;
updateBotChatInviteRequester#11dfa986 peer:Peer date:int user_id:long about:string invite:ExportedChatInvite qts:int = Update;
updateMessageReactions#5e1b3cb8 flags:# peer:Peer msg_id:int top_msg_id:flags.0?int reactions:MessageReactions = Update;
updateMessageReactions#1e297bfa flags:# peer:Peer msg_id:int top_msg_id:flags.0?int saved_peer_id:flags.1?Peer reactions:MessageReactions = Update;
updateAttachMenuBots#17b7a20b = Update;
updateWebViewResultSent#1592b79d query_id:long = Update;
updateBotMenuButton#14b85813 bot_id:long button:BotMenuButton = Update;
@ -433,6 +433,8 @@ updateBotPurchasedPaidMedia#283bd312 user_id:long payload:string qts:int = Updat
updatePaidReactionPrivacy#8b725fce private:PaidReactionPrivacy = Update;
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;
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
@ -1638,8 +1640,9 @@ stories.storyViewsList#59d78fc5 flags:# count:int views_count:int forwards_count
stories.storyViews#de9eed1d views:Vector<StoryViews> users:Vector<User> = stories.StoryViews;
inputReplyToMessage#22c0f6d5 flags:# reply_to_msg_id:int top_msg_id:flags.0?int reply_to_peer_id:flags.1?InputPeer quote_text:flags.2?string quote_entities:flags.3?Vector<MessageEntity> quote_offset:flags.4?int = InputReplyTo;
inputReplyToMessage#b07038b0 flags:# reply_to_msg_id:int top_msg_id:flags.0?int reply_to_peer_id:flags.1?InputPeer quote_text:flags.2?string quote_entities:flags.3?Vector<MessageEntity> quote_offset:flags.4?int monoforum_peer_id:flags.5?InputPeer = InputReplyTo;
inputReplyToStory#5881323a peer:InputPeer story_id:int = InputReplyTo;
inputReplyToMonoForum#69d66c45 monoforum_peer_id:InputPeer = InputReplyTo;
exportedStoryLink#3fc9053b link:string = ExportedStoryLink;
@ -1712,6 +1715,7 @@ storyReactionPublicRepost#cfcd0f13 peer_id:Peer story:StoryItem = StoryReaction;
stories.storyReactionsList#aa5f789c flags:# count:int reactions:Vector<StoryReaction> chats:Vector<Chat> users:Vector<User> next_offset:flags.0?string = stories.StoryReactionsList;
savedDialog#bd87cb6c flags:# pinned:flags.2?true peer:Peer top_message:int = SavedDialog;
monoForumDialog#64407ea7 flags:# unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_reactions_count:int draft:flags.1?DraftMessage = SavedDialog;
messages.savedDialogs#f83ae221 dialogs:Vector<SavedDialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.SavedDialogs;
messages.savedDialogsSlice#44ba9dd9 count:int dialogs:Vector<SavedDialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.SavedDialogs;
@ -2177,7 +2181,7 @@ 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#bb9fa475 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 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.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.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;
@ -2259,8 +2263,8 @@ messages.sendMultiMedia#1bf89d74 flags:# silent:flags.5?true background:flags.6?
messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile;
messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets;
messages.getSplitRanges#1cff7e08 = Vector<MessageRange>;
messages.markDialogUnread#c286d98f flags:# unread:flags.0?true peer:InputDialogPeer = Bool;
messages.getDialogUnreadMarks#22e24e22 = Vector<DialogPeer>;
messages.markDialogUnread#8c5006f8 flags:# unread:flags.0?true parent_peer:flags.1?InputPeer peer:InputDialogPeer = Bool;
messages.getDialogUnreadMarks#21202222 flags:# parent_peer:flags.0?InputPeer = Vector<DialogPeer>;
messages.clearAllDrafts#7e58ee9c = Bool;
messages.updatePinnedMessage#d2aaf7ec flags:# silent:flags.0?true unpin:flags.1?true pm_oneside:flags.2?true peer:InputPeer id:int = Updates;
messages.sendVote#10ea6184 peer:InputPeer msg_id:int options:Vector<bytes> = Updates;
@ -2290,7 +2294,7 @@ messages.getOldFeaturedStickers#7ed094a1 offset:int limit:int hash:long = messag
messages.getReplies#22ddd30c peer:InputPeer msg_id:int offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.DiscussionMessage;
messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Bool;
messages.unpinAllMessages#ee22b9a8 flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory;
messages.unpinAllMessages#62dd747 flags:# peer:InputPeer top_msg_id:flags.0?int saved_peer_id:flags.1?InputPeer = messages.AffectedHistory;
messages.deleteChat#5bd0ee50 chat_id:long = Bool;
messages.deletePhoneCallHistory#f9cbe409 flags:# revoke:flags.0?true = messages.AffectedFoundMessages;
messages.checkHistoryImport#43fe19f3 import_head:string = messages.HistoryImportParsed;
@ -2321,8 +2325,8 @@ messages.setChatAvailableReactions#864b2581 flags:# peer:InputPeer available_rea
messages.getAvailableReactions#18dea0ac hash:int = messages.AvailableReactions;
messages.setDefaultReaction#4f47a016 reaction:Reaction = Bool;
messages.translateText#63183030 flags:# peer:flags.0?InputPeer id:flags.0?Vector<int> text:flags.1?Vector<TextWithEntities> to_lang:string = messages.TranslatedText;
messages.getUnreadReactions#3223495b flags:# peer:InputPeer top_msg_id:flags.0?int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
messages.readReactions#54aa7f8e flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory;
messages.getUnreadReactions#bd7f90ac flags:# peer:InputPeer top_msg_id:flags.0?int saved_peer_id:flags.1?InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
messages.readReactions#9ec44f93 flags:# peer:InputPeer top_msg_id:flags.0?int saved_peer_id:flags.1?InputPeer = messages.AffectedHistory;
messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = messages.Messages;
messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots;
messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot;
@ -2354,9 +2358,9 @@ messages.getBotApp#34fdc5c3 app:InputBotApp hash:long = messages.BotApp;
messages.requestAppWebView#53618bce flags:# write_allowed:flags.0?true compact:flags.7?true fullscreen:flags.8?true peer:InputPeer app:InputBotApp start_param:flags.1?string theme_params:flags.2?DataJSON platform:string = WebViewResult;
messages.setChatWallPaper#8ffacae1 flags:# for_both:flags.3?true revert:flags.4?true peer:InputPeer wallpaper:flags.0?InputWallPaper settings:flags.2?WallPaperSettings id:flags.1?int = Updates;
messages.searchEmojiStickerSets#92b4494c flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets;
messages.getSavedDialogs#5381d21a flags:# exclude_pinned:flags.0?true offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.SavedDialogs;
messages.getSavedHistory#3d9a414d peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
messages.deleteSavedHistory#6e98102b flags:# peer:InputPeer max_id:int min_date:flags.2?int max_date:flags.3?int = messages.AffectedHistory;
messages.getSavedDialogs#1e91fc99 flags:# exclude_pinned:flags.0?true parent_peer:flags.1?InputPeer offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.SavedDialogs;
messages.getSavedHistory#998ab009 flags:# parent_peer:flags.0?InputPeer peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
messages.deleteSavedHistory#4dc5085f flags:# parent_peer:flags.0?InputPeer peer:InputPeer max_id:int min_date:flags.2?int max_date:flags.3?int = messages.AffectedHistory;
messages.getPinnedSavedDialogs#d63d94e0 = messages.SavedDialogs;
messages.toggleSavedDialogPin#ac81bbde flags:# pinned:flags.0?true peer:InputDialogPeer = Bool;
messages.reorderPinnedSavedDialogs#8b716587 flags:# force:flags.0?true order:Vector<InputDialogPeer> = Bool;
@ -2391,6 +2395,8 @@ messages.savePreparedInlineMessage#f21f7f2f flags:# result:InputBotInlineResult
messages.getPreparedInlineMessage#857ebdb8 bot:InputUser id:string = messages.PreparedInlineMessage;
messages.searchStickers#29b1c66a flags:# emojis:flags.0?true q:string emoticon:string lang_code:Vector<string> offset:int limit:int hash:long = messages.FoundStickers;
messages.reportMessagesDelivery#5a6d7395 flags:# push:flags.0?true peer:InputPeer id:Vector<int> = Bool;
messages.getSavedDialogsByID#6f6f9c96 flags:# parent_peer:flags.1?InputPeer ids:Vector<InputPeer> = messages.SavedDialogs;
messages.readSavedHistory#ba4a3b5b parent_peer:InputPeer peer:InputPeer max_id:int = Bool;
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;
@ -2479,7 +2485,7 @@ channels.toggleJoinRequest#4c2985b6 channel:InputChannel enabled:Bool = Updates;
channels.reorderUsernames#b45ced1d channel:InputChannel order:Vector<string> = Bool;
channels.toggleUsername#50f24105 channel:InputChannel username:string active:Bool = Bool;
channels.deactivateAllUsernames#a245dd3 channel:InputChannel = Bool;
channels.toggleForum#a4298b29 channel:InputChannel enabled:Bool = Updates;
channels.toggleForum#3ff75734 channel:InputChannel enabled:Bool tabs:Bool = Updates;
channels.createForumTopic#f40c0224 flags:# channel:InputChannel title:string icon_color:flags.0?int icon_emoji_id:flags.3?long random_id:long send_as:flags.2?InputPeer = Updates;
channels.getForumTopics#de560d1 flags:# channel:InputChannel q:flags.0?string offset_date:int offset_id:int offset_topic:int limit:int = messages.ForumTopics;
channels.getForumTopicsByID#b0831eb9 channel:InputChannel topics:Vector<int> = messages.ForumTopics;
@ -2498,8 +2504,9 @@ channels.setBoostsToUnblockRestrictions#ad399cee channel:InputChannel boosts:int
channels.setEmojiStickers#3cd930b7 channel:InputChannel stickerset:InputStickerSet = Bool;
channels.restrictSponsoredMessages#9ae91519 channel:InputChannel restricted:Bool = Updates;
channels.searchPosts#d19f987b hashtag:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
channels.updatePaidMessagesPrice#fc84653f channel:InputChannel send_paid_messages_stars:long = Updates;
channels.updatePaidMessagesPrice#4b12327b flags:# broadcast_messages_allowed:flags.0?true channel:InputChannel send_paid_messages_stars:long = Updates;
channels.toggleAutotranslation#167fc0a1 channel:InputChannel enabled:Bool = Updates;
channels.getMessageAuthor#ece2a0e6 channel:InputChannel id:int = User;
bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;

View File

@ -1509,7 +1509,7 @@ export interface LangPair {
'FrozenAccountAppealTitle': undefined;
'ButtonAppeal': undefined;
'ButtonUnderstood': undefined;
'ActionPaidMessageGroupPriceFree': undefined;
'ActionPaidMessagePriceFreeYou': undefined;
'NotificationTitleNotSupportedInFrozenAccount': undefined;
'NotificationMessageNotSupportedInFrozenAccount': undefined;
'GiftRibbonSale': undefined;
@ -1530,6 +1530,10 @@ export interface LangPair {
'ValueGiftSortByNumber': undefined;
'ResellGiftsNoFound': undefined;
'ResellGiftsClearFilters': undefined;
'MonoforumBadge': undefined;
'MonoforumStatus': undefined;
'MonoforumComposerPlaceholder': undefined;
'ChannelSendMessage': undefined;
'AutomaticTranslation': undefined;
}
@ -2393,8 +2397,12 @@ export interface LangPairWithVariables<V = LangVariable> {
'percent': V;
'amount': V;
};
'FirstMessageInPaidMessagesChat': {
'user': V;
'MessagesPlaceholderPaidUser': {
'peer': V;
'amount': V;
};
'MessagesPlaceholderPaidChannel': {
'peer': V;
'amount': V;
};
'ComposerPlaceholderPaidMessage': {
@ -2434,8 +2442,21 @@ export interface LangPairWithVariables<V = LangVariable> {
'botLink': V;
'date': V;
};
'ActionPaidMessageGroupPrice': {
'stars': V;
'ActionPaidMessagePrice': {
'peer': V;
'amount': V;
};
'ActionPaidMessagePriceYou': {
'amount': V;
};
'ActionPaidMessagePriceFree': {
'peer': V;
};
'ActionMessageChannelFree': {
'peer': V;
};
'ActionMessageChannelDisabled': {
'peer': V;
};
'ApiMessageActionPaidMessagesRefundedOutgoing': {
'stars': V;

View File

@ -1,15 +1,17 @@
import { CHANNEL_ID_LENGTH } from '../../config';
import { CHANNEL_ID_BASE } from '../../config';
export function isUserId(entityId: string) {
return !entityId.startsWith('-');
}
export function isChannelId(entityId: string) {
return entityId.length === CHANNEL_ID_LENGTH && entityId.startsWith('-1');
const n = Number(entityId);
return n < -CHANNEL_ID_BASE;
}
export function toChannelId(mtpId: string) {
return `-1${mtpId.padStart(CHANNEL_ID_LENGTH - 2, '0')}`;
const n = Number(mtpId);
return String(-CHANNEL_ID_BASE - n);
}
export function getCleanPeerId(peerId: string) {