import type { ChatListType } from '../../types'; import type { GlobalState, TabArgs } from '../types'; import { type ApiChat, type ApiChatFullInfo, type ApiChatType, } from '../../api/types'; import { ALL_FOLDER_ID, ARCHIVED_FOLDER_ID, MEMBERS_LOAD_SLICE, SAVED_FOLDER_ID, SERVICE_NOTIFICATIONS_USER_ID, } from '../../config'; import { IS_TRANSLATION_SUPPORTED } from '../../util/browser/windowEnvironment'; import { isUserId } from '../../util/entities/ids'; import { getCurrentTabId } from '../../util/establishMultitabRole'; import { getHasAdminRight, getPrivateChatUserId, isChatChannel, isChatPublic, isChatSuperGroup, isHistoryClearMessage, isUserBot, isUserOnline, } from '../helpers'; import { selectActiveRestrictionReasons } from './messages'; import { selectTabState } from './tabs'; import { selectBot, selectIsCurrentUserPremium, selectUser, selectUserFullInfo, } from './users'; export function selectChat(global: T, chatId: string): ApiChat | undefined { return global.chats.byId[chatId]; } export function selectChatFullInfo(global: T, chatId: string): ApiChatFullInfo | undefined { return global.chats.fullInfoById[chatId]; } export function selectPeerFullInfo(global: T, peerId: string) { if (isUserId(peerId)) return selectUserFullInfo(global, peerId); return selectChatFullInfo(global, peerId); } export function selectChatListLoadingParameters( global: T, listType: ChatListType, ) { return global.chats.loadingParameters[listType]; } export function selectChatUser(global: T, chat: ApiChat) { const userId = getPrivateChatUserId(chat); if (!userId) { return false; } return selectUser(global, userId); } export function selectIsChatWithSelf(global: T, chatId: string) { return chatId === global.currentUserId; } export function selectIsChatWithBot(global: T, chat: ApiChat) { const user = selectChatUser(global, chat); return user && isUserBot(user); } export function selectSupportChat(global: T) { return Object.values(global.chats.byId).find(({ isSupport }: ApiChat) => isSupport); } export function selectChatOnlineCount(global: T, chat: ApiChat) { const fullInfo = selectChatFullInfo(global, chat.id); if (isUserId(chat.id) || isChatChannel(chat) || !fullInfo) { return undefined; } if (!fullInfo.members || fullInfo.members.length === MEMBERS_LOAD_SLICE) { return fullInfo.onlineCount; } return fullInfo.members.reduce((onlineCount, { userId }) => { if ( !selectIsChatWithSelf(global, userId) && global.users.byId[userId] && isUserOnline(global.users.byId[userId], global.users.statusesById[userId]) ) { return onlineCount + 1; } return onlineCount; }, 0); } export function selectIsTrustedBot(global: T, botId: string) { return global.trustedBotIds.includes(botId); } export function selectChatType(global: T, chatId: string): ApiChatType | undefined { const bot = selectBot(global, chatId); if (bot) { return 'bots'; } const user = selectUser(global, chatId); if (user) { return 'users'; } const chat = selectChat(global, chatId); if (!chat) return undefined; if (isChatChannel(chat)) { return 'channels'; } return 'chats'; } export function selectIsChatBotNotStarted(global: T, chatId: string) { const bot = selectBot(global, chatId); if (!bot) { return false; } const lastMessage = selectChatLastMessage(global, chatId); if (lastMessage && isHistoryClearMessage(lastMessage)) { return true; } return Boolean(!lastMessage); } export function selectAreActiveChatsLoaded(global: T): boolean { return Boolean(global.chats.listIds.active); } export function selectIsChatListed( global: T, chatId: string, type?: ChatListType, ): boolean { const { listIds } = global.chats; if (type) { const targetList = listIds[type]; return Boolean(targetList && targetList.includes(chatId)); } return Object.values(listIds).some((list) => list && list.includes(chatId)); } export function selectChatListType( global: T, chatId: string, ): 'active' | 'archived' | undefined { const chat = selectChat(global, chatId); if (!chat || !selectIsChatListed(global, chatId)) { return undefined; } return chat.folderId === ARCHIVED_FOLDER_ID ? 'archived' : 'active'; } export function selectChatFolder(global: T, folderId: number) { return global.chatFolders.byId[folderId]; } export function selectTotalChatCount(global: T, listType: 'active' | 'archived'): number { const { totalCount } = global.chats; const allChatsCount = totalCount.all; const archivedChatsCount = totalCount.archived || 0; if (listType === 'archived') { return archivedChatsCount; } return allChatsCount ? allChatsCount - archivedChatsCount : 0; } export function selectIsChatPinned( global: T, chatId: string, folderId = ALL_FOLDER_ID, ): boolean { const { active, archived, saved } = global.chats.orderedPinnedIds; if (folderId === ALL_FOLDER_ID) { return Boolean(active?.includes(chatId)); } if (folderId === ARCHIVED_FOLDER_ID) { return Boolean(archived?.includes(chatId)); } if (folderId === SAVED_FOLDER_ID) { return Boolean(saved?.includes(chatId)); } const { byId: chatFoldersById } = global.chatFolders; const { pinnedChatIds } = chatFoldersById[folderId] || {}; return Boolean(pinnedChatIds?.includes(chatId)); } // Slow, not to be used in `withGlobal` export function selectChatByUsername(global: T, username: string) { const usernameLowered = username.toLowerCase(); return Object.values(global.chats.byId).find( (chat) => chat.usernames?.some((c) => c.username.toLowerCase() === usernameLowered), ); } export function selectIsServiceChatReady(global: T) { return Boolean(selectChat(global, SERVICE_NOTIFICATIONS_USER_ID)); } export function selectSendAs(global: T, chatId: string) { const chat = selectChat(global, chatId); if (!chat) return undefined; const id = selectChatFullInfo(global, chatId)?.sendAsId; if (!id) return undefined; return selectUser(global, id) || selectChat(global, id); } export function selectRequestedDraft( global: T, chatId: string, ...[tabId = getCurrentTabId()]: TabArgs ) { const { requestedDraft } = selectTabState(global, tabId); if (requestedDraft?.chatId === chatId && !requestedDraft.files?.length) { return requestedDraft.text; } return undefined; } export function selectRequestedDraftFiles( global: T, chatId: string, ...[tabId = getCurrentTabId()]: TabArgs ) { const { requestedDraft } = selectTabState(global, tabId); if (requestedDraft?.chatId === chatId) { return requestedDraft.files; } return undefined; } export function filterChatIdsByType( global: T, chatIds: string[], filter: readonly ApiChatType[], ) { return chatIds.filter((id) => { const type = selectChatType(global, id); if (!type) { return false; } return filter.includes(type); }); } export function selectCanInviteToChat(global: T, chatId: string) { const chat = selectChat(global, chatId); if (!chat) return false; // 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)) ? ( chat.isCreator || getHasAdminRight(chat, 'inviteUsers') || (isChatPublic(chat) && !chat.isJoinRequest) ) : (chat.isCreator || getHasAdminRight(chat, 'inviteUsers')))); } export function selectCanShareFolder(global: T, folderId: number) { const folder = selectChatFolder(global, folderId); if (!folder) return false; const { bots, groups, channels, contacts, nonContacts, includedChatIds, pinnedChatIds, excludeArchived, excludeMuted, excludeRead, excludedChatIds, } = folder; return !bots && !groups && !channels && !contacts && !nonContacts && !excludeArchived && !excludeMuted && !excludeRead && !excludedChatIds?.length && (pinnedChatIds?.length || includedChatIds.length) && folder.includedChatIds.concat(folder.pinnedChatIds || []).some((chatId) => { return selectCanInviteToChat(global, chatId); }); } export function selectShouldDetectChatLanguage( global: T, chatId: string, ) { const chat = selectChat(global, chatId); if (!chat) return false; if (chat.hasAutoTranslation) return true; const { canTranslateChats } = global.settings.byKey; const isPremium = selectIsCurrentUserPremium(global); const isSavedMessages = selectIsChatWithSelf(global, chatId); return IS_TRANSLATION_SUPPORTED && canTranslateChats && isPremium && !isSavedMessages; } export function selectCanTranslateChat( global: T, chatId: string, ...[tabId = getCurrentTabId()]: TabArgs ) { const chat = selectChat(global, chatId); if (!chat) return false; const requestedTranslation = selectRequestedChatTranslationLanguage(global, chatId, tabId); if (requestedTranslation) return true; // Prevent translation dropping on reevaluation const isLanguageDetectable = selectShouldDetectChatLanguage(global, chatId); const detectedLanguage = chat.detectedLanguage; const { doNotTranslate } = global.settings.byKey; return Boolean(isLanguageDetectable && detectedLanguage && !doNotTranslate.includes(detectedLanguage)); } export function selectRequestedChatTranslationLanguage( global: T, chatId: string, ...[tabId = getCurrentTabId()]: TabArgs ) { const { requestedTranslations } = selectTabState(global, tabId); return requestedTranslations.byChatId[chatId]?.toLanguage; } export function selectSimilarChannelIds( global: T, chatId: string, ) { return global.chats.similarChannelsById[chatId]; } export function selectSimilarBotsIds( global: T, chatId: string, ) { return global.chats.similarBotsById[chatId]; } export function selectChatLastMessageId( global: T, chatId: string, listType: 'all' | 'saved' = 'all', ) { return global.chats.lastMessageIds[listType]?.[chatId]; } export function selectChatLastMessage( global: T, chatId: string, listType: 'all' | 'saved' = 'all', ) { const id = selectChatLastMessageId(global, chatId, listType); if (!id) return undefined; const realChatId = listType === 'saved' ? global.currentUserId! : chatId; return global.messages.byChatId[realChatId]?.byId[id]; } export function selectIsMonoforumAdmin( 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( global: T, chatId: string, ) { const chat = selectChat(global, chatId); if (!chat) return; return chat.isMonoforum ? selectChat(global, chat.linkedMonoforumId!) : undefined; } export function selectIsChatRestricted(global: T, chatId: string): boolean { const chat = selectChat(global, chatId); if (!chat) return false; const activeRestrictions = selectActiveRestrictionReasons(global, chat.restrictionReasons); return activeRestrictions.length > 0; } export function selectAreFoldersPresent(global: T) { const ids = global.chatFolders.orderedIds; return Boolean(ids && ids.length > 1); }