import { Api as GramJs } from '../../../lib/gramjs'; import type { ApiError, ApiInputPrivacyRules, ApiPeer, ApiPeerStories, ApiReaction, ApiStealthMode, ApiTypeStory, } from '../../types'; import { MESSAGE_ID_REQUIRED_ERROR, STORY_LIST_LIMIT } from '../../../config'; import { buildCollectionByCallback } from '../../../util/iteratees'; import { buildApiReportResult } from '../apiBuilders/messages'; import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers'; import { buildApiPeerStories, buildApiStealthMode, buildApiStory, buildApiStoryView, buildApiStoryViews, } from '../apiBuilders/stories'; import { buildInputPeer, buildInputPrivacyRules, buildInputReaction, } from '../gramjsBuilders'; import { addStoryToLocalDb } from '../helpers/localDb'; import { deserializeBytes } from '../helpers/misc'; import { invokeRequest } from './client'; export async function fetchAllStories({ stateHash, isFirstRequest = false, isHidden = false, }: { isFirstRequest?: boolean; stateHash?: string; isHidden?: boolean; }): Promise< undefined | { state: string; stealthMode: ApiStealthMode } | { peerStories: Record; hasMore?: true; state: string; stealthMode: ApiStealthMode; }> { const params: ConstructorParameters[0] = isFirstRequest ? (isHidden ? { hidden: true } : {}) : { state: stateHash, next: true, ...(isHidden && { hidden: true }) }; const result = await invokeRequest(new GramJs.stories.GetAllStories(params)); if (!result) { return undefined; } if (result instanceof GramJs.stories.AllStoriesNotModified) { return { state: result.state, stealthMode: buildApiStealthMode(result.stealthMode), }; } const allUserStories = result.peerStories.reduce>((acc, peerStories) => { const peerId = getApiChatIdFromMtpPeer(peerStories.peer); const stories = buildApiPeerStories(peerStories); const { profileIds, orderedIds, lastUpdatedAt } = Object.values(stories).reduce< { profileIds: number[]; orderedIds: number[]; lastUpdatedAt?: number; } >((dataAcc, story) => { if ('isInProfile' in story && story.isInProfile) { dataAcc.profileIds.push(story.id); } if (!('isDeleted' in story)) { dataAcc.orderedIds.push(story.id); dataAcc.lastUpdatedAt = Math.max(story.date, dataAcc.lastUpdatedAt || 0); } return dataAcc; }, { profileIds: [], orderedIds: [], lastUpdatedAt: undefined, }); if (orderedIds.length === 0) { return acc; } acc[peerId] = { byId: stories, orderedIds, profileIds, lastUpdatedAt, lastReadId: peerStories.maxReadId, }; return acc; }, {}); // Add after building stories to avoid overwriting repair info result.peerStories.forEach((peerStories) => ( peerStories.stories.forEach((story) => addStoryToLocalDb(story, getApiChatIdFromMtpPeer(peerStories.peer))) )); return { peerStories: allUserStories, hasMore: result.hasMore, state: result.state, stealthMode: buildApiStealthMode(result.stealthMode), }; } export async function fetchPeerStories({ peer, }: { peer: ApiPeer; }) { const result = await invokeRequest(new GramJs.stories.GetPeerStories({ peer: buildInputPeer(peer.id, peer.accessHash), })); if (!result) { return undefined; } const stories = buildCollectionByCallback(result.stories.stories, (story) => ( [story.id, buildApiStory(peer.id, story)] )); // Add after building stories to avoid overwriting repair info result.stories.stories.forEach((story) => addStoryToLocalDb(story, peer.id)); return { stories, lastReadStoryId: result.stories.maxReadId, }; } export function fetchPeerProfileStories({ peer, offsetId, }: { peer: ApiPeer; offsetId?: number; }) { return fetchCommonStoriesRequest({ method: new GramJs.stories.GetPinnedStories({ peer: buildInputPeer(peer.id, peer.accessHash), offsetId, limit: STORY_LIST_LIMIT, }), peerId: peer.id, }); } export function fetchStoriesArchive({ peer, offsetId, }: { peer: ApiPeer; offsetId?: number; }) { return fetchCommonStoriesRequest({ method: new GramJs.stories.GetStoriesArchive({ peer: peer && buildInputPeer(peer.id, peer.accessHash), offsetId, limit: STORY_LIST_LIMIT, }), peerId: peer.id, }); } export async function fetchPeerStoriesByIds({ peer, ids }: { peer: ApiPeer; ids: number[] }) { const result = await invokeRequest(new GramJs.stories.GetStoriesByID({ peer: buildInputPeer(peer.id, peer.accessHash), id: ids, })); if (!result) { return undefined; } const stories = ids.reduce>((acc, id) => { const story = result.stories.find(({ id: currentId }) => currentId === id); if (story) { acc[id] = buildApiStory(peer.id, story); } else { acc[id] = { id, peerId: peer.id, isDeleted: true, }; } return acc; }, {}); // Add after building stories to avoid overwriting repair info result.stories.forEach((story) => addStoryToLocalDb(story, peer.id)); return { pinnedIds: result.pinnedToTop, stories, }; } export function viewStory({ peer, storyId }: { peer: ApiPeer; storyId: number }) { return invokeRequest(new GramJs.stories.IncrementStoryViews({ peer: buildInputPeer(peer.id, peer.accessHash), id: [storyId], })); } export function markStoryRead({ peer, storyId }: { peer: ApiPeer; storyId: number }) { return invokeRequest(new GramJs.stories.ReadStories({ peer: buildInputPeer(peer.id, peer.accessHash), maxId: storyId, })); } export function deleteStory({ peer, storyId }: { peer: ApiPeer; storyId: number }) { return invokeRequest(new GramJs.stories.DeleteStories({ peer: buildInputPeer(peer.id, peer.accessHash), id: [storyId], })); } export function toggleStoryInProfile({ peer, storyId, isInProfile, }: { peer: ApiPeer; storyId: number; isInProfile?: boolean; }) { return invokeRequest(new GramJs.stories.TogglePinned({ peer: buildInputPeer(peer.id, peer.accessHash), id: [storyId], pinned: isInProfile, })); } export function toggleStoryPinnedToTop({ peer, storyIds, }: { peer: ApiPeer; storyIds: number[]; }) { return invokeRequest(new GramJs.stories.TogglePinnedToTop({ peer: buildInputPeer(peer.id, peer.accessHash), id: storyIds, })); } export async function fetchStoryViewList({ peer, storyId, areJustContacts, query, areReactionsFirst, limit = STORY_LIST_LIMIT, offset = '', }: { peer: ApiPeer; storyId: number; areJustContacts?: true; areReactionsFirst?: true; query?: string; limit?: number; offset?: string; }) { const result = await invokeRequest(new GramJs.stories.GetStoryViewsList({ peer: buildInputPeer(peer.id, peer.accessHash), id: storyId, justContacts: areJustContacts, q: query, reactionsFirst: areReactionsFirst, limit, offset, })); if (!result) { return undefined; } const views = result.views.map(buildApiStoryView).filter(Boolean); return { views, nextOffset: result.nextOffset, reactionsCount: result.reactionsCount, viewsCount: result.count, }; } export async function fetchStoriesViews({ peer, storyIds, }: { peer: ApiPeer; storyIds: number[]; }) { const result = await invokeRequest(new GramJs.stories.GetStoriesViews({ peer: buildInputPeer(peer.id, peer.accessHash), id: storyIds, })); if (!result?.views[0]) { return undefined; } const views = buildApiStoryViews(result.views[0]); return { views, }; } export async function fetchStoryLink({ peer, storyId }: { peer: ApiPeer ; storyId: number }) { const result = await invokeRequest(new GramJs.stories.ExportStoryLink({ peer: buildInputPeer(peer.id, peer.accessHash), id: storyId, })); if (!result) { return undefined; } return result.link; } export async function reportStory({ peer, storyId, description, option, }: { peer: ApiPeer; storyId: number; description: string; option: string; }) { try { const result = await invokeRequest(new GramJs.stories.Report({ peer: buildInputPeer(peer.id, peer.accessHash), id: [storyId], option: deserializeBytes(option), message: description, }), { shouldThrow: true }); if (!result) return undefined; return { result: buildApiReportResult(result), error: undefined }; } catch (err: any) { const errorMessage = (err as ApiError).message; if (errorMessage === MESSAGE_ID_REQUIRED_ERROR) { return { result: undefined, error: errorMessage, }; } throw err; } } export function editStoryPrivacy({ peer, id, privacy, }: { peer: ApiPeer; id: number; privacy: ApiInputPrivacyRules; }) { return invokeRequest(new GramJs.stories.EditStory({ peer: buildInputPeer(peer.id, peer.accessHash), id, privacyRules: buildInputPrivacyRules(privacy), }), { shouldReturnTrue: true, }); } export function toggleStoriesHidden({ peer, isHidden, }: { peer: ApiPeer; isHidden: boolean; }) { return invokeRequest(new GramJs.stories.TogglePeerStoriesHidden({ peer: buildInputPeer(peer.id, peer.accessHash), hidden: isHidden, })); } export function fetchStoriesMaxIds({ peers, }: { peers: ApiPeer[]; }) { return invokeRequest(new GramJs.stories.GetPeerMaxIDs({ id: peers.map((peer) => buildInputPeer(peer.id, peer.accessHash)), }), { shouldIgnoreErrors: true }); } async function fetchCommonStoriesRequest({ method, peerId }: { method: GramJs.stories.GetPinnedStories | GramJs.stories.GetStoriesArchive; peerId: string; }) { const result = await invokeRequest(method); if (!result) { return undefined; } const stories = buildCollectionByCallback(result.stories, (story) => ( [story.id, buildApiStory(peerId, story)] )); // Add after building stories to avoid overwriting repair info result.stories.forEach((story) => addStoryToLocalDb(story, peerId)); return { count: result.count, stories, pinnedIds: result.pinnedToTop, }; } export function sendStoryReaction({ peer, storyId, reaction, shouldAddToRecent, }: { peer: ApiPeer; storyId: number; reaction?: ApiReaction; shouldAddToRecent?: boolean; }) { return invokeRequest(new GramJs.stories.SendReaction({ reaction: reaction ? buildInputReaction(reaction) : new GramJs.ReactionEmpty(), peer: buildInputPeer(peer.id, peer.accessHash), storyId, ...(shouldAddToRecent && { addToRecent: true }), }), { shouldReturnTrue: true, }); } export function activateStealthMode({ isForPast, isForFuture, }: { isForPast?: true; isForFuture?: true; }) { return invokeRequest(new GramJs.stories.ActivateStealthMode({ past: isForPast, future: isForFuture, }), { shouldReturnTrue: true, }); }