463 lines
11 KiB
TypeScript

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<string, ApiPeerStories>;
hasMore?: true;
state: string;
stealthMode: ApiStealthMode;
}> {
const params: ConstructorParameters<typeof GramJs.stories.GetAllStories>[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<Record<string, ApiPeerStories>>((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<Record<string, ApiTypeStory>>((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,
});
}