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) {
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),

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') {
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) {

View File

@ -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<ApiGroupCall>) {

View File

@ -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];

View File

@ -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<OwnProps & StateProps> = ({
topicId,
description,
chatInviteLink,
topicLink,
}) => {
const {
loadFullUser,
@ -118,10 +120,8 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
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<OwnProps>(
|| getHasAdminRight(chat, 'inviteUsers')
);
const topicLink = topicId ? selectTopicLink(global, chatOrUserId, topicId) : undefined;
return {
phoneCodeList,
chat,
@ -290,6 +292,7 @@ export default memo(withGlobal<OwnProps>(
topicId,
chatInviteLink,
description,
topicLink,
};
},
)(ChatExtra));

View File

@ -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<OwnProps & StateProps> = ({
@ -119,7 +118,6 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
topReactions,
isOpen,
messageListType,
chatUsername,
message,
customEmojiSetsInfo,
customEmojiSets,
@ -162,10 +160,10 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
canTranslate,
canShowOriginal,
canSelectLanguage,
threadId,
isReactionPickerOpen,
messageLink,
onClose,
onCloseAnimationEnd,
isReactionPickerOpen,
}) => {
const {
openChat,
@ -403,7 +401,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
});
const handleCopyLink = useLastCallback(() => {
copyTextToClipboard(getChatMessageLink(message.chatId, chatUsername, threadId, message.id));
copyTextToClipboard(messageLink!);
closeMenu();
});
@ -641,6 +639,7 @@ export default memo(withGlobal<OwnProps>(
: 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<OwnProps>(
customEmojiSetsInfo,
customEmojiSets,
canScheduleUntilOnline: selectCanScheduleUntilOnline(global, message.chatId),
threadId,
canTranslate,
canShowOriginal: hasTranslation && !isChatTranslated,
canSelectLanguage: hasTranslation && !isChatTranslated,
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
isReactionPickerOpen: selectIsReactionPickerOpen(global),
messageLink,
};
},
)(ContextMenuContainer));

View File

@ -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<OwnProps & StateProps> = ({
message,
chatUsernames,
observeIntersectionForBottom,
observeIntersectionForLoading,
observeIntersectionForPlaying,
@ -1278,7 +1275,6 @@ const Message: FC<OwnProps & StateProps> = ({
}
const forwardAuthor = isGroup && asForwarded ? message.postAuthorTitle : undefined;
const chatUsername = useMemo(() => chatUsernames?.find((c) => c.isActive), [chatUsernames]);
return (
<div
@ -1384,7 +1380,6 @@ const Message: FC<OwnProps & StateProps> = ({
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<OwnProps>(
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<OwnProps>(
return {
theme: selectTheme(global),
chatUsernames,
forceSenderName,
canShowSender,
originSender,

View File

@ -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;

View File

@ -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,
});

View File

@ -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(

View File

@ -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<T extends GlobalState>(
return IS_TRANSLATION_SUPPORTED && isTranslationEnabled && canTranslateLanguage && isTranslatable
&& !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);
}