Send As: Fix error in some chats (#5966)

This commit is contained in:
zubiden 2025-06-04 20:41:33 +02:00 committed by Alexander Zinchuk
parent f1a538ed8e
commit 91b8463b35
22 changed files with 142 additions and 81 deletions

View File

@ -52,67 +52,77 @@ function buildApiChatFieldsFromPeerEntity(
peerEntity: Entity, peerEntity: Entity,
isSupport = false, isSupport = false,
): PeerEntityApiChatFields { ): PeerEntityApiChatFields {
const user = peerEntity instanceof GramJs.User ? peerEntity : undefined;
const channel = peerEntity instanceof GramJs.Channel ? peerEntity : undefined;
const userOrChannel = user || channel;
// Shared fields
const isMin = Boolean('min' in peerEntity && peerEntity.min); const isMin = Boolean('min' in peerEntity && peerEntity.min);
const accessHash = ('accessHash' in peerEntity) ? String(peerEntity.accessHash) : undefined; const accessHash = ('accessHash' in peerEntity) ? String(peerEntity.accessHash) : undefined;
const hasVideoAvatar = 'photo' in peerEntity && peerEntity.photo && 'hasVideo' in peerEntity.photo const hasVideoAvatar = 'photo' in peerEntity && peerEntity.photo && 'hasVideo' in peerEntity.photo
&& peerEntity.photo.hasVideo; && peerEntity.photo.hasVideo;
const avatarPhotoId = ('photo' in peerEntity) && peerEntity.photo ? buildAvatarPhotoId(peerEntity.photo) : undefined; const avatarPhotoId = ('photo' in peerEntity) && peerEntity.photo ? buildAvatarPhotoId(peerEntity.photo) : undefined;
const areSignaturesShown = Boolean('signatures' in peerEntity && peerEntity.signatures); const hasUsername = Boolean('username' in peerEntity && peerEntity.username);
const hasPrivateLink = Boolean('hasLink' in peerEntity && peerEntity.hasLink);
const isScam = Boolean('scam' in peerEntity && peerEntity.scam);
const isFake = Boolean('fake' in peerEntity && peerEntity.fake);
const isJoinToSend = Boolean('joinToSend' in peerEntity && peerEntity.joinToSend);
const isJoinRequest = Boolean('joinRequest' in peerEntity && peerEntity.joinRequest);
const usernames = buildApiUsernames(peerEntity); const usernames = buildApiUsernames(peerEntity);
const isForum = Boolean('forum' in peerEntity && peerEntity.forum);
const areStoriesHidden = Boolean('storiesHidden' in peerEntity && peerEntity.storiesHidden); // Chat and channel shared fields
const maxStoryId = 'storiesMaxId' in peerEntity ? peerEntity.storiesMaxId : undefined; const isCallActive = 'callActive' in peerEntity && peerEntity.callActive;
const botVerificationIconId = 'botVerificationIcon' in peerEntity const isCallNotEmpty = 'callNotEmpty' in peerEntity && peerEntity.callNotEmpty;
? peerEntity.botVerificationIcon?.toString() : undefined; const creationDate = 'date' in peerEntity ? peerEntity.date : undefined;
const storiesUnavailable = Boolean('storiesUnavailable' in peerEntity && peerEntity.storiesUnavailable); const membersCount = 'participantsCount' in peerEntity ? peerEntity.participantsCount : undefined;
const color = ('color' in peerEntity && peerEntity.color) ? buildApiPeerColor(peerEntity.color) : undefined; const isProtected = 'noforwards' in peerEntity && peerEntity.noforwards;
const emojiStatus = ('emojiStatus' in peerEntity && peerEntity.emojiStatus) const isCreator = 'creator' in peerEntity && peerEntity.creator;
? buildApiEmojiStatus(peerEntity.emojiStatus) : undefined;
const boostLevel = ('level' in peerEntity) ? peerEntity.level : undefined; // User and channel shared fields
const areProfilesShown = Boolean('signatureProfiles' in peerEntity && peerEntity.signatureProfiles); const isScam = userOrChannel?.scam;
const subscriptionUntil = 'subscriptionUntilDate' in peerEntity ? peerEntity.subscriptionUntilDate : undefined; const isFake = userOrChannel?.fake;
const paidMessagesStars = 'sendPaidMessagesStars' in peerEntity ? peerEntity.sendPaidMessagesStars : undefined; const areStoriesHidden = userOrChannel?.storiesHidden;
const maxStoryId = userOrChannel?.storiesMaxId;
const botVerificationIconId = userOrChannel?.botVerificationIcon?.toString();
const storiesUnavailable = userOrChannel?.storiesUnavailable;
const color = userOrChannel?.color ? buildApiPeerColor(userOrChannel.color) : undefined;
const emojiStatus = userOrChannel?.emojiStatus ? buildApiEmojiStatus(userOrChannel.emojiStatus) : undefined;
const paidMessagesStars = userOrChannel?.sendPaidMessagesStars;
const isVerified = userOrChannel?.verified;
return { return {
isMin, isMin,
hasPrivateLink, isLinkedInDiscussion: channel?.hasLink,
areSignaturesShown, areSignaturesShown: channel?.signatures,
areProfilesShown, areProfilesShown: channel?.signatureProfiles,
usernames, usernames,
accessHash, accessHash,
hasVideoAvatar, hasVideoAvatar,
avatarPhotoId, avatarPhotoId,
...('verified' in peerEntity && { isVerified: peerEntity.verified }), isVerified,
...('callActive' in peerEntity && { isCallActive: peerEntity.callActive }), isCallActive,
...('callNotEmpty' in peerEntity && { isCallNotEmpty: peerEntity.callNotEmpty }), isCallNotEmpty,
...('date' in peerEntity && { creationDate: peerEntity.date }), creationDate,
...('participantsCount' in peerEntity && peerEntity.participantsCount !== undefined && { hasUsername,
membersCount: peerEntity.participantsCount, ...(membersCount !== undefined && { membersCount }),
}), isProtected,
...('noforwards' in peerEntity && { isProtected: Boolean(peerEntity.noforwards) }),
isSupport: isSupport || undefined, isSupport: isSupport || undefined,
...buildApiChatPermissions(peerEntity), isCreator,
...('creator' in peerEntity && { isCreator: peerEntity.creator }),
...buildApiChatRestrictions(peerEntity),
...buildApiChatMigrationInfo(peerEntity),
fakeType: isScam ? 'scam' : (isFake ? 'fake' : undefined), fakeType: isScam ? 'scam' : (isFake ? 'fake' : undefined),
color, color,
isJoinToSend, isJoinToSend: channel?.joinToSend,
isJoinRequest, isJoinRequest: channel?.joinRequest,
isForum, isForum: channel?.forum,
areStoriesHidden, areStoriesHidden,
maxStoryId, maxStoryId,
hasStories: Boolean(maxStoryId) && !storiesUnavailable, hasStories: Boolean(maxStoryId) && !storiesUnavailable,
emojiStatus, emojiStatus,
boostLevel, boostLevel: channel?.level,
botVerificationIconId, botVerificationIconId,
subscriptionUntil, hasGeo: channel?.hasGeo,
subscriptionUntil: channel?.subscriptionUntilDate,
paidMessagesStars: paidMessagesStars?.toJSNumber(), paidMessagesStars: paidMessagesStars?.toJSNumber(),
...buildApiChatPermissions(peerEntity),
...buildApiChatRestrictions(peerEntity),
...buildApiChatMigrationInfo(peerEntity),
}; };
} }

View File

@ -96,7 +96,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
const { const {
id, firstName, lastName, fake, scam, support, closeFriend, storiesUnavailable, storiesMaxId, id, firstName, lastName, fake, scam, support, closeFriend, storiesUnavailable, storiesMaxId,
bot, botActiveUsers, botVerificationIcon, botInlinePlaceholder, botAttachMenu, botCanEdit, bot, botActiveUsers, botVerificationIcon, botInlinePlaceholder, botAttachMenu, botCanEdit,
sendPaidMessagesStars, sendPaidMessagesStars, username,
} = mtpUser; } = mtpUser;
const hasVideoAvatar = mtpUser.photo instanceof GramJs.UserProfilePhoto ? Boolean(mtpUser.photo.hasVideo) : undefined; const hasVideoAvatar = mtpUser.photo instanceof GramJs.UserProfilePhoto ? Boolean(mtpUser.photo.hasVideo) : undefined;
const avatarPhotoId = mtpUser.photo && buildAvatarPhotoId(mtpUser.photo); const avatarPhotoId = mtpUser.photo && buildAvatarPhotoId(mtpUser.photo);
@ -121,6 +121,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
canEditBot: botCanEdit, canEditBot: botCanEdit,
...(userType === 'userTypeBot' && { canBeInvitedToGroup: !mtpUser.botNochats }), ...(userType === 'userTypeBot' && { canBeInvitedToGroup: !mtpUser.botNochats }),
...(usernames && { usernames }), ...(usernames && { usernames }),
hasUsername: Boolean(username),
phoneNumber: mtpUser.phone || '', phoneNumber: mtpUser.phone || '',
noStatus: !mtpUser.status, noStatus: !mtpUser.status,
...(mtpUser.accessHash && { accessHash: String(mtpUser.accessHash) }), ...(mtpUser.accessHash && { accessHash: String(mtpUser.accessHash) }),

View File

@ -29,12 +29,14 @@ export interface ApiChat {
isVerified?: true; isVerified?: true;
areSignaturesShown?: boolean; areSignaturesShown?: boolean;
areProfilesShown?: boolean; areProfilesShown?: boolean;
hasPrivateLink?: boolean; isLinkedInDiscussion?: boolean;
hasGeo?: boolean;
accessHash?: string; accessHash?: string;
isMin?: boolean; isMin?: boolean;
hasVideoAvatar?: boolean; hasVideoAvatar?: boolean;
avatarPhotoId?: string; avatarPhotoId?: string;
usernames?: ApiUsername[]; usernames?: ApiUsername[];
hasUsername?: boolean;
membersCount?: number; membersCount?: number;
creationDate?: number; creationDate?: number;
isSupport?: true; isSupport?: true;

View File

@ -20,6 +20,7 @@ export interface ApiUser {
lastName?: string; lastName?: string;
noStatus?: boolean; noStatus?: boolean;
usernames?: ApiUsername[]; usernames?: ApiUsername[];
hasUsername?: boolean;
phoneNumber: string; phoneNumber: string;
accessHash?: string; accessHash?: string;
hasVideoAvatar?: boolean; hasVideoAvatar?: boolean;

View File

@ -1304,6 +1304,7 @@
"ComposerSilentPostingEnabledTootlip" = "Subscribers will receive a silent notification."; "ComposerSilentPostingEnabledTootlip" = "Subscribers will receive a silent notification.";
"ComposerSilentPostingDisabledTootlip" = "Subscribers will be notified when you post."; "ComposerSilentPostingDisabledTootlip" = "Subscribers will be notified when you post.";
"ComposerPlaceholder" = "Message"; "ComposerPlaceholder" = "Message";
"ComposerPlaceholderAnonymous" = "Send anonymously";
"ComposerPlaceholderBroadcast" = "Broadcast"; "ComposerPlaceholderBroadcast" = "Broadcast";
"ComposerPlaceholderBroadcastSilent" = "Silent Broadcast"; "ComposerPlaceholderBroadcastSilent" = "Silent Broadcast";
"ComposerPlaceholderTopic" = "Message in {topic}"; "ComposerPlaceholderTopic" = "Message in {topic}";

View File

@ -21,6 +21,7 @@ import type {
ApiMessage, ApiMessage,
ApiMessageEntity, ApiMessageEntity,
ApiNewPoll, ApiNewPoll,
ApiPeer,
ApiQuickReply, ApiQuickReply,
ApiReaction, ApiReaction,
ApiStealthMode, ApiStealthMode,
@ -62,6 +63,7 @@ import {
getStoryKey, getStoryKey,
isChatAdmin, isChatAdmin,
isChatChannel, isChatChannel,
isChatPublic,
isChatSuperGroup, isChatSuperGroup,
isSameReaction, isSameReaction,
isSystemBot, isSystemBot,
@ -253,8 +255,7 @@ type StateProps =
inlineBots?: Record<string, false | InlineBotSettings>; inlineBots?: Record<string, false | InlineBotSettings>;
botCommands?: ApiBotCommand[] | false; botCommands?: ApiBotCommand[] | false;
botMenuButton?: ApiBotMenuButton; botMenuButton?: ApiBotMenuButton;
sendAsUser?: ApiUser; sendAsPeer?: ApiPeer;
sendAsChat?: ApiChat;
sendAsId?: string; sendAsId?: string;
editingDraft?: ApiFormattedText; editingDraft?: ApiFormattedText;
requestedDraft?: ApiFormattedText; requestedDraft?: ApiFormattedText;
@ -374,8 +375,7 @@ const Composer: FC<OwnProps & StateProps> = ({
inlineBots, inlineBots,
isInlineBotLoading, isInlineBotLoading,
botCommands, botCommands,
sendAsUser, sendAsPeer,
sendAsChat,
sendAsId, sendAsId,
editingDraft, editingDraft,
requestedDraft, requestedDraft,
@ -472,8 +472,7 @@ const Composer: FC<OwnProps & StateProps> = ({
const isInMessageList = type === 'messageList'; const isInMessageList = type === 'messageList';
const isInStoryViewer = type === 'story'; const isInStoryViewer = type === 'story';
const sendAsPeerIds = isInMessageList ? chat?.sendAsPeerIds : undefined; const sendAsPeerIds = isInMessageList ? chat?.sendAsPeerIds : undefined;
const canShowSendAs = sendAsPeerIds const canShowSendAs = Boolean(sendAsPeerIds?.length);
&& (sendAsPeerIds.length > 1 || !sendAsPeerIds.some((peer) => peer.id === currentUserId!));
// Prevent Symbol Menu from closing when calendar is open // Prevent Symbol Menu from closing when calendar is open
const [isSymbolMenuForced, forceShowSymbolMenu, cancelForceShowSymbolMenu] = useFlag(); const [isSymbolMenuForced, forceShowSymbolMenu, cancelForceShowSymbolMenu] = useFlag();
const sendMessageAction = useSendMessageAction(chatId, threadId); const sendMessageAction = useSendMessageAction(chatId, threadId);
@ -518,7 +517,9 @@ const Composer: FC<OwnProps & StateProps> = ({
useEffect(() => { useEffect(() => {
const isChannelWithProfiles = isChannel && chat?.areProfilesShown; const isChannelWithProfiles = isChannel && chat?.areProfilesShown;
if (chatId && chat && !sendAsPeerIds && isReady && (isChatSuperGroup(chat) || isChannelWithProfiles)) { const isChatWithSendAs = chat && isChatSuperGroup(chat)
&& Boolean(isChatPublic(chat) || chat.isLinkedInDiscussion || chat.hasGeo);
if (!sendAsPeerIds && isReady && (isChatWithSendAs || isChannelWithProfiles)) {
loadSendAs({ chatId }); loadSendAs({ chatId });
} }
}, [chat, chatId, isChannel, isReady, loadSendAs, sendAsPeerIds]); }, [chat, chatId, isChannel, isReady, loadSendAs, sendAsPeerIds]);
@ -1586,6 +1587,11 @@ const Composer: FC<OwnProps & StateProps> = ({
withNodes: true, withNodes: true,
}); });
} }
if (chat?.adminRights?.anonymous) {
return lang('ComposerPlaceholderAnonymous');
}
if (chat?.isForum && chat?.isForumAsMessages && threadId === MAIN_THREAD_ID) { if (chat?.isForum && chat?.isForumAsMessages && threadId === MAIN_THREAD_ID) {
return replyToTopic return replyToTopic
? lang('ComposerPlaceholderTopic', { topic: replyToTopic.title }) ? lang('ComposerPlaceholderTopic', { topic: replyToTopic.title })
@ -1978,7 +1984,7 @@ const Composer: FC<OwnProps & StateProps> = ({
<Icon name="bot-commands-filled" /> <Icon name="bot-commands-filled" />
</ResponsiveHoverButton> </ResponsiveHoverButton>
)} )}
{canShowSendAs && (sendAsUser || sendAsChat) && ( {canShowSendAs && sendAsPeer && (
<Button <Button
round round
color="translucent" color="translucent"
@ -1991,7 +1997,7 @@ const Composer: FC<OwnProps & StateProps> = ({
)} )}
> >
<Avatar <Avatar
peer={sendAsUser || sendAsChat} peer={sendAsPeer}
size="tiny" size="tiny"
/> />
</Button> </Button>
@ -2363,13 +2369,8 @@ export default memo(withGlobal<OwnProps>(
const { currentUserId } = global; const { currentUserId } = global;
const currentUser = selectUser(global, currentUserId!)!; const currentUser = selectUser(global, currentUserId!)!;
const defaultSendAsId = chatFullInfo ? chatFullInfo?.sendAsId || currentUserId : undefined; const defaultSendAsId = chatFullInfo ? chatFullInfo?.sendAsId || currentUserId : undefined;
const sendAsId = chat?.sendAsPeerIds && defaultSendAsId && ( const sendAsId = defaultSendAsId;
chat.sendAsPeerIds.some((peer) => peer.id === defaultSendAsId) const sendAsPeer = sendAsId ? selectPeer(global, sendAsId) : undefined;
? defaultSendAsId
: (chat?.adminRights?.anonymous ? chat?.id : undefined)
);
const sendAsUser = sendAsId ? selectUser(global, sendAsId) : undefined;
const sendAsChat = !sendAsUser && sendAsId ? selectChat(global, sendAsId) : undefined;
const requestedDraft = selectRequestedDraft(global, chatId); const requestedDraft = selectRequestedDraft(global, chatId);
const requestedDraftFiles = selectRequestedDraftFiles(global, chatId); const requestedDraftFiles = selectRequestedDraftFiles(global, chatId);
@ -2467,8 +2468,7 @@ export default memo(withGlobal<OwnProps>(
isInlineBotLoading: tabState.inlineBots.isLoading, isInlineBotLoading: tabState.inlineBots.isLoading,
botCommands: userFullInfo ? (userFullInfo.botInfo?.commands || false) : undefined, botCommands: userFullInfo ? (userFullInfo.botInfo?.commands || false) : undefined,
botMenuButton: userFullInfo?.botInfo?.menuButton, botMenuButton: userFullInfo?.botInfo?.menuButton,
sendAsUser, sendAsPeer,
sendAsChat,
sendAsId, sendAsId,
editingDraft, editingDraft,
requestedDraft, requestedDraft,

View File

@ -7,7 +7,7 @@ import type { CustomPeerType, UniqueCustomPeer } from '../../../types';
import { DEBUG } from '../../../config'; import { DEBUG } from '../../../config';
import { requestMeasure } from '../../../lib/fasterdom/fasterdom'; import { requestMeasure } from '../../../lib/fasterdom/fasterdom';
import { getGroupStatus, getUserStatus, isUserOnline } from '../../../global/helpers'; import { getGroupStatus, getMainUsername, getUserStatus, isUserOnline } from '../../../global/helpers';
import { getPeerTypeKey, isApiPeerChat } from '../../../global/helpers/peers'; import { getPeerTypeKey, isApiPeerChat } from '../../../global/helpers/peers';
import { selectPeer, selectUserStatus } from '../../../global/selectors'; import { selectPeer, selectUserStatus } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
@ -279,7 +279,7 @@ const PeerPicker = <CategoryType extends string = CustomPeerType>({
if (!peer) return undefined; if (!peer) return undefined;
if (withPeerUsernames) { if (withPeerUsernames) {
const username = peer.usernames?.[0]?.username; const username = getMainUsername(peer);
if (username) { if (username) {
return [`@${username}`]; return [`@${username}`];
} }

View File

@ -9,6 +9,7 @@ import type {
} from '../../../api/types'; } from '../../../api/types';
import type { Signal } from '../../../util/signals'; import type { Signal } from '../../../util/signals';
import { getMainUsername } from '../../../global/helpers';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import freezeWhenClosed from '../../../util/hoc/freezeWhenClosed'; import freezeWhenClosed from '../../../util/hoc/freezeWhenClosed';
import setTooltipItemVisible from '../../../util/setTooltipItemVisible'; import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
@ -63,7 +64,7 @@ const ChatCommandTooltip: FC<OwnProps> = ({
const bot = usersById[botId]; const bot = usersById[botId];
sendBotCommand({ sendBotCommand({
command: `/${command}${withUsername && bot ? `@${bot.usernames![0].username}` : ''}`, command: `/${command}${withUsername && bot ? `@${getMainUsername(bot)}` : ''}`,
}); });
onClick(); onClick();
}); });

View File

@ -102,7 +102,7 @@ export default function useMentionTooltip(
forceFocus = false, forceFocus = false,
insertAtEnd = false, insertAtEnd = false,
) => { ) => {
if (!peer.usernames && !getPeerTitle(lang, peer)) { if (!peer.hasUsername && !getPeerTitle(lang, peer)) {
return; return;
} }

View File

@ -9,6 +9,7 @@ import {
TME_LINK_PREFIX, TME_LINK_PREFIX,
} from '../../../config'; } from '../../../config';
import { import {
getMainUsername,
getMessageInvoice, getMessageText, isChatChannel, getMessageInvoice, getMessageText, isChatChannel,
} from '../../../global/helpers'; } from '../../../global/helpers';
import { getPeerTitle } from '../../../global/helpers/peers'; import { getPeerTitle } from '../../../global/helpers/peers';
@ -386,10 +387,9 @@ const ActionMessageText = ({
if (isAttachMenu) return lang('ActionAttachMenuBotAllowed'); if (isAttachMenu) return lang('ActionAttachMenuBotAllowed');
if (isFromRequest) return lang('ActionWebappBotAllowed'); if (isFromRequest) return lang('ActionWebappBotAllowed');
if (app) { if (app) {
const link = sender?.usernames?.length const senderUsername = sender && getMainUsername(sender);
&& `${TME_LINK_PREFIX + sender.usernames[0].username}/${app.shortName}`; const link = senderUsername && `${TME_LINK_PREFIX + senderUsername}/${app.shortName}`;
const appLink = link const appLink = link
? <Link onClick={() => openTelegramLink({ url: link })}>{app.title}</Link> ? <Link onClick={() => openTelegramLink({ url: link })}>{app.title}</Link>
: lang('ActionBotAppPlaceholder'); : lang('ActionBotAppPlaceholder');
return lang('ActionBotAllowedFromApp', { app: appLink }, { withNodes: true }); return lang('ActionBotAllowedFromApp', { app: appLink }, { withNodes: true });

View File

@ -47,6 +47,7 @@ import { EMOJI_STATUS_LOOP_LIMIT, MESSAGE_APPEARANCE_DELAY } from '../../../conf
import { import {
areReactionsEmpty, areReactionsEmpty,
getIsDownloading, getIsDownloading,
getMainUsername,
getMessageContent, getMessageContent,
getMessageCustomShape, getMessageCustomShape,
getMessageDownloadableMedia, getMessageDownloadableMedia,
@ -1499,7 +1500,7 @@ const Message: FC<OwnProps & StateProps> = ({
const senderIsPremium = senderPeer && 'isPremium' in senderPeer && senderPeer.isPremium; const senderIsPremium = senderPeer && 'isPremium' in senderPeer && senderPeer.isPremium;
const shouldRenderForwardAvatar = asForwarded && senderPeer; const shouldRenderForwardAvatar = asForwarded && senderPeer;
const hasBotSenderUsername = botSender?.usernames?.length; const hasBotSenderUsername = botSender?.hasUsername;
return ( return (
<div className="message-title" dir="ltr"> <div className="message-title" dir="ltr">
{(senderTitle || asForwarded) ? ( {(senderTitle || asForwarded) ? (
@ -1543,14 +1544,14 @@ const Message: FC<OwnProps & StateProps> = ({
) : !botSender ? ( ) : !botSender ? (
NBSP NBSP
) : undefined} ) : undefined}
{Boolean(botSender?.usernames?.length) && ( {botSender?.hasUsername && (
<span className="interactive"> <span className="interactive">
<span className="via">{lang('ViaBot')}</span> <span className="via">{lang('ViaBot')}</span>
<span <span
className="sender-title" className="sender-title"
onClick={handleViaBotClick} onClick={handleViaBotClick}
> >
{renderText(`@${botSender.usernames[0].username}`)} {renderText(`@${getMainUsername(botSender)}`)}
</span> </span>
</span> </span>
)} )}

View File

@ -3,6 +3,7 @@ import { getActions, withGlobal } from '../../../global';
import type { TabState } from '../../../global/types'; import type { TabState } from '../../../global/types';
import { getMainUsername } from '../../../global/helpers';
import { selectUser } from '../../../global/selectors'; import { selectUser } from '../../../global/selectors';
import { formatDateToString } from '../../../util/dates/dateFormat'; import { formatDateToString } from '../../../util/dates/dateFormat';
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets'; import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
@ -130,9 +131,8 @@ export default memo(withGlobal<OwnProps>(
(global): StateProps => { (global): StateProps => {
const freezeUntilDate = global.appConfig?.freezeUntilDate; const freezeUntilDate = global.appConfig?.freezeUntilDate;
const freezeAppealUrl = global.appConfig?.freezeAppealUrl; const freezeAppealUrl = global.appConfig?.freezeAppealUrl;
const botFreezeAppealId = global.botFreezeAppealId; const botFreezeAppeal = global.botFreezeAppealId ? selectUser(global, global.botFreezeAppealId) : undefined;
const botFreezeAppealUsername = botFreezeAppealId const botFreezeAppealUsername = botFreezeAppeal && getMainUsername(botFreezeAppeal);
? selectUser(global, botFreezeAppealId)?.usernames?.[0]?.username : undefined;
return { return {
freezeUntilDate, freezeUntilDate,

View File

@ -8,7 +8,7 @@ import type { ApiChat } from '../../../api/types';
import { ManagementScreens } from '../../../types'; import { ManagementScreens } from '../../../types';
import { STICKER_SIZE_DISCUSSION_GROUPS } from '../../../config'; import { STICKER_SIZE_DISCUSSION_GROUPS } from '../../../config';
import { isChatChannel } from '../../../global/helpers'; import { isChatChannel, isChatPublic } from '../../../global/helpers';
import { selectChat, selectChatFullInfo } from '../../../global/selectors'; import { selectChat, selectChatFullInfo } from '../../../global/selectors';
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets'; import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
import renderText from '../../common/helpers/renderText'; import renderText from '../../common/helpers/renderText';
@ -152,7 +152,7 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
const linkedGroup = chatsByIds[linkedGroupId]; const linkedGroup = chatsByIds[linkedGroupId];
if (!linkedGroup) return undefined; if (!linkedGroup) return undefined;
if (linkedGroup.hasPrivateLink) { if (isChatPublic(linkedGroup)) {
return renderText( return renderText(
`Do you want to make **${linkedGroup.title}** the discussion board for **${chat!.title}**?`, `Do you want to make **${linkedGroup.title}** the discussion board for **${chat!.title}**?`,
['br', 'simple_markdown'], ['br', 'simple_markdown'],

View File

@ -100,9 +100,9 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
const primaryInvite = exportedInvites?.find(({ isPermanent }) => isPermanent); const primaryInvite = exportedInvites?.find(({ isPermanent }) => isPermanent);
const primaryInviteLink = chatMainUsername ? `${TME_LINK_PREFIX}${chatMainUsername}` : primaryInvite?.link; const primaryInviteLink = chatMainUsername ? `${TME_LINK_PREFIX}${chatMainUsername}` : primaryInvite?.link;
const temporalInvites = useMemo(() => { const temporalInvites = useMemo(() => {
const invites = chat?.usernames ? exportedInvites : exportedInvites?.filter(({ isPermanent }) => !isPermanent); const invites = chat?.hasUsername ? exportedInvites : exportedInvites?.filter(({ isPermanent }) => !isPermanent);
return invites?.sort(inviteComparator); return invites?.sort(inviteComparator);
}, [chat?.usernames, exportedInvites]); }, [chat?.hasUsername, exportedInvites]);
const editInvite = (invite: ApiExportedInvite) => { const editInvite = (invite: ApiExportedInvite) => {
setEditingExportedInvite({ chatId, invite }); setEditingExportedInvite({ chatId, invite });

View File

@ -216,7 +216,7 @@ function Story({
isLoadedStory isLoadedStory
&& story.isPublic && story.isPublic
&& !isChangelog && !isChangelog
&& peer?.usernames?.length, && peer?.hasUsername,
); );
const canShare = Boolean( const canShare = Boolean(

View File

@ -27,6 +27,7 @@ import { debounce } from '../../../util/schedulers';
import { getServerTime } from '../../../util/serverTime'; import { getServerTime } from '../../../util/serverTime';
import { extractCurrentThemeParams } from '../../../util/themeStyle'; import { extractCurrentThemeParams } from '../../../util/themeStyle';
import { callApi } from '../../../api/gramjs'; import { callApi } from '../../../api/gramjs';
import { getMainUsername } from '../../helpers';
import { import {
getWebAppKey, getWebAppKey,
} from '../../helpers/bots'; } from '../../helpers/bots';
@ -377,7 +378,7 @@ addActionHandler('switchBotInline', (global, actions, payload): ActionReturnType
actions.openChatWithDraft({ actions.openChatWithDraft({
text: { text: {
text: `@${botSender.usernames![0].username} ${query}`, text: `@${getMainUsername(botSender)} ${query}`,
}, },
chatId: isSamePeer ? chat.id : undefined, chatId: isSamePeer ? chat.id : undefined,
filter, filter,

View File

@ -36,6 +36,7 @@ import {
import { copyTextToClipboard } from '../../../util/clipboard'; import { copyTextToClipboard } from '../../../util/clipboard';
import { formatShareText, processDeepLink } from '../../../util/deeplink'; import { formatShareText, processDeepLink } from '../../../util/deeplink';
import { isDeepLink } from '../../../util/deepLinkParser'; import { isDeepLink } from '../../../util/deepLinkParser';
import { isUserId } from '../../../util/entities/ids';
import { getCurrentTabId } from '../../../util/establishMultitabRole'; import { getCurrentTabId } from '../../../util/establishMultitabRole';
import { getOrderedIds } from '../../../util/folderManager'; import { getOrderedIds } from '../../../util/folderManager';
import { import {
@ -564,6 +565,30 @@ addActionHandler('loadFullChat', (global, actions, payload): ActionReturnType =>
} }
}); });
addActionHandler('invalidateFullInfo', (global, actions, payload): ActionReturnType => {
const { peerId } = payload;
const isUser = isUserId(peerId);
if (isUser) {
return {
...global,
users: {
...global.users,
fullInfoById: omit(global.users.fullInfoById, [peerId]),
},
};
}
return {
...global,
chats: {
...global.chats,
fullInfoById: omit(global.chats.fullInfoById, [peerId]),
},
};
});
addActionHandler('loadTopChats', (): ActionReturnType => { addActionHandler('loadTopChats', (): ActionReturnType => {
runThrottledForLoadTopChats(() => { runThrottledForLoadTopChats(() => {
loadChats('active', undefined, true); loadChats('active', undefined, true);

View File

@ -1,4 +1,4 @@
import type { ApiMessage, ApiUpdateChat } from '../../../api/types'; import type { ApiChat, ApiMessage, ApiUpdateChat } from '../../../api/types';
import type { ActionReturnType } from '../../types'; import type { ActionReturnType } from '../../types';
import { MAIN_THREAD_ID } from '../../../api/types'; import { MAIN_THREAD_ID } from '../../../api/types';
@ -43,6 +43,10 @@ import {
} from '../../selectors'; } from '../../selectors';
const TYPING_STATUS_CLEAR_DELAY = 6000; // 6 seconds const TYPING_STATUS_CLEAR_DELAY = 6000; // 6 seconds
const INVALIDATE_FULL_CHAT_FIELDS = new Set<keyof ApiChat>([
'boostLevel', 'isForum', 'isLinkedInDiscussion', 'fakeType', 'restrictionReason', 'isJoinToSend', 'isJoinRequest',
'type',
]);
addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
switch (update['@type']) { switch (update['@type']) {
@ -93,6 +97,15 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
} }
}); });
if (localChat) {
const chatUpdate = update.chat;
const changedFields = (Object.keys(chatUpdate) as (keyof ApiChat)[])
.filter((key) => localChat[key] !== chatUpdate[key]);
if (changedFields.some((key) => INVALIDATE_FULL_CHAT_FIELDS.has(key))) {
actions.invalidateFullInfo({ peerId: update.id });
}
}
return undefined; return undefined;
} }

View File

@ -328,7 +328,7 @@ export function getFolderDescriptionText(lang: OldLangFn, folder: ApiChatFolder,
} }
export function isChatPublic(chat: ApiChat) { export function isChatPublic(chat: ApiChat) {
return chat.usernames?.some(({ isActive }) => isActive); return chat.hasUsername;
} }
export function getOrderedTopics( export function getOrderedTopics(

View File

@ -14,6 +14,7 @@ import {
getHasAdminRight, getHasAdminRight,
getPrivateChatUserId, getPrivateChatUserId,
isChatChannel, isChatChannel,
isChatPublic,
isChatSuperGroup, isChatSuperGroup,
isHistoryClearMessage, isHistoryClearMessage,
isUserBot, isUserBot,
@ -256,7 +257,7 @@ export function selectCanInviteToChat<T extends GlobalState>(global: T, chatId:
// https://github.com/TelegramMessenger/Telegram-iOS/blob/5126be83b3b9578fb014eb52ca553da9e7a8b83a/submodules/TelegramCore/Sources/TelegramEngine/Peers/Communities.swift#L6 // https://github.com/TelegramMessenger/Telegram-iOS/blob/5126be83b3b9578fb014eb52ca553da9e7a8b83a/submodules/TelegramCore/Sources/TelegramEngine/Peers/Communities.swift#L6
return !chat.migratedTo && Boolean(!isUserId(chatId) && ((isChatChannel(chat) || isChatSuperGroup(chat)) ? ( return !chat.migratedTo && Boolean(!isUserId(chatId) && ((isChatChannel(chat) || isChatSuperGroup(chat)) ? (
chat.isCreator || getHasAdminRight(chat, 'inviteUsers') chat.isCreator || getHasAdminRight(chat, 'inviteUsers')
|| (chat.usernames?.length && !chat.isJoinRequest) || (isChatPublic(chat) && !chat.isJoinRequest)
) : (chat.isCreator || getHasAdminRight(chat, 'inviteUsers')))); ) : (chat.isCreator || getHasAdminRight(chat, 'inviteUsers'))));
} }

View File

@ -1154,6 +1154,9 @@ export interface ActionPayloads {
withPhotos?: boolean; withPhotos?: boolean;
force?: boolean; force?: boolean;
}; };
invalidateFullInfo: {
peerId: string;
};
updateChatPhoto: { updateChatPhoto: {
chatId: string; chatId: string;
photo: ApiPhoto; photo: ApiPhoto;

View File

@ -1112,6 +1112,7 @@ export interface LangPair {
'ComposerSilentPostingEnabledTootlip': undefined; 'ComposerSilentPostingEnabledTootlip': undefined;
'ComposerSilentPostingDisabledTootlip': undefined; 'ComposerSilentPostingDisabledTootlip': undefined;
'ComposerPlaceholder': undefined; 'ComposerPlaceholder': undefined;
'ComposerPlaceholderAnonymous': undefined;
'ComposerPlaceholderBroadcast': undefined; 'ComposerPlaceholderBroadcast': undefined;
'ComposerPlaceholderBroadcastSilent': undefined; 'ComposerPlaceholderBroadcastSilent': undefined;
'ComposerPlaceholderTopicGeneral': undefined; 'ComposerPlaceholderTopicGeneral': undefined;