From f8c021b0434690575ff003f5ba1e98ba767c161f Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Mon, 25 Sep 2023 13:00:10 +0200 Subject: [PATCH] Implement TDLib-like channel prefix (#3858) --- src/api/gramjs/apiBuilders/chats.ts | 7 +++- src/api/gramjs/apiBuilders/peers.ts | 10 ++++- src/api/gramjs/gramjsBuilders/index.ts | 37 ++++++++----------- src/api/gramjs/methods/client.ts | 2 +- src/components/common/ChatExtra.tsx | 13 ++++--- .../middle/message/ContextMenuContainer.tsx | 15 ++++---- src/components/middle/message/Message.tsx | 7 ---- src/config.ts | 2 +- src/global/actions/api/chats.ts | 4 +- src/global/helpers/chats.ts | 22 ++--------- src/global/selectors/messages.ts | 28 +++++++++++++- 11 files changed, 80 insertions(+), 67 deletions(-) diff --git a/src/api/gramjs/apiBuilders/chats.ts b/src/api/gramjs/apiBuilders/chats.ts index 2faa2cd82..ac47c71f8 100644 --- a/src/api/gramjs/apiBuilders/chats.ts +++ b/src/api/gramjs/apiBuilders/chats.ts @@ -220,9 +220,14 @@ export function buildApiChatFromPreview( if (preview instanceof GramJs.ChatEmpty || preview instanceof GramJs.UserEmpty) { return undefined; } + const id = buildApiPeerId( + preview.id, + preview instanceof GramJs.User ? 'user' + : (preview instanceof GramJs.Chat || preview instanceof GramJs.ChatForbidden) ? 'chat' : 'channel', + ); return { - id: buildApiPeerId(preview.id, preview instanceof GramJs.User ? 'user' : 'chat'), + id, type: getApiChatTypeFromPeerEntity(preview), title: preview instanceof GramJs.User ? getUserName(preview) : preview.title, ...buildApiChatFieldsFromPeerEntity(preview, isSupport), diff --git a/src/api/gramjs/apiBuilders/peers.ts b/src/api/gramjs/apiBuilders/peers.ts index fce49190b..2d7170076 100644 --- a/src/api/gramjs/apiBuilders/peers.ts +++ b/src/api/gramjs/apiBuilders/peers.ts @@ -15,7 +15,15 @@ export function isPeerChannel(peer: GramJs.TypePeer | GramJs.TypeInputPeer): pee } export function buildApiPeerId(id: BigInt.BigInteger, type: 'user' | 'chat' | 'channel') { - return type === 'user' ? String(id) : `-${id}`; + if (type === 'user') { + return id.toString(); + } + + if (type === 'channel') { + return `-100${id}`; + } + + return `-${id}`; } export function getApiChatIdFromMtpPeer(peer: GramJs.TypePeer | GramJs.TypeInputPeer) { diff --git a/src/api/gramjs/gramjsBuilders/index.ts b/src/api/gramjs/gramjsBuilders/index.ts index ee1db52eb..844433abe 100644 --- a/src/api/gramjs/gramjsBuilders/index.ts +++ b/src/api/gramjs/gramjsBuilders/index.ts @@ -37,19 +37,17 @@ import { pick } from '../../../util/iteratees'; import { deserializeBytes } from '../helpers'; import localDb from '../localDb'; -const CHANNEL_ID_MIN_LENGTH = 11; // Example: -1000000000 +const CHANNEL_ID_MIN_LENGTH = 11; // Example: -1234567890 +const CHANNEL_ID_NEW_FORMAT_MIN_LENGTH = 14; // Example: -1001234567890 function checkIfChannelId(id: string) { - // HOTFIX New group id range starts with -4 + if (id.length >= CHANNEL_ID_NEW_FORMAT_MIN_LENGTH) return id.startsWith('-100'); + // LEGACY Unprefixed channel id if (id.length === CHANNEL_ID_MIN_LENGTH && id.startsWith('-4')) return false; return id.length >= CHANNEL_ID_MIN_LENGTH; } export function getEntityTypeById(chatOrUserId: string) { - if (typeof chatOrUserId === 'number') { - return getEntityTypeByDeprecatedId(chatOrUserId); - } - if (!chatOrUserId.startsWith('-')) { return 'user'; } else if (checkIfChannelId(chatOrUserId)) { @@ -59,17 +57,6 @@ export function getEntityTypeById(chatOrUserId: string) { } } -// Workaround for old-fashioned IDs stored locally -export function getEntityTypeByDeprecatedId(chatOrUserId: number) { - if (chatOrUserId > 0) { - return 'user'; - } else if (chatOrUserId <= -1000000000) { - return 'channel'; - } else { - return 'chat'; - } -} - export function buildPeer(chatOrUserId: string): GramJs.TypePeer { const type = getEntityTypeById(chatOrUserId); @@ -546,12 +533,20 @@ export function buildInputThemeParams(params: ApiThemeParameters) { } export function buildMtpPeerId(id: string, type: 'user' | 'chat' | 'channel') { - // Workaround for old-fashioned IDs stored locally - if (typeof id === 'number') { - return BigInt(Math.abs(id)); + if (type === 'user') { + return BigInt(id); } - return type === 'user' ? BigInt(id) : BigInt(id.slice(1)); + if (type === 'channel') { + if (id.length === CHANNEL_ID_NEW_FORMAT_MIN_LENGTH) { + return BigInt(id.slice(4)); + } + + // LEGACY Unprefixed channel id + return BigInt(id.slice(1)); + } + + return BigInt(id.slice(1)); } export function buildInputGroupCall(groupCall: Partial) { diff --git a/src/api/gramjs/methods/client.ts b/src/api/gramjs/methods/client.ts index 0528776c8..de9799f7e 100644 --- a/src/api/gramjs/methods/client.ts +++ b/src/api/gramjs/methods/client.ts @@ -480,7 +480,7 @@ export async function repairFileReference({ if (!result || result instanceof GramJs.messages.MessagesNotModified) return false; if (peer && 'pts' in result) { - updateChannelState(peer.channelId.toString(), result.pts); + updateChannelState(buildApiPeerId(peer.channelId, 'channel'), result.pts); } const message = result.messages[0]; diff --git a/src/components/common/ChatExtra.tsx b/src/components/common/ChatExtra.tsx index 79b2c106e..3dff47f25 100644 --- a/src/components/common/ChatExtra.tsx +++ b/src/components/common/ChatExtra.tsx @@ -13,7 +13,6 @@ import { TME_LINK_PREFIX } from '../../config'; import { getChatLink, getHasAdminRight, - getTopicLink, isChatChannel, isUserId, isUserRightBanned, @@ -25,6 +24,7 @@ import { selectCurrentMessageList, selectNotifyExceptions, selectNotifySettings, + selectTopicLink, selectUser, selectUserFullInfo, } from '../../global/selectors'; @@ -55,6 +55,7 @@ type StateProps = topicId?: number; description?: string; chatInviteLink?: string; + topicLink?: string; }; const runDebounced = debounce((cb) => cb(), 500, false); @@ -69,6 +70,7 @@ const ChatExtra: FC = ({ topicId, description, chatInviteLink, + topicLink, }) => { const { loadFullUser, @@ -118,10 +120,8 @@ const ChatExtra: FC = ({ return undefined; } - return isTopicInfo - ? getTopicLink(chat.id, activeChatUsernames?.[0].username, topicId) - : getChatLink(chat) || chatInviteLink; - }, [chat, isTopicInfo, activeChatUsernames, topicId, chatInviteLink]); + return isTopicInfo ? topicLink! : getChatLink(chat) || chatInviteLink; + }, [chat, isTopicInfo, topicLink, chatInviteLink]); const handleNotificationChange = useLastCallback(() => { setAreNotificationsEnabled((current) => { @@ -281,6 +281,8 @@ export default memo(withGlobal( || getHasAdminRight(chat, 'inviteUsers') ); + const topicLink = topicId ? selectTopicLink(global, chatOrUserId, topicId) : undefined; + return { phoneCodeList, chat, @@ -290,6 +292,7 @@ export default memo(withGlobal( topicId, chatInviteLink, description, + topicLink, }; }, )(ChatExtra)); diff --git a/src/components/middle/message/ContextMenuContainer.tsx b/src/components/middle/message/ContextMenuContainer.tsx index 25ef8029d..a975e654a 100644 --- a/src/components/middle/message/ContextMenuContainer.tsx +++ b/src/components/middle/message/ContextMenuContainer.tsx @@ -13,7 +13,6 @@ import type { IAlbum, IAnchorPosition } from '../../../types'; import { PREVIEW_AVATAR_COUNT, SERVICE_NOTIFICATIONS_USER_ID } from '../../../config'; import { areReactionsEmpty, - getChatMessageLink, getMessageVideo, isActionMessage, isChatChannel, @@ -36,6 +35,7 @@ import { selectIsPremiumPurchaseBlocked, selectIsReactionPickerOpen, selectMessageCustomEmojiSets, + selectMessageLink, selectMessageTranslations, selectRequestedChatTranslationLanguage, selectRequestedMessageTranslationLanguage, @@ -58,7 +58,6 @@ import MessageContextMenu from './MessageContextMenu'; export type OwnProps = { isOpen: boolean; - chatUsername?: string; message: ApiMessage; album?: IAlbum; anchor: IAnchorPosition; @@ -109,9 +108,9 @@ type StateProps = { enabledReactions?: ApiChatReactions; canScheduleUntilOnline?: boolean; maxUniqueReactions?: number; - threadId?: number; canPlayAnimatedEmojis?: boolean; isReactionPickerOpen?: boolean; + messageLink?: string; }; const ContextMenuContainer: FC = ({ @@ -119,7 +118,6 @@ const ContextMenuContainer: FC = ({ topReactions, isOpen, messageListType, - chatUsername, message, customEmojiSetsInfo, customEmojiSets, @@ -162,10 +160,10 @@ const ContextMenuContainer: FC = ({ canTranslate, canShowOriginal, canSelectLanguage, - threadId, + isReactionPickerOpen, + messageLink, onClose, onCloseAnimationEnd, - isReactionPickerOpen, }) => { const { openChat, @@ -403,7 +401,7 @@ const ContextMenuContainer: FC = ({ }); const handleCopyLink = useLastCallback(() => { - copyTextToClipboard(getChatMessageLink(message.chatId, chatUsername, threadId, message.id)); + copyTextToClipboard(messageLink!); closeMenu(); }); @@ -641,6 +639,7 @@ export default memo(withGlobal( : undefined; const canTranslate = !hasTranslation && selectCanTranslateMessage(global, message, detectedLanguage); const isChatTranslated = selectRequestedChatTranslationLanguage(global, message.chatId); + const messageLink = selectMessageLink(global, message.chatId, threadId, message.id); return { availableReactions: global.availableReactions, @@ -677,12 +676,12 @@ export default memo(withGlobal( customEmojiSetsInfo, customEmojiSets, canScheduleUntilOnline: selectCanScheduleUntilOnline(global, message.chatId), - threadId, canTranslate, canShowOriginal: hasTranslation && !isChatTranslated, canSelectLanguage: hasTranslation && !isChatTranslated, canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global), isReactionPickerOpen: selectIsReactionPickerOpen(global), + messageLink, }; }, )(ContextMenuContainer)); diff --git a/src/components/middle/message/Message.tsx b/src/components/middle/message/Message.tsx index 0d7536ff5..f41f99f5e 100644 --- a/src/components/middle/message/Message.tsx +++ b/src/components/middle/message/Message.tsx @@ -15,7 +15,6 @@ import type { ApiTopic, ApiTypeStory, ApiUser, - ApiUsername, } from '../../../api/types'; import type { ActiveEmojiInteraction, @@ -200,7 +199,6 @@ type OwnProps = type StateProps = { theme: ISettings['theme']; forceSenderName?: boolean; - chatUsernames?: ApiUsername[]; sender?: ApiUser | ApiChat; canShowSender: boolean; originSender?: ApiUser | ApiChat; @@ -288,7 +286,6 @@ const RESIZE_ANIMATION_DURATION = 400; const Message: FC = ({ message, - chatUsernames, observeIntersectionForBottom, observeIntersectionForLoading, observeIntersectionForPlaying, @@ -1278,7 +1275,6 @@ const Message: FC = ({ } const forwardAuthor = isGroup && asForwarded ? message.postAuthorTitle : undefined; - const chatUsername = useMemo(() => chatUsernames?.find((c) => c.isActive), [chatUsernames]); return (
= ({ targetHref={contextMenuTarget?.matches('a[href]') ? (contextMenuTarget as HTMLAnchorElement).href : undefined} message={message} album={album} - chatUsername={chatUsername?.username} messageListType={messageListType} onClose={handleContextMenuClose} onCloseAnimationEnd={handleContextMenuHide} @@ -1439,7 +1434,6 @@ export default memo(withGlobal( const isRepliesChat = isChatWithRepliesBot(chatId); const isChannel = chat && isChatChannel(chat); const isGroup = chat && isChatGroup(chat); - const chatUsernames = chat?.usernames; const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined; const webPageStoryData = message.content.webPage?.story; const webPageStory = webPageStoryData @@ -1529,7 +1523,6 @@ export default memo(withGlobal( return { theme: selectTheme(global), - chatUsernames, forceSenderName, canShowSender, originSender, diff --git a/src/config.ts b/src/config.ts index 20f64c08c..2a4616b72 100644 --- a/src/config.ts +++ b/src/config.ts @@ -285,7 +285,7 @@ export const RESTRICTED_EMOJI_SET_ID = '7173162320003080'; export const DEFAULT_GIF_SEARCH_BOT_USERNAME = 'gif'; export const ALL_FOLDER_ID = 0; export const ARCHIVED_FOLDER_ID = 1; -export const DELETED_COMMENTS_CHANNEL_ID = '-777'; +export const DELETED_COMMENTS_CHANNEL_ID = '-100777'; export const MAX_MEDIA_FILES_FOR_ALBUM = 10; export const MAX_ACTIVE_PINNED_CHATS = 5; export const SCHEDULED_WHEN_ONLINE = 0x7FFFFFFE; diff --git a/src/global/actions/api/chats.ts b/src/global/actions/api/chats.ts index cb7ed04b7..555247675 100644 --- a/src/global/actions/api/chats.ts +++ b/src/global/actions/api/chats.ts @@ -1065,7 +1065,7 @@ addActionHandler('openTelegramLink', (global, actions, payload): ActionReturnTyp tabId, }); } else if (part1 === 'c' && chatOrChannelPostId && messageId) { - const chatId = `-${chatOrChannelPostId}`; + const chatId = `-100${chatOrChannelPostId}`; const chat = selectChat(global, chatId); if (!chat) { showNotification({ message: 'Chat does not exist', tabId }); @@ -1073,7 +1073,7 @@ addActionHandler('openTelegramLink', (global, actions, payload): ActionReturnTyp } focusMessage({ - chatId, + chatId: chat.id, messageId, tabId, }); diff --git a/src/global/helpers/chats.ts b/src/global/helpers/chats.ts index 37692d53e..50774ca54 100644 --- a/src/global/helpers/chats.ts +++ b/src/global/helpers/chats.ts @@ -18,7 +18,7 @@ import { import { formatDateToString, formatTime } from '../../util/dateFormat'; import { orderBy } from '../../util/iteratees'; import { prepareSearchWordsForNeedle } from '../../util/searchWords'; -import { getUserFirstOrLastName } from './users'; +import { getMainUsername, getUserFirstOrLastName } from './users'; const FOREVER_BANNED_DATE = Date.now() / 1000 + 31622400; // 366 days @@ -26,11 +26,6 @@ const VERIFIED_PRIORITY_BASE = 3e9; const PINNED_PRIORITY_BASE = 3e8; export function isUserId(entityId: string) { - // Workaround for old-fashioned IDs stored locally - if (typeof entityId === 'number') { - return entityId > 0; - } - return !entityId.startsWith('-'); } @@ -87,20 +82,9 @@ export function getChatTitle(lang: LangFn, chat: ApiChat, isSelf = false) { } export function getChatLink(chat: ApiChat) { - const activeUsername = chat.usernames?.find((u) => u.isActive); + const activeUsername = getMainUsername(chat); - return activeUsername ? `${TME_LINK_PREFIX}${activeUsername.username}` : undefined; -} - -export function getChatMessageLink(chatId: string, chatUsername?: string, threadId?: number, messageId?: number) { - const chatPart = chatUsername || `c/${chatId.replace('-', '')}`; - const threadPart = threadId && threadId !== MAIN_THREAD_ID ? `/${threadId}` : ''; - const messagePart = messageId ? `/${messageId}` : ''; - return `${TME_LINK_PREFIX}${chatPart}${threadPart}${messagePart}`; -} - -export function getTopicLink(chatId: string, chatUsername?: string, topicId?: number) { - return getChatMessageLink(chatId, chatUsername, topicId); + return activeUsername ? `${TME_LINK_PREFIX}${activeUsername}` : undefined; } export function getChatAvatarHash( diff --git a/src/global/selectors/messages.ts b/src/global/selectors/messages.ts index 427a1a810..844a35f22 100644 --- a/src/global/selectors/messages.ts +++ b/src/global/selectors/messages.ts @@ -13,7 +13,7 @@ import type { import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types'; import { - GENERAL_TOPIC_ID, REPLIES_USER_ID, SERVICE_NOTIFICATIONS_USER_ID, + GENERAL_TOPIC_ID, REPLIES_USER_ID, SERVICE_NOTIFICATIONS_USER_ID, TME_LINK_PREFIX, } from '../../config'; import { getCurrentTabId } from '../../util/establishMultitabRole'; import { findLast } from '../../util/iteratees'; @@ -25,6 +25,7 @@ import { getAllowedAttachmentOptions, getCanPostInChat, getHasAdminRight, + getMainUsername, getMessageAudio, getMessageDocument, getMessageOriginalId, @@ -1353,3 +1354,28 @@ export function selectCanTranslateMessage( return IS_TRANSLATION_SUPPORTED && isTranslationEnabled && canTranslateLanguage && isTranslatable && !chatRequestedLanguage; } + +export function selectMessageLink( + global: T, chatId: string, threadId?: number, messageId?: number, +) { + const chat = selectChat(global, chatId); + if (!chat) { + return undefined; + } + + const chatUsername = getMainUsername(chat); + + const isChannelId = isChatChannel(chat) || isChatSuperGroup(chat); + const normalizedId = isChannelId ? chatId.replace('-100', '') : chatId.replace('-', ''); + + const chatPart = chatUsername || `c/${normalizedId}`; + const threadPart = threadId && threadId !== MAIN_THREAD_ID ? `/${threadId}` : ''; + const messagePart = messageId ? `/${messageId}` : ''; + return `${TME_LINK_PREFIX}${chatPart}${threadPart}${messagePart}`; +} + +export function selectTopicLink( + global: T, chatId: string, topicId?: number, +) { + return selectMessageLink(global, chatId, topicId); +}