Profile: add similar channels tab (#4104)

This commit is contained in:
Alexander Zinchuk 2023-12-28 14:38:07 +01:00
parent 20d8087d87
commit d2b834d1a6
17 changed files with 221 additions and 12 deletions

View File

@ -15,9 +15,21 @@ import localDb from '../localDb';
import { buildJson } from './misc'; import { buildJson } from './misc';
type LimitType = 'default' | 'premium'; type LimitType = 'default' | 'premium';
type Limit = 'upload_max_fileparts' | 'stickers_faved_limit' | 'saved_gifs_limit' | 'dialog_filters_chats_limit' | type Limit =
'dialog_filters_limit' | 'dialogs_folder_pinned_limit' | 'dialogs_pinned_limit' | 'caption_length_limit' | | 'upload_max_fileparts'
'channels_limit' | 'channels_public_limit' | 'about_length_limit' | 'chatlist_invites_limit' | 'chatlist_joined_limit'; | 'stickers_faved_limit'
| 'saved_gifs_limit'
| 'dialog_filters_chats_limit'
| 'dialog_filters_limit'
| 'dialogs_folder_pinned_limit'
| 'dialogs_pinned_limit'
| 'caption_length_limit'
| 'channels_limit'
| 'channels_public_limit'
| 'about_length_limit'
| 'chatlist_invites_limit'
| 'chatlist_joined_limit'
| 'recommended_channels_limit';
type LimitKey = `${Limit}_${LimitType}`; type LimitKey = `${Limit}_${LimitType}`;
type LimitsConfig = Record<LimitKey, number>; type LimitsConfig = Record<LimitKey, number>;
@ -111,6 +123,7 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
aboutLength: getLimit(appConfig, 'about_length_limit', 'aboutLength'), aboutLength: getLimit(appConfig, 'about_length_limit', 'aboutLength'),
chatlistInvites: getLimit(appConfig, 'chatlist_invites_limit', 'chatlistInvites'), chatlistInvites: getLimit(appConfig, 'chatlist_invites_limit', 'chatlistInvites'),
chatlistJoined: getLimit(appConfig, 'chatlist_joined_limit', 'chatlistJoined'), chatlistJoined: getLimit(appConfig, 'chatlist_joined_limit', 'chatlistJoined'),
recommendedChannels: getLimit(appConfig, 'recommended_channels_limit', 'recommendedChannels'),
}, },
hash, hash,
areStoriesHidden: appConfig.stories_all_hidden, areStoriesHidden: appConfig.stories_all_hidden,

View File

@ -1895,6 +1895,22 @@ export function setViewForumAsMessages({ chat, isEnabled }: { chat: ApiChat; isE
}); });
} }
export async function fetchChannelRecommendations({ chat }: { chat: ApiChat }) {
const { id, accessHash } = chat;
const channel = buildInputEntity(id, accessHash);
const result = await invokeRequest(new GramJs.channels.GetChannelRecommendations({
channel: channel as GramJs.InputChannel,
}));
if (!result) {
return undefined;
}
updateLocalDb(result);
return result?.chats.map((_chat) => buildApiChatFromPreview(_chat)).filter(Boolean);
}
function handleUserPrivacyRestrictedUpdates(updates: GramJs.TypeUpdates) { function handleUserPrivacyRestrictedUpdates(updates: GramJs.TypeUpdates) {
if (!(updates instanceof GramJs.Updates) && !(updates instanceof GramJs.UpdatesCombined)) { if (!(updates instanceof GramJs.Updates) && !(updates instanceof GramJs.UpdatesCombined)) {
return undefined; return undefined;

View File

@ -24,6 +24,7 @@ export {
editTopic, toggleForum, fetchTopicById, createTopic, toggleParticipantsHidden, checkChatlistInvite, editTopic, toggleForum, fetchTopicById, createTopic, toggleParticipantsHidden, checkChatlistInvite,
joinChatlistInvite, createChalistInvite, editChatlistInvite, deleteChatlistInvite, fetchChatlistInvites, joinChatlistInvite, createChalistInvite, editChatlistInvite, deleteChatlistInvite, fetchChatlistInvites,
fetchLeaveChatlistSuggestions, leaveChatlist, togglePeerTranslations, setViewForumAsMessages, fetchLeaveChatlistSuggestions, leaveChatlist, togglePeerTranslations, setViewForumAsMessages,
fetchChannelRecommendations,
} from './chats'; } from './chats';
export { export {

View File

@ -98,6 +98,7 @@ const LIMITS_ORDER: ApiLimitTypeWithoutUpload[] = [
'captionLength', 'captionLength',
'dialogFilters', 'dialogFilters',
'dialogFiltersChats', 'dialogFiltersChats',
'recommendedChannels',
]; ];
const LIMITS_TITLES: Record<ApiLimitTypeWithoutUpload, string> = { const LIMITS_TITLES: Record<ApiLimitTypeWithoutUpload, string> = {
@ -110,6 +111,7 @@ const LIMITS_TITLES: Record<ApiLimitTypeWithoutUpload, string> = {
captionLength: 'CaptionsLimitTitle', captionLength: 'CaptionsLimitTitle',
dialogFilters: 'FoldersLimitTitle', dialogFilters: 'FoldersLimitTitle',
dialogFiltersChats: 'ChatPerFolderLimitTitle', dialogFiltersChats: 'ChatPerFolderLimitTitle',
recommendedChannels: 'SimilarChannelsLimitTitle',
}; };
const LIMITS_DESCRIPTIONS: Record<ApiLimitTypeWithoutUpload, string> = { const LIMITS_DESCRIPTIONS: Record<ApiLimitTypeWithoutUpload, string> = {
@ -122,6 +124,7 @@ const LIMITS_DESCRIPTIONS: Record<ApiLimitTypeWithoutUpload, string> = {
captionLength: 'CaptionsLimitSubtitle', captionLength: 'CaptionsLimitSubtitle',
dialogFilters: 'FoldersLimitSubtitle', dialogFilters: 'FoldersLimitSubtitle',
dialogFiltersChats: 'ChatPerFolderLimitSubtitle', dialogFiltersChats: 'ChatPerFolderLimitSubtitle',
recommendedChannels: 'SimilarChannelsLimitSubtitle',
}; };
const BORDER_THRESHOLD = 20; const BORDER_THRESHOLD = 20;

View File

@ -121,6 +121,7 @@
} }
} }
&.similarChannels-list,
&.commonChats-list, &.commonChats-list,
&.members-list { &.members-list {
padding: 0.5rem; padding: 0.5rem;
@ -133,5 +134,30 @@
} }
} }
} }
&.similarChannels-list {
.ListItem.blured {
filter: opacity(0.8);
}
.show-more-channels {
width: calc(100% - 1rem);
margin: 0 auto;
margin-top: -1.8125rem;
z-index: 1;
border-radius: var(--border-radius-default-small);
box-shadow: 0px 0px 1rem 1rem white;
.icon {
margin-left: 0.625rem;
}
}
.more-similar {
text-align: center;
margin-top: 1rem;
font-size: 0.8125rem;
}
}
} }
} }

View File

@ -34,15 +34,20 @@ import {
selectChatFullInfo, selectChatFullInfo,
selectChatMessages, selectChatMessages,
selectCurrentMediaSearch, selectCurrentMediaSearch,
selectIsCurrentUserPremium,
selectIsRightColumnShown, selectIsRightColumnShown,
selectPeerFullInfo, selectPeerFullInfo,
selectPeerStories, selectPeerStories,
selectSimilarChannelIds,
selectTabState, selectTabState,
selectTheme, selectTheme,
selectUser, selectUser,
} from '../../global/selectors'; } from '../../global/selectors';
import { selectPremiumLimit } from '../../global/selectors/limits';
import buildClassName from '../../util/buildClassName';
import { captureEvents, SwipeDirection } from '../../util/captureEvents'; import { captureEvents, SwipeDirection } from '../../util/captureEvents';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment'; import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import renderText from '../common/helpers/renderText';
import { getSenderName } from '../left/search/helpers/getSenderName'; import { getSenderName } from '../left/search/helpers/getSenderName';
import usePeerStoriesPolling from '../../hooks/polling/usePeerStoriesPolling'; import usePeerStoriesPolling from '../../hooks/polling/usePeerStoriesPolling';
@ -66,6 +71,7 @@ import PrivateChatInfo from '../common/PrivateChatInfo';
import ProfileInfo from '../common/ProfileInfo'; import ProfileInfo from '../common/ProfileInfo';
import WebLink from '../common/WebLink'; import WebLink from '../common/WebLink';
import MediaStory from '../story/MediaStory'; import MediaStory from '../story/MediaStory';
import Button from '../ui/Button';
import FloatingActionButton from '../ui/FloatingActionButton'; import FloatingActionButton from '../ui/FloatingActionButton';
import InfiniteScroll from '../ui/InfiniteScroll'; import InfiniteScroll from '../ui/InfiniteScroll';
import ListItem, { type MenuItemContextAction } from '../ui/ListItem'; import ListItem, { type MenuItemContextAction } from '../ui/ListItem';
@ -113,6 +119,9 @@ type StateProps = {
isChatProtected?: boolean; isChatProtected?: boolean;
nextProfileTab?: ProfileTabType; nextProfileTab?: ProfileTabType;
shouldWarnAboutSvg?: boolean; shouldWarnAboutSvg?: boolean;
similarChannels?: string[];
isCurrentUserPremium?: boolean;
limitSimilarChannels: number;
}; };
const TABS = [ const TABS = [
@ -158,6 +167,9 @@ const Profile: FC<OwnProps & StateProps> = ({
isChatProtected, isChatProtected,
nextProfileTab, nextProfileTab,
shouldWarnAboutSvg, shouldWarnAboutSvg,
similarChannels,
isCurrentUserPremium,
limitSimilarChannels,
}) => { }) => {
const { const {
setLocalMediaSearchType, setLocalMediaSearchType,
@ -172,6 +184,8 @@ const Profile: FC<OwnProps & StateProps> = ({
setNewChatMembersDialogState, setNewChatMembersDialogState,
loadPeerPinnedStories, loadPeerPinnedStories,
loadStoriesArchive, loadStoriesArchive,
openPremiumModal,
fetchChannelRecommendations,
} = getActions(); } = getActions();
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
@ -192,7 +206,19 @@ const Profile: FC<OwnProps & StateProps> = ({
// in forum topics. Return it when it's fixed on the server side. // in forum topics. Return it when it's fixed on the server side.
...(!topicId ? [{ type: 'voice', title: 'SharedVoiceTab2' }] : []), ...(!topicId ? [{ type: 'voice', title: 'SharedVoiceTab2' }] : []),
...(hasCommonChatsTab ? [{ type: 'commonChats', title: 'SharedGroupsTab2' }] : []), ...(hasCommonChatsTab ? [{ type: 'commonChats', title: 'SharedGroupsTab2' }] : []),
]), [chatId, currentUserId, hasCommonChatsTab, hasMembersTab, hasStoriesTab, isChannel, topicId]); ...(isChannel && similarChannels?.length
? [{ type: 'similarChannels', title: 'SimilarChannelsTab' }]
: []),
]), [
chatId,
currentUserId,
hasCommonChatsTab,
hasMembersTab,
hasStoriesTab,
isChannel,
topicId,
similarChannels,
]);
const initialTab = useMemo(() => { const initialTab = useMemo(() => {
if (!nextProfileTab) { if (!nextProfileTab) {
@ -213,6 +239,12 @@ const Profile: FC<OwnProps & StateProps> = ({
setActiveTab(index); setActiveTab(index);
}, [nextProfileTab, tabs]); }, [nextProfileTab, tabs]);
useEffect(() => {
if (isChannel) {
fetchChannelRecommendations({ chatId });
}
}, [chatId, isChannel]);
const renderingActiveTab = activeTab > tabs.length - 1 ? tabs.length - 1 : activeTab; const renderingActiveTab = activeTab > tabs.length - 1 ? tabs.length - 1 : activeTab;
const tabType = tabs[renderingActiveTab].type as ProfileTabType; const tabType = tabs[renderingActiveTab].type as ProfileTabType;
const handleLoadPeerStories = useCallback(({ offsetId }: { offsetId: number }) => { const handleLoadPeerStories = useCallback(({ offsetId }: { offsetId: number }) => {
@ -240,6 +272,7 @@ const Profile: FC<OwnProps & StateProps> = ({
topicId, topicId,
storyIds, storyIds,
archiveStoryIds, archiveStoryIds,
similarChannels,
); );
const isFirstTab = (hasStoriesTab && resultType === 'stories') const isFirstTab = (hasStoriesTab && resultType === 'stories')
|| resultType === 'members' || resultType === 'members'
@ -512,6 +545,35 @@ const Profile: FC<OwnProps & StateProps> = ({
<GroupChatInfo chatId={id} /> <GroupChatInfo chatId={id} />
</ListItem> </ListItem>
)) ))
) : resultType === 'similarChannels' ? (
<div key={resultType}>
{(viewportIds as string[])!.map((channelId, i) => (
<ListItem
key={channelId}
teactOrderKey={i}
className={buildClassName(
'chat-item-clickable search-result',
!isCurrentUserPremium && i === similarChannels!.length - 1 && 'blured',
)}
// eslint-disable-next-line react/jsx-no-bind
onClick={() => openChat({ id: channelId })}
>
<GroupChatInfo avatarSize="large" chatId={channelId} withFullInfo />
</ListItem>
))}
{!isCurrentUserPremium && (
<>
{/* eslint-disable-next-line react/jsx-no-bind */}
<Button className="show-more-channels" size="smaller" onClick={() => openPremiumModal()}>
{lang('UnlockSimilar')}
<i className="icon icon-unlock-badge" />
</Button>
<div className="more-similar">
{renderText(lang('MoreSimilarText', limitSimilarChannels), ['simple_markdown'])}
</div>
</>
)}
</div>
) : undefined} ) : undefined}
</div> </div>
); );
@ -604,6 +666,8 @@ export default memo(withGlobal<OwnProps>(
&& (getHasAdminRight(chat, 'inviteUsers') || !isUserRightBanned(chat, 'inviteUsers') || chat.isCreator); && (getHasAdminRight(chat, 'inviteUsers') || !isUserRightBanned(chat, 'inviteUsers') || chat.isCreator);
const canDeleteMembers = hasMembersTab && chat && (getHasAdminRight(chat, 'banUsers') || chat.isCreator); const canDeleteMembers = hasMembersTab && chat && (getHasAdminRight(chat, 'banUsers') || chat.isCreator);
const activeDownloads = selectActiveDownloads(global, chatId); const activeDownloads = selectActiveDownloads(global, chatId);
const similarChannels = selectSimilarChannelIds(global, chatId);
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
let hasCommonChatsTab; let hasCommonChatsTab;
let resolvedUserId; let resolvedUserId;
@ -648,6 +712,9 @@ export default memo(withGlobal<OwnProps>(
isChatProtected: chat?.isProtected, isChatProtected: chat?.isProtected,
nextProfileTab: selectTabState(global).nextProfileTab, nextProfileTab: selectTabState(global).nextProfileTab,
shouldWarnAboutSvg: global.settings.byKey.shouldWarnAboutSvg, shouldWarnAboutSvg: global.settings.byKey.shouldWarnAboutSvg,
similarChannels,
isCurrentUserPremium,
limitSimilarChannels: selectPremiumLimit(global, 'recommendedChannels'),
...(hasMembersTab && members && { members, adminMembersById }), ...(hasMembersTab && members && { members, adminMembersById }),
...(hasCommonChatsTab && user && { commonChatIds: user.commonChats?.ids }), ...(hasCommonChatsTab && user && { commonChatIds: user.commonChats?.ids }),
}; };

View File

@ -29,6 +29,7 @@ export default function useProfileViewportIds(
topicId?: number, topicId?: number,
storyIds?: number[], storyIds?: number[],
archiveStoryIds?: number[], archiveStoryIds?: number[],
similarChannels?: string[],
) { ) {
const resultType = tabType === 'members' || !mediaSearchType ? tabType : mediaSearchType; const resultType = tabType === 'members' || !mediaSearchType ? tabType : mediaSearchType;
@ -142,6 +143,9 @@ export default function useProfileViewportIds(
getMore = getMoreStoriesArchive; getMore = getMoreStoriesArchive;
noProfileInfo = noProfileInfoForStoriesArchive; noProfileInfo = noProfileInfoForStoriesArchive;
break; break;
case 'similarChannels':
viewportIds = similarChannels;
break;
} }
return [resultType, viewportIds, getMore, noProfileInfo] as const; return [resultType, viewportIds, getMore, noProfileInfo] as const;

View File

@ -337,4 +337,5 @@ export const DEFAULT_LIMITS: Record<ApiLimitType, readonly [number, number]> = {
aboutLength: [70, 140], aboutLength: [70, 140],
chatlistInvites: [3, 100], chatlistInvites: [3, 100],
chatlistJoined: [2, 20], chatlistJoined: [2, 20],
recommendedChannels: [10, 100],
}; };

View File

@ -51,6 +51,7 @@ import {
addChatMembers, addChatMembers,
addChats, addChats,
addMessages, addMessages,
addSimilarChannels,
addUsers, addUsers,
addUserStatuses, addUserStatuses,
addUsersToRestrictedInviteList, addUsersToRestrictedInviteList,
@ -2517,6 +2518,29 @@ addActionHandler('setViewForumAsMessages', (global, actions, payload): ActionRet
void callApi('setViewForumAsMessages', { chat, isEnabled }); void callApi('setViewForumAsMessages', { chat, isEnabled });
}); });
addActionHandler('fetchChannelRecommendations', async (global, actions, payload): Promise<void> => {
const { chatId } = payload;
const chat = selectChat(global, chatId);
if (!chat) {
return;
}
const similarChannels = await callApi('fetchChannelRecommendations', {
chat,
});
if (!similarChannels) {
return;
}
global = getGlobal();
global = addChats(global, buildCollectionByKey(similarChannels, 'id'));
global = addSimilarChannels(global, chatId, similarChannels.map((channel) => channel.id));
setGlobal(global);
});
async function loadChats( async function loadChats(
listType: 'active' | 'archived', listType: 'active' | 'archived',
offsetId?: string, offsetId?: string,

View File

@ -205,6 +205,10 @@ function unsafeMigrateCache(cached: GlobalState, initialState: GlobalState) {
cached.stories.orderedPeerIds = initialState.stories.orderedPeerIds; cached.stories.orderedPeerIds = initialState.stories.orderedPeerIds;
} }
if (!cached.chats.similarChannelsById) {
cached.chats.similarChannelsById = initialState.chats.similarChannelsById;
}
// Clear old color storage to optimize cache size // Clear old color storage to optimize cache size
if (untypedCached?.appConfig?.peerColors) { if (untypedCached?.appConfig?.peerColors) {
untypedCached.appConfig.peerColors = undefined; untypedCached.appConfig.peerColors = undefined;
@ -373,6 +377,7 @@ function reduceChats<T extends GlobalState>(global: T): GlobalState['chats'] {
return { return {
...global.chats, ...global.chats,
similarChannelsById: {},
isFullyLoaded: {}, isFullyLoaded: {},
byId: pick(global.chats.byId, idsToSave), byId: pick(global.chats.byId, idsToSave),
fullInfoById: pick(global.chats.fullInfoById, idsToSave), fullInfoById: pick(global.chats.fullInfoById, idsToSave),

View File

@ -105,6 +105,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
totalCount: {}, totalCount: {},
byId: {}, byId: {},
fullInfoById: {}, fullInfoById: {},
similarChannelsById: {},
}, },
messages: { messages: {

View File

@ -401,3 +401,20 @@ export function deleteTopic<T extends GlobalState>(
return global; return global;
} }
export function addSimilarChannels<T extends GlobalState>(
global: T,
chatId: string,
similarChannels: string[],
) {
return {
...global,
chats: {
...global.chats,
similarChannelsById: {
...global.chats.similarChannelsById,
[chatId]: similarChannels,
},
},
};
}

View File

@ -316,3 +316,10 @@ export function selectRequestedChatTranslationLanguage<T extends GlobalState>(
return requestedTranslations.byChatId[chatId]?.toLanguage; return requestedTranslations.byChatId[chatId]?.toLanguage;
} }
export function selectSimilarChannelIds<T extends GlobalState>(
global: T,
chatId: string,
): string[] | undefined {
return global.chats.similarChannelsById[chatId];
}

View File

@ -169,13 +169,23 @@ export interface ServiceNotification {
isDeleted?: boolean; isDeleted?: boolean;
} }
export type ApiLimitType = ( export type ApiLimitType =
'uploadMaxFileparts' | 'stickersFaved' | 'savedGifs' | 'dialogFiltersChats' | 'dialogFilters' | 'dialogFolderPinned' | | 'uploadMaxFileparts'
'captionLength' | 'channels' | 'channelsPublic' | 'aboutLength' | 'chatlistInvites' | 'chatlistJoined' | 'stickersFaved'
); | 'savedGifs'
| 'dialogFiltersChats'
| 'dialogFilters'
| 'dialogFolderPinned'
| 'captionLength'
| 'channels'
| 'channelsPublic'
| 'aboutLength'
| 'chatlistInvites'
| 'chatlistJoined'
| 'recommendedChannels';
export type ApiLimitTypeWithModal = Exclude<ApiLimitType, ( export type ApiLimitTypeWithModal = Exclude<ApiLimitType, (
'captionLength' | 'aboutLength' | 'stickersFaved' | 'savedGifs' 'captionLength' | 'aboutLength' | 'stickersFaved' | 'savedGifs' | 'recommendedChannels'
)>; )>;
export type TranslatedMessage = { export type TranslatedMessage = {
@ -771,6 +781,7 @@ export type GlobalState = {
forDiscussionIds?: string[]; forDiscussionIds?: string[];
// Obtained from GetFullChat / GetFullChannel // Obtained from GetFullChat / GetFullChannel
fullInfoById: Record<string, ApiChatFullInfo>; fullInfoById: Record<string, ApiChatFullInfo>;
similarChannelsById: Record<string, string[]>;
}; };
messages: { messages: {
@ -1759,6 +1770,9 @@ export interface ActionPayloads {
fetchChat: { fetchChat: {
chatId: string; chatId: string;
}; };
fetchChannelRecommendations: {
chatId: string;
};
updateChatMutedState: { updateChatMutedState: {
chatId: string; chatId: string;
isMuted?: boolean; isMuted?: boolean;

View File

@ -1443,6 +1443,7 @@ channels.deleteTopicHistory#34435f2d channel:InputChannel top_msg_id:int = messa
channels.toggleParticipantsHidden#6a6e7854 channel:InputChannel enabled:Bool = Updates; channels.toggleParticipantsHidden#6a6e7854 channel:InputChannel enabled:Bool = Updates;
channels.clickSponsoredMessage#18afbc93 channel:InputChannel random_id:bytes = Bool; channels.clickSponsoredMessage#18afbc93 channel:InputChannel random_id:bytes = Bool;
channels.toggleViewForumAsMessages#9738bb15 channel:InputChannel enabled:Bool = Updates; channels.toggleViewForumAsMessages#9738bb15 channel:InputChannel enabled:Bool = Updates;
channels.getChannelRecommendations#83b70d97 channel:InputChannel = messages.Chats;
bots.canSendMessage#1359f4e6 bot:InputUser = Bool; bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
bots.allowSendMessage#f132e3ef bot:InputUser = Updates; bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON; bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON;

View File

@ -214,6 +214,7 @@
"channels.toggleUsername", "channels.toggleUsername",
"channels.viewSponsoredMessage", "channels.viewSponsoredMessage",
"channels.getSponsoredMessages", "channels.getSponsoredMessages",
"channels.getChannelRecommendations",
"bots.canSendMessage", "bots.canSendMessage",
"bots.allowSendMessage", "bots.allowSendMessage",
"bots.invokeWebViewCustomMethod", "bots.invokeWebViewCustomMethod",

View File

@ -355,9 +355,17 @@ export enum NewChatMembersProgress {
Loading, Loading,
} }
export type ProfileTabType = ( export type ProfileTabType =
'members' | 'commonChats' | 'media' | 'documents' | 'links' | 'audio' | 'voice' | 'stories' | 'storiesArchive' | 'members'
); | 'commonChats'
| 'media'
| 'documents'
| 'links'
| 'audio'
| 'voice'
| 'stories'
| 'storiesArchive'
| 'similarChannels';
export type SharedMediaType = 'media' | 'documents' | 'links' | 'audio' | 'voice'; export type SharedMediaType = 'media' | 'documents' | 'links' | 'audio' | 'voice';
export type ApiPrivacyKey = 'phoneNumber' | 'addByPhone' | 'lastSeen' | 'profilePhoto' | 'voiceMessages' | export type ApiPrivacyKey = 'phoneNumber' | 'addByPhone' | 'lastSeen' | 'profilePhoto' | 'voiceMessages' |
'forwards' | 'chatInvite' | 'phoneCall' | 'phoneP2P' | 'bio'; 'forwards' | 'chatInvite' | 'phoneCall' | 'phoneP2P' | 'bio';