Implement TDLib-like channel prefix (#3858)

This commit is contained in:
Alexander Zinchuk 2023-09-25 13:00:10 +02:00
parent 3cd9192c1d
commit f8c021b043
11 changed files with 80 additions and 67 deletions

View File

@ -220,9 +220,14 @@ export function buildApiChatFromPreview(
if (preview instanceof GramJs.ChatEmpty || preview instanceof GramJs.UserEmpty) { if (preview instanceof GramJs.ChatEmpty || preview instanceof GramJs.UserEmpty) {
return undefined; return undefined;
} }
const id = buildApiPeerId(
preview.id,
preview instanceof GramJs.User ? 'user'
: (preview instanceof GramJs.Chat || preview instanceof GramJs.ChatForbidden) ? 'chat' : 'channel',
);
return { return {
id: buildApiPeerId(preview.id, preview instanceof GramJs.User ? 'user' : 'chat'), id,
type: getApiChatTypeFromPeerEntity(preview), type: getApiChatTypeFromPeerEntity(preview),
title: preview instanceof GramJs.User ? getUserName(preview) : preview.title, title: preview instanceof GramJs.User ? getUserName(preview) : preview.title,
...buildApiChatFieldsFromPeerEntity(preview, isSupport), ...buildApiChatFieldsFromPeerEntity(preview, isSupport),

View File

@ -15,7 +15,15 @@ export function isPeerChannel(peer: GramJs.TypePeer | GramJs.TypeInputPeer): pee
} }
export function buildApiPeerId(id: BigInt.BigInteger, type: 'user' | 'chat' | 'channel') { 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) { export function getApiChatIdFromMtpPeer(peer: GramJs.TypePeer | GramJs.TypeInputPeer) {

View File

@ -37,19 +37,17 @@ import { pick } from '../../../util/iteratees';
import { deserializeBytes } from '../helpers'; import { deserializeBytes } from '../helpers';
import localDb from '../localDb'; 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) { 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; if (id.length === CHANNEL_ID_MIN_LENGTH && id.startsWith('-4')) return false;
return id.length >= CHANNEL_ID_MIN_LENGTH; return id.length >= CHANNEL_ID_MIN_LENGTH;
} }
export function getEntityTypeById(chatOrUserId: string) { export function getEntityTypeById(chatOrUserId: string) {
if (typeof chatOrUserId === 'number') {
return getEntityTypeByDeprecatedId(chatOrUserId);
}
if (!chatOrUserId.startsWith('-')) { if (!chatOrUserId.startsWith('-')) {
return 'user'; return 'user';
} else if (checkIfChannelId(chatOrUserId)) { } 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 { export function buildPeer(chatOrUserId: string): GramJs.TypePeer {
const type = getEntityTypeById(chatOrUserId); const type = getEntityTypeById(chatOrUserId);
@ -546,12 +533,20 @@ export function buildInputThemeParams(params: ApiThemeParameters) {
} }
export function buildMtpPeerId(id: string, type: 'user' | 'chat' | 'channel') { export function buildMtpPeerId(id: string, type: 'user' | 'chat' | 'channel') {
// Workaround for old-fashioned IDs stored locally if (type === 'user') {
if (typeof id === 'number') { return BigInt(id);
return BigInt(Math.abs(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<ApiGroupCall>) { export function buildInputGroupCall(groupCall: Partial<ApiGroupCall>) {

View File

@ -480,7 +480,7 @@ export async function repairFileReference({
if (!result || result instanceof GramJs.messages.MessagesNotModified) return false; if (!result || result instanceof GramJs.messages.MessagesNotModified) return false;
if (peer && 'pts' in result) { if (peer && 'pts' in result) {
updateChannelState(peer.channelId.toString(), result.pts); updateChannelState(buildApiPeerId(peer.channelId, 'channel'), result.pts);
} }
const message = result.messages[0]; const message = result.messages[0];

View File

@ -13,7 +13,6 @@ import { TME_LINK_PREFIX } from '../../config';
import { import {
getChatLink, getChatLink,
getHasAdminRight, getHasAdminRight,
getTopicLink,
isChatChannel, isChatChannel,
isUserId, isUserId,
isUserRightBanned, isUserRightBanned,
@ -25,6 +24,7 @@ import {
selectCurrentMessageList, selectCurrentMessageList,
selectNotifyExceptions, selectNotifyExceptions,
selectNotifySettings, selectNotifySettings,
selectTopicLink,
selectUser, selectUser,
selectUserFullInfo, selectUserFullInfo,
} from '../../global/selectors'; } from '../../global/selectors';
@ -55,6 +55,7 @@ type StateProps =
topicId?: number; topicId?: number;
description?: string; description?: string;
chatInviteLink?: string; chatInviteLink?: string;
topicLink?: string;
}; };
const runDebounced = debounce((cb) => cb(), 500, false); const runDebounced = debounce((cb) => cb(), 500, false);
@ -69,6 +70,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
topicId, topicId,
description, description,
chatInviteLink, chatInviteLink,
topicLink,
}) => { }) => {
const { const {
loadFullUser, loadFullUser,
@ -118,10 +120,8 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
return undefined; return undefined;
} }
return isTopicInfo return isTopicInfo ? topicLink! : getChatLink(chat) || chatInviteLink;
? getTopicLink(chat.id, activeChatUsernames?.[0].username, topicId) }, [chat, isTopicInfo, topicLink, chatInviteLink]);
: getChatLink(chat) || chatInviteLink;
}, [chat, isTopicInfo, activeChatUsernames, topicId, chatInviteLink]);
const handleNotificationChange = useLastCallback(() => { const handleNotificationChange = useLastCallback(() => {
setAreNotificationsEnabled((current) => { setAreNotificationsEnabled((current) => {
@ -281,6 +281,8 @@ export default memo(withGlobal<OwnProps>(
|| getHasAdminRight(chat, 'inviteUsers') || getHasAdminRight(chat, 'inviteUsers')
); );
const topicLink = topicId ? selectTopicLink(global, chatOrUserId, topicId) : undefined;
return { return {
phoneCodeList, phoneCodeList,
chat, chat,
@ -290,6 +292,7 @@ export default memo(withGlobal<OwnProps>(
topicId, topicId,
chatInviteLink, chatInviteLink,
description, description,
topicLink,
}; };
}, },
)(ChatExtra)); )(ChatExtra));

View File

@ -13,7 +13,6 @@ import type { IAlbum, IAnchorPosition } from '../../../types';
import { PREVIEW_AVATAR_COUNT, SERVICE_NOTIFICATIONS_USER_ID } from '../../../config'; import { PREVIEW_AVATAR_COUNT, SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
import { import {
areReactionsEmpty, areReactionsEmpty,
getChatMessageLink,
getMessageVideo, getMessageVideo,
isActionMessage, isActionMessage,
isChatChannel, isChatChannel,
@ -36,6 +35,7 @@ import {
selectIsPremiumPurchaseBlocked, selectIsPremiumPurchaseBlocked,
selectIsReactionPickerOpen, selectIsReactionPickerOpen,
selectMessageCustomEmojiSets, selectMessageCustomEmojiSets,
selectMessageLink,
selectMessageTranslations, selectMessageTranslations,
selectRequestedChatTranslationLanguage, selectRequestedChatTranslationLanguage,
selectRequestedMessageTranslationLanguage, selectRequestedMessageTranslationLanguage,
@ -58,7 +58,6 @@ import MessageContextMenu from './MessageContextMenu';
export type OwnProps = { export type OwnProps = {
isOpen: boolean; isOpen: boolean;
chatUsername?: string;
message: ApiMessage; message: ApiMessage;
album?: IAlbum; album?: IAlbum;
anchor: IAnchorPosition; anchor: IAnchorPosition;
@ -109,9 +108,9 @@ type StateProps = {
enabledReactions?: ApiChatReactions; enabledReactions?: ApiChatReactions;
canScheduleUntilOnline?: boolean; canScheduleUntilOnline?: boolean;
maxUniqueReactions?: number; maxUniqueReactions?: number;
threadId?: number;
canPlayAnimatedEmojis?: boolean; canPlayAnimatedEmojis?: boolean;
isReactionPickerOpen?: boolean; isReactionPickerOpen?: boolean;
messageLink?: string;
}; };
const ContextMenuContainer: FC<OwnProps & StateProps> = ({ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
@ -119,7 +118,6 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
topReactions, topReactions,
isOpen, isOpen,
messageListType, messageListType,
chatUsername,
message, message,
customEmojiSetsInfo, customEmojiSetsInfo,
customEmojiSets, customEmojiSets,
@ -162,10 +160,10 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
canTranslate, canTranslate,
canShowOriginal, canShowOriginal,
canSelectLanguage, canSelectLanguage,
threadId, isReactionPickerOpen,
messageLink,
onClose, onClose,
onCloseAnimationEnd, onCloseAnimationEnd,
isReactionPickerOpen,
}) => { }) => {
const { const {
openChat, openChat,
@ -403,7 +401,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
}); });
const handleCopyLink = useLastCallback(() => { const handleCopyLink = useLastCallback(() => {
copyTextToClipboard(getChatMessageLink(message.chatId, chatUsername, threadId, message.id)); copyTextToClipboard(messageLink!);
closeMenu(); closeMenu();
}); });
@ -641,6 +639,7 @@ export default memo(withGlobal<OwnProps>(
: undefined; : undefined;
const canTranslate = !hasTranslation && selectCanTranslateMessage(global, message, detectedLanguage); const canTranslate = !hasTranslation && selectCanTranslateMessage(global, message, detectedLanguage);
const isChatTranslated = selectRequestedChatTranslationLanguage(global, message.chatId); const isChatTranslated = selectRequestedChatTranslationLanguage(global, message.chatId);
const messageLink = selectMessageLink(global, message.chatId, threadId, message.id);
return { return {
availableReactions: global.availableReactions, availableReactions: global.availableReactions,
@ -677,12 +676,12 @@ export default memo(withGlobal<OwnProps>(
customEmojiSetsInfo, customEmojiSetsInfo,
customEmojiSets, customEmojiSets,
canScheduleUntilOnline: selectCanScheduleUntilOnline(global, message.chatId), canScheduleUntilOnline: selectCanScheduleUntilOnline(global, message.chatId),
threadId,
canTranslate, canTranslate,
canShowOriginal: hasTranslation && !isChatTranslated, canShowOriginal: hasTranslation && !isChatTranslated,
canSelectLanguage: hasTranslation && !isChatTranslated, canSelectLanguage: hasTranslation && !isChatTranslated,
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global), canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
isReactionPickerOpen: selectIsReactionPickerOpen(global), isReactionPickerOpen: selectIsReactionPickerOpen(global),
messageLink,
}; };
}, },
)(ContextMenuContainer)); )(ContextMenuContainer));

View File

@ -15,7 +15,6 @@ import type {
ApiTopic, ApiTopic,
ApiTypeStory, ApiTypeStory,
ApiUser, ApiUser,
ApiUsername,
} from '../../../api/types'; } from '../../../api/types';
import type { import type {
ActiveEmojiInteraction, ActiveEmojiInteraction,
@ -200,7 +199,6 @@ type OwnProps =
type StateProps = { type StateProps = {
theme: ISettings['theme']; theme: ISettings['theme'];
forceSenderName?: boolean; forceSenderName?: boolean;
chatUsernames?: ApiUsername[];
sender?: ApiUser | ApiChat; sender?: ApiUser | ApiChat;
canShowSender: boolean; canShowSender: boolean;
originSender?: ApiUser | ApiChat; originSender?: ApiUser | ApiChat;
@ -288,7 +286,6 @@ const RESIZE_ANIMATION_DURATION = 400;
const Message: FC<OwnProps & StateProps> = ({ const Message: FC<OwnProps & StateProps> = ({
message, message,
chatUsernames,
observeIntersectionForBottom, observeIntersectionForBottom,
observeIntersectionForLoading, observeIntersectionForLoading,
observeIntersectionForPlaying, observeIntersectionForPlaying,
@ -1278,7 +1275,6 @@ const Message: FC<OwnProps & StateProps> = ({
} }
const forwardAuthor = isGroup && asForwarded ? message.postAuthorTitle : undefined; const forwardAuthor = isGroup && asForwarded ? message.postAuthorTitle : undefined;
const chatUsername = useMemo(() => chatUsernames?.find((c) => c.isActive), [chatUsernames]);
return ( return (
<div <div
@ -1384,7 +1380,6 @@ const Message: FC<OwnProps & StateProps> = ({
targetHref={contextMenuTarget?.matches('a[href]') ? (contextMenuTarget as HTMLAnchorElement).href : undefined} targetHref={contextMenuTarget?.matches('a[href]') ? (contextMenuTarget as HTMLAnchorElement).href : undefined}
message={message} message={message}
album={album} album={album}
chatUsername={chatUsername?.username}
messageListType={messageListType} messageListType={messageListType}
onClose={handleContextMenuClose} onClose={handleContextMenuClose}
onCloseAnimationEnd={handleContextMenuHide} onCloseAnimationEnd={handleContextMenuHide}
@ -1439,7 +1434,6 @@ export default memo(withGlobal<OwnProps>(
const isRepliesChat = isChatWithRepliesBot(chatId); const isRepliesChat = isChatWithRepliesBot(chatId);
const isChannel = chat && isChatChannel(chat); const isChannel = chat && isChatChannel(chat);
const isGroup = chat && isChatGroup(chat); const isGroup = chat && isChatGroup(chat);
const chatUsernames = chat?.usernames;
const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined; const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined;
const webPageStoryData = message.content.webPage?.story; const webPageStoryData = message.content.webPage?.story;
const webPageStory = webPageStoryData const webPageStory = webPageStoryData
@ -1529,7 +1523,6 @@ export default memo(withGlobal<OwnProps>(
return { return {
theme: selectTheme(global), theme: selectTheme(global),
chatUsernames,
forceSenderName, forceSenderName,
canShowSender, canShowSender,
originSender, originSender,

View File

@ -285,7 +285,7 @@ export const RESTRICTED_EMOJI_SET_ID = '7173162320003080';
export const DEFAULT_GIF_SEARCH_BOT_USERNAME = 'gif'; export const DEFAULT_GIF_SEARCH_BOT_USERNAME = 'gif';
export const ALL_FOLDER_ID = 0; export const ALL_FOLDER_ID = 0;
export const ARCHIVED_FOLDER_ID = 1; 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_MEDIA_FILES_FOR_ALBUM = 10;
export const MAX_ACTIVE_PINNED_CHATS = 5; export const MAX_ACTIVE_PINNED_CHATS = 5;
export const SCHEDULED_WHEN_ONLINE = 0x7FFFFFFE; export const SCHEDULED_WHEN_ONLINE = 0x7FFFFFFE;

View File

@ -1065,7 +1065,7 @@ addActionHandler('openTelegramLink', (global, actions, payload): ActionReturnTyp
tabId, tabId,
}); });
} else if (part1 === 'c' && chatOrChannelPostId && messageId) { } else if (part1 === 'c' && chatOrChannelPostId && messageId) {
const chatId = `-${chatOrChannelPostId}`; const chatId = `-100${chatOrChannelPostId}`;
const chat = selectChat(global, chatId); const chat = selectChat(global, chatId);
if (!chat) { if (!chat) {
showNotification({ message: 'Chat does not exist', tabId }); showNotification({ message: 'Chat does not exist', tabId });
@ -1073,7 +1073,7 @@ addActionHandler('openTelegramLink', (global, actions, payload): ActionReturnTyp
} }
focusMessage({ focusMessage({
chatId, chatId: chat.id,
messageId, messageId,
tabId, tabId,
}); });

View File

@ -18,7 +18,7 @@ import {
import { formatDateToString, formatTime } from '../../util/dateFormat'; import { formatDateToString, formatTime } from '../../util/dateFormat';
import { orderBy } from '../../util/iteratees'; import { orderBy } from '../../util/iteratees';
import { prepareSearchWordsForNeedle } from '../../util/searchWords'; import { prepareSearchWordsForNeedle } from '../../util/searchWords';
import { getUserFirstOrLastName } from './users'; import { getMainUsername, getUserFirstOrLastName } from './users';
const FOREVER_BANNED_DATE = Date.now() / 1000 + 31622400; // 366 days const FOREVER_BANNED_DATE = Date.now() / 1000 + 31622400; // 366 days
@ -26,11 +26,6 @@ const VERIFIED_PRIORITY_BASE = 3e9;
const PINNED_PRIORITY_BASE = 3e8; const PINNED_PRIORITY_BASE = 3e8;
export function isUserId(entityId: string) { export function isUserId(entityId: string) {
// Workaround for old-fashioned IDs stored locally
if (typeof entityId === 'number') {
return entityId > 0;
}
return !entityId.startsWith('-'); return !entityId.startsWith('-');
} }
@ -87,20 +82,9 @@ export function getChatTitle(lang: LangFn, chat: ApiChat, isSelf = false) {
} }
export function getChatLink(chat: ApiChat) { 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; return activeUsername ? `${TME_LINK_PREFIX}${activeUsername}` : 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);
} }
export function getChatAvatarHash( export function getChatAvatarHash(

View File

@ -13,7 +13,7 @@ import type {
import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types'; import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types';
import { 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'; } from '../../config';
import { getCurrentTabId } from '../../util/establishMultitabRole'; import { getCurrentTabId } from '../../util/establishMultitabRole';
import { findLast } from '../../util/iteratees'; import { findLast } from '../../util/iteratees';
@ -25,6 +25,7 @@ import {
getAllowedAttachmentOptions, getAllowedAttachmentOptions,
getCanPostInChat, getCanPostInChat,
getHasAdminRight, getHasAdminRight,
getMainUsername,
getMessageAudio, getMessageAudio,
getMessageDocument, getMessageDocument,
getMessageOriginalId, getMessageOriginalId,
@ -1353,3 +1354,28 @@ export function selectCanTranslateMessage<T extends GlobalState>(
return IS_TRANSLATION_SUPPORTED && isTranslationEnabled && canTranslateLanguage && isTranslatable return IS_TRANSLATION_SUPPORTED && isTranslationEnabled && canTranslateLanguage && isTranslatable
&& !chatRequestedLanguage; && !chatRequestedLanguage;
} }
export function selectMessageLink<T extends GlobalState>(
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<T extends GlobalState>(
global: T, chatId: string, topicId?: number,
) {
return selectMessageLink(global, chatId, topicId);
}