308 lines
9.1 KiB
TypeScript
308 lines
9.1 KiB
TypeScript
import type {
|
|
ApiChat, ApiGlobalMessageSearchType, ApiMessage, ApiPeer, ApiTopic,
|
|
ApiUserStatus,
|
|
} from '../../../api/types';
|
|
import type { ActionReturnType, GlobalState, TabArgs } from '../../types';
|
|
|
|
import { GLOBAL_SEARCH_SLICE, GLOBAL_TOPIC_SEARCH_SLICE } from '../../../config';
|
|
import { timestampPlusDay } from '../../../util/dates/dateFormat';
|
|
import { isDeepLink, tryParseDeepLink } from '../../../util/deepLinkParser';
|
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
|
import { throttle } from '../../../util/schedulers';
|
|
import { callApi } from '../../../api/gramjs';
|
|
import { isChatChannel, isChatGroup, toChannelId } from '../../helpers/chats';
|
|
import { isApiPeerChat } from '../../helpers/peers';
|
|
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
|
import {
|
|
addMessages,
|
|
addUserStatuses,
|
|
updateGlobalSearch,
|
|
updateGlobalSearchFetchingStatus,
|
|
updateGlobalSearchResults,
|
|
updateTopics,
|
|
} from '../../reducers';
|
|
import {
|
|
selectChat, selectChatByUsername, selectChatMessage, selectCurrentGlobalSearchQuery, selectPeer, selectTabState,
|
|
} from '../../selectors';
|
|
|
|
const searchThrottled = throttle((cb) => cb(), 500, false);
|
|
|
|
addActionHandler('setGlobalSearchQuery', (global, actions, payload): ActionReturnType => {
|
|
const { query, tabId = getCurrentTabId() } = payload!;
|
|
const { chatId } = selectTabState(global, tabId).globalSearch;
|
|
|
|
if (query && !chatId) {
|
|
void searchThrottled(async () => {
|
|
const result = await callApi('searchChats', { query });
|
|
|
|
global = getGlobal();
|
|
const currentSearchQuery = selectCurrentGlobalSearchQuery(global, tabId);
|
|
if (!result || !currentSearchQuery || (query !== currentSearchQuery)) {
|
|
global = updateGlobalSearchFetchingStatus(global, { chats: false }, tabId);
|
|
setGlobal(global);
|
|
return;
|
|
}
|
|
|
|
const {
|
|
accountResultIds, globalResultIds,
|
|
} = result;
|
|
|
|
global = updateGlobalSearchFetchingStatus(global, { chats: false }, tabId);
|
|
global = updateGlobalSearch(global, {
|
|
localResults: {
|
|
peerIds: accountResultIds,
|
|
},
|
|
globalResults: {
|
|
...selectTabState(global, tabId).globalSearch.globalResults,
|
|
peerIds: globalResultIds,
|
|
},
|
|
}, tabId);
|
|
|
|
setGlobal(global);
|
|
});
|
|
}
|
|
});
|
|
|
|
addActionHandler('setGlobalSearchDate', (global, actions, payload): ActionReturnType => {
|
|
const { date, tabId = getCurrentTabId() } = payload!;
|
|
const maxDate = date ? timestampPlusDay(date) : date;
|
|
|
|
global = updateGlobalSearch(global, {
|
|
minDate: date,
|
|
maxDate,
|
|
query: '',
|
|
resultsByType: {
|
|
...selectTabState(global, tabId).globalSearch.resultsByType,
|
|
text: {
|
|
totalCount: undefined,
|
|
foundIds: [],
|
|
nextOffsetId: 0,
|
|
},
|
|
},
|
|
}, tabId);
|
|
setGlobal(global);
|
|
|
|
actions.searchMessagesGlobal({ type: 'text', tabId });
|
|
});
|
|
|
|
addActionHandler('searchMessagesGlobal', (global, actions, payload): ActionReturnType => {
|
|
const { type, tabId = getCurrentTabId() } = payload;
|
|
const {
|
|
query, resultsByType, chatId,
|
|
} = selectTabState(global, tabId).globalSearch;
|
|
const {
|
|
totalCount, foundIds, nextOffsetId, nextOffsetPeerId, nextOffsetRate,
|
|
} = resultsByType?.[type] || {};
|
|
|
|
// Stop loading if we have all the messages or server returned 0
|
|
if (totalCount !== undefined && (!totalCount || (foundIds && foundIds.length >= totalCount))) {
|
|
return;
|
|
}
|
|
|
|
const chat = chatId ? selectPeer(global, chatId) : undefined;
|
|
const offsetPeer = nextOffsetPeerId ? selectPeer(global, nextOffsetPeerId) : undefined;
|
|
|
|
searchMessagesGlobal(global, {
|
|
query,
|
|
type,
|
|
offsetRate: nextOffsetRate,
|
|
offsetId: nextOffsetId,
|
|
offsetPeer,
|
|
peer: chat,
|
|
tabId,
|
|
});
|
|
});
|
|
|
|
addActionHandler('searchPopularBotApps', async (global, actions, payload): Promise<void> => {
|
|
const { tabId = getCurrentTabId() } = payload || {};
|
|
const popularBotApps = selectTabState(global, tabId).globalSearch.popularBotApps;
|
|
const offset = popularBotApps?.nextOffset;
|
|
if (popularBotApps?.peerIds && !offset) return; // Already fetched all
|
|
|
|
global = updateGlobalSearchFetchingStatus(global, { botApps: true }, tabId);
|
|
setGlobal(global);
|
|
|
|
const result = await callApi('fetchPopularAppBots', { offset });
|
|
|
|
global = getGlobal();
|
|
if (!result) {
|
|
global = updateGlobalSearchFetchingStatus(global, { botApps: false }, tabId);
|
|
setGlobal(global);
|
|
return;
|
|
}
|
|
|
|
global = updateGlobalSearch(global, {
|
|
popularBotApps: {
|
|
peerIds: [...(popularBotApps?.peerIds || []), ...result.peerIds],
|
|
nextOffset: result.nextOffset,
|
|
},
|
|
}, tabId);
|
|
global = updateGlobalSearchFetchingStatus(global, { botApps: false }, tabId);
|
|
|
|
setGlobal(global);
|
|
});
|
|
|
|
async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
|
query?: string;
|
|
type: ApiGlobalMessageSearchType;
|
|
offsetRate?: number;
|
|
offsetId?: number;
|
|
offsetPeer?: ApiPeer;
|
|
peer?: ApiPeer;
|
|
maxDate?: number;
|
|
minDate?: number;
|
|
tabId: TabArgs<T>[0];
|
|
}) {
|
|
const {
|
|
query = '', type, offsetRate, offsetId, offsetPeer, peer, maxDate, minDate, tabId = getCurrentTabId(),
|
|
} = params;
|
|
let result: {
|
|
messages: ApiMessage[];
|
|
userStatusesById?: Record<number, ApiUserStatus>;
|
|
topics?: ApiTopic[];
|
|
totalTopicsCount?: number;
|
|
totalCount: number;
|
|
nextOffsetRate?: number;
|
|
nextOffsetId?: number;
|
|
nextOffsetPeerId?: string;
|
|
} | undefined;
|
|
|
|
let messageLink: ApiMessage | undefined;
|
|
|
|
if (peer) {
|
|
const inChatResultRequest = callApi('searchMessagesInChat', {
|
|
peer,
|
|
query,
|
|
type,
|
|
limit: GLOBAL_SEARCH_SLICE,
|
|
offsetId,
|
|
minDate,
|
|
maxDate,
|
|
});
|
|
const isChat = isApiPeerChat(peer);
|
|
const topicsRequest = isChat && peer.isForum ? callApi('fetchTopics', {
|
|
chat: peer,
|
|
query,
|
|
limit: GLOBAL_TOPIC_SEARCH_SLICE,
|
|
}) : undefined;
|
|
|
|
const [inChatResult, topics] = await Promise.all([inChatResultRequest, topicsRequest]);
|
|
|
|
if (inChatResult) {
|
|
const {
|
|
messages, totalCount, nextOffsetId,
|
|
} = inChatResult;
|
|
|
|
const { topics: localTopics, count } = topics || {};
|
|
|
|
result = {
|
|
topics: localTopics,
|
|
totalTopicsCount: count,
|
|
messages,
|
|
totalCount,
|
|
nextOffsetId,
|
|
};
|
|
}
|
|
} else {
|
|
result = await callApi('searchMessagesGlobal', {
|
|
query,
|
|
offsetRate,
|
|
offsetId,
|
|
offsetPeer,
|
|
limit: GLOBAL_SEARCH_SLICE,
|
|
type,
|
|
maxDate,
|
|
minDate,
|
|
});
|
|
if (isDeepLink(query)) {
|
|
const link = tryParseDeepLink(query);
|
|
if (link?.type === 'publicMessageLink') {
|
|
messageLink = await getMessageByPublicLink(global, link);
|
|
} else if (link?.type === 'privateMessageLink') {
|
|
messageLink = await getMessageByPrivateLink(global, link);
|
|
}
|
|
}
|
|
}
|
|
|
|
global = getGlobal();
|
|
const currentSearchQuery = selectCurrentGlobalSearchQuery(global, tabId);
|
|
if (!result || (query !== '' && query !== currentSearchQuery)) {
|
|
global = updateGlobalSearchFetchingStatus(global, { messages: false }, tabId);
|
|
setGlobal(global);
|
|
return;
|
|
}
|
|
|
|
if (messageLink) {
|
|
result.totalCount = result.messages.unshift(messageLink);
|
|
}
|
|
|
|
const {
|
|
messages, userStatusesById, totalCount, nextOffsetRate, nextOffsetId, nextOffsetPeerId,
|
|
} = result;
|
|
|
|
if (userStatusesById) {
|
|
global = addUserStatuses(global, userStatusesById);
|
|
}
|
|
|
|
if (messages.length) {
|
|
global = addMessages(global, messages);
|
|
}
|
|
|
|
global = updateGlobalSearchResults(
|
|
global,
|
|
messages,
|
|
totalCount,
|
|
type,
|
|
nextOffsetRate,
|
|
nextOffsetId,
|
|
nextOffsetPeerId,
|
|
tabId,
|
|
);
|
|
|
|
if (result.topics) {
|
|
global = updateTopics(global, peer!.id, result.totalTopicsCount!, result.topics);
|
|
}
|
|
|
|
const sortedTopics = result.topics?.map(({ id }) => id).sort((a, b) => b - a);
|
|
global = updateGlobalSearch(global, {
|
|
foundTopicIds: sortedTopics,
|
|
}, tabId);
|
|
|
|
setGlobal(global);
|
|
}
|
|
|
|
async function getMessageByPublicLink(global: GlobalState, link: { username: string; messageId: number }) {
|
|
const { username, messageId } = link;
|
|
const localChat = selectChatByUsername(global, username);
|
|
if (localChat) {
|
|
return getChatGroupOrChannelMessage(global, localChat, messageId);
|
|
}
|
|
const { chat } = await callApi('getChatByUsername', username) ?? {};
|
|
if (!chat) {
|
|
return undefined;
|
|
}
|
|
return getChatGroupOrChannelMessage(global, chat, messageId);
|
|
}
|
|
|
|
function getMessageByPrivateLink(global: GlobalState, link: { channelId: string; messageId: number }) {
|
|
const { channelId, messageId } = link;
|
|
const internalChannelId = toChannelId(channelId);
|
|
const chat = selectChat(global, internalChannelId);
|
|
if (!chat) {
|
|
return undefined;
|
|
}
|
|
return getChatGroupOrChannelMessage(global, chat, messageId);
|
|
}
|
|
|
|
async function getChatGroupOrChannelMessage(global: GlobalState, chat: ApiChat, messageId: number) {
|
|
if (!isChatGroup(chat) && !isChatChannel(chat)) {
|
|
return undefined;
|
|
}
|
|
const localMessage = selectChatMessage(global, chat.id, messageId);
|
|
if (localMessage) {
|
|
return localMessage;
|
|
}
|
|
const result = await callApi('fetchMessage', { chat, messageId });
|
|
return result === 'MESSAGE_DELETED' ? undefined : result?.message;
|
|
}
|