Invite restricted users via link (#3982)

This commit is contained in:
Alexander Zinchuk 2023-12-04 14:38:38 +01:00
parent 0e5660abd7
commit 4d70ae306d
12 changed files with 317 additions and 36 deletions

View File

@ -70,7 +70,7 @@ import {
} from '../helpers'; } from '../helpers';
import localDb from '../localDb'; import localDb from '../localDb';
import { scheduleMutedChatUpdate } from '../scheduleUnmute'; import { scheduleMutedChatUpdate } from '../scheduleUnmute';
import { applyState, updateChannelState } from '../updateManager'; import { applyState, processUpdate, updateChannelState } from '../updateManager';
import { invokeRequest, uploadFile } from './client'; import { invokeRequest, uploadFile } from './client';
type FullChatData = { type FullChatData = {
@ -626,7 +626,7 @@ export async function createChannel({
title, about = '', users, title, about = '', users,
}: { }: {
title: string; about?: string; users?: ApiUser[]; title: string; about?: string; users?: ApiUser[];
}, noErrorUpdate = false): Promise<ApiChat | undefined> { }) {
const result = await invokeRequest(new GramJs.channels.CreateChannel({ const result = await invokeRequest(new GramJs.channels.CreateChannel({
broadcast: true, broadcast: true,
title, title,
@ -657,20 +657,36 @@ export async function createChannel({
const channel = buildApiChatFromPreview(newChannel)!; const channel = buildApiChatFromPreview(newChannel)!;
let restrictedUserIds: string[] | undefined;
if (users?.length) { if (users?.length) {
try { try {
await invokeRequest(new GramJs.channels.InviteToChannel({ const updates = await invokeRequest(new GramJs.channels.InviteToChannel({
channel: buildInputEntity(channel.id, channel.accessHash) as GramJs.InputChannel, channel: buildInputEntity(channel.id, channel.accessHash) as GramJs.InputChannel,
users: users.map(({ id, accessHash }) => buildInputEntity(id, accessHash)) as GramJs.InputUser[], users: users.map(({ id, accessHash }) => buildInputEntity(id, accessHash)) as GramJs.InputUser[],
}), { }), {
shouldThrow: noErrorUpdate, shouldIgnoreUpdates: true,
shouldThrow: true,
}); });
if (updates) {
processUpdate(updates);
restrictedUserIds = handleUserPrivacyRestrictedUpdates(updates);
}
} catch (err) { } catch (err) {
// `noErrorUpdate` will cause an exception which we don't want either if ((err as Error).message === 'USER_PRIVACY_RESTRICTED') {
restrictedUserIds = users.map(({ id }) => id);
} else {
onUpdate({
'@type': 'error',
error: {
message: (err as Error).message,
},
});
}
} }
} }
return channel; return { channel, restrictedUserIds };
} }
export function joinChannel({ export function joinChannel({
@ -740,11 +756,12 @@ export async function createGroupChat({
title, users, title, users,
}: { }: {
title: string; users: ApiUser[]; title: string; users: ApiUser[];
}): Promise<ApiChat | undefined> { }) {
const result = await invokeRequest(new GramJs.messages.CreateChat({ const result = await invokeRequest(new GramJs.messages.CreateChat({
title, title,
users: users.map(({ id, accessHash }) => buildInputEntity(id, accessHash)) as GramJs.InputUser[], users: users.map(({ id, accessHash }) => buildInputEntity(id, accessHash)) as GramJs.InputUser[],
}), { }), {
shouldIgnoreUpdates: true,
shouldThrow: true, shouldThrow: true,
}); });
@ -758,6 +775,8 @@ export async function createGroupChat({
} }
return undefined; return undefined;
} }
processUpdate(result);
const restrictedUserIds = handleUserPrivacyRestrictedUpdates(result);
const newChat = result.chats[0]; const newChat = result.chats[0];
if (!newChat || !(newChat instanceof GramJs.Chat)) { if (!newChat || !(newChat instanceof GramJs.Chat)) {
@ -768,7 +787,7 @@ export async function createGroupChat({
return undefined; return undefined;
} }
return buildApiChatFromPreview(newChat); return { chat: buildApiChatFromPreview(newChat), restrictedUserIds };
} }
export async function editChatPhoto({ export async function editChatPhoto({
@ -1265,31 +1284,64 @@ export async function openChatByInvite(hash: string) {
return { chatId: chat.id }; return { chatId: chat.id };
} }
export async function addChatMembers(chat: ApiChat, users: ApiUser[], noErrorUpdate = false) { export async function addChatMembers(chat: ApiChat, users: ApiUser[]) {
try { try {
if (chat.type === 'chatTypeChannel' || chat.type === 'chatTypeSuperGroup') { if (chat.type === 'chatTypeChannel' || chat.type === 'chatTypeSuperGroup') {
return await invokeRequest(new GramJs.channels.InviteToChannel({ try {
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel, const updates = await invokeRequest(new GramJs.channels.InviteToChannel({
users: users.map((user) => buildInputEntity(user.id, user.accessHash)) as GramJs.InputUser[], channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
}), { users: users.map((user) => buildInputEntity(user.id, user.accessHash)) as GramJs.InputUser[],
shouldReturnTrue: true, }), {
shouldThrow: noErrorUpdate, shouldIgnoreUpdates: true,
}); shouldThrow: true,
});
if (updates) {
processUpdate(updates);
return handleUserPrivacyRestrictedUpdates(updates);
}
} catch (err) {
if ((err as Error).message === 'USER_PRIVACY_RESTRICTED') {
return users.map(({ id }) => id);
}
throw err;
}
} }
return await Promise.all(users.map((user) => { const addChatUsersResult = await Promise.all(
return invokeRequest(new GramJs.messages.AddChatUser({ users.map(async (user) => {
chatId: buildInputEntity(chat.id) as BigInt.BigInteger, try {
userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser, const updates = await invokeRequest(new GramJs.messages.AddChatUser({
}), { chatId: buildInputEntity(chat.id) as BigInt.BigInteger,
shouldReturnTrue: true, userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser,
shouldThrow: noErrorUpdate, }), {
}); shouldIgnoreUpdates: true,
})); shouldThrow: true,
});
if (updates) {
processUpdate(updates);
return handleUserPrivacyRestrictedUpdates(updates);
}
return undefined;
} catch (err) {
if ((err as Error).message === 'USER_PRIVACY_RESTRICTED') {
return [user.id];
}
throw err;
}
}),
);
if (addChatUsersResult) {
return addChatUsersResult.flat().filter(Boolean);
}
} catch (err) { } catch (err) {
// `noErrorUpdate` will cause an exception which we don't want either onUpdate({
return undefined; '@type': 'error',
error: {
message: (err as Error).message,
},
});
} }
return undefined;
} }
export function deleteChatMember(chat: ApiChat, user: ApiUser) { export function deleteChatMember(chat: ApiChat, user: ApiUser) {
@ -1819,3 +1871,24 @@ export function togglePeerTranslations({
peer: buildInputPeer(chat.id, chat.accessHash), peer: buildInputPeer(chat.id, chat.accessHash),
})); }));
} }
function handleUserPrivacyRestrictedUpdates(updates: GramJs.TypeUpdates) {
if (!(updates instanceof GramJs.Updates) && !(updates instanceof GramJs.UpdatesCombined)) {
return undefined;
}
const eligibleUpdates = updates
.updates
.filter(
(u): u is GramJs.UpdateGroupInvitePrivacyForbidden => {
return u instanceof GramJs.UpdateGroupInvitePrivacyForbidden;
},
);
if (eligibleUpdates.length === 0) {
return undefined;
}
return eligibleUpdates
.map((u) => buildApiPeerId(u.userId, 'user'));
}

View File

@ -674,6 +674,11 @@ export type ApiUpdateNewAuthorization = {
location?: string; location?: string;
}; };
export type ApiUpdateGroupInvitePrivacyForbidden = {
'@type': 'updateGroupInvitePrivacyForbidden';
userId: string;
};
export type ApiUpdate = ( export type ApiUpdate = (
ApiUpdateReady | ApiUpdateSession | ApiUpdateWebAuthTokenFailed | ApiUpdateRequestUserUpdate | ApiUpdateReady | ApiUpdateSession | ApiUpdateWebAuthTokenFailed | ApiUpdateRequestUserUpdate |
ApiUpdateAuthorizationState | ApiUpdateAuthorizationError | ApiUpdateConnectionState | ApiUpdateCurrentUser | ApiUpdateAuthorizationState | ApiUpdateAuthorizationError | ApiUpdateConnectionState | ApiUpdateCurrentUser |
@ -702,7 +707,7 @@ export type ApiUpdate = (
ApiUpdatePinnedTopicsOrder | ApiUpdateTopic | ApiUpdateTopics | ApiUpdateRecentEmojiStatuses | ApiUpdatePinnedTopicsOrder | ApiUpdateTopic | ApiUpdateTopics | ApiUpdateRecentEmojiStatuses |
ApiUpdateRecentReactions | ApiUpdateStory | ApiUpdateReadStories | ApiUpdateDeleteStory | ApiUpdateSentStoryReaction | ApiUpdateRecentReactions | ApiUpdateStory | ApiUpdateReadStories | ApiUpdateDeleteStory | ApiUpdateSentStoryReaction |
ApiRequestReconnectApi | ApiRequestSync | ApiUpdateFetchingDifference | ApiUpdateChannelMessages | ApiRequestReconnectApi | ApiRequestSync | ApiUpdateFetchingDifference | ApiUpdateChannelMessages |
ApiUpdateStealthMode | ApiUpdateAttachMenuBots | ApiUpdateNewAuthorization ApiUpdateStealthMode | ApiUpdateAttachMenuBots | ApiUpdateNewAuthorization | ApiUpdateGroupInvitePrivacyForbidden
); );
export type OnApiUpdate = (update: ApiUpdate) => void; export type OnApiUpdate = (update: ApiUpdate) => void;

View File

@ -81,3 +81,4 @@ export { default as Management } from '../components/right/management/Management
export { default as PaymentModal } from '../components/payment/PaymentModal'; export { default as PaymentModal } from '../components/payment/PaymentModal';
export { default as ReceiptModal } from '../components/payment/ReceiptModal'; export { default as ReceiptModal } from '../components/payment/ReceiptModal';
export { default as InviteViaLinkModal } from '../components/main/InviteViaLinkModal';

View File

@ -0,0 +1,18 @@
import type { FC } from '../../lib/teact/teact';
import React from '../../lib/teact/teact';
import type { OwnProps } from './InviteViaLinkModal';
import { Bundles } from '../../util/moduleLoader';
import useModuleLoader from '../../hooks/useModuleLoader';
const InviteViaLinkModalAsync: FC<OwnProps> = (props) => {
const { userIds, chatId } = props;
const InviteViaLinkModal = useModuleLoader(Bundles.Extra, 'InviteViaLinkModal', !(userIds && chatId));
// eslint-disable-next-line react/jsx-props-no-spreading
return InviteViaLinkModal ? <InviteViaLinkModal {...props} /> : undefined;
};
export default InviteViaLinkModalAsync;

View File

@ -0,0 +1,3 @@
.contentText {
color: var(--color-text-secondary);
}

View File

@ -0,0 +1,89 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useCallback,
useEffect,
useMemo, useState,
} from '../../lib/teact/teact';
import { getActions, getGlobal } from '../../global';
import { getUserFullName } from '../../global/helpers';
import renderText from '../common/helpers/renderText';
import useLang from '../../hooks/useLang';
import useLastCallback from '../../hooks/useLastCallback';
import Picker from '../common/Picker';
import Button from '../ui/Button';
import Modal from '../ui/Modal';
import styles from './InviteViaLinkModal.module.scss';
export type OwnProps = {
chatId?: string;
userIds?: string[];
};
const InviteViaLinkModal: FC<OwnProps> = ({
chatId, userIds,
}) => {
const { sendInviteMessages, closeInviteViaLinkModal } = getActions();
const lang = useLang();
const [selectedMemberIds, setSelectedMemberIds] = useState<string[]>([]);
useEffect(() => {
if (userIds) {
setSelectedMemberIds(userIds);
}
}, [userIds]);
const handleClose = useLastCallback(() => closeInviteViaLinkModal());
const handleClickSkip = useLastCallback(() => closeInviteViaLinkModal());
const handleClickSendInviteLink = useCallback(() => {
sendInviteMessages({ chatId: chatId!, userIds: selectedMemberIds! });
closeInviteViaLinkModal();
}, [selectedMemberIds, chatId]);
const userNames = useMemo(() => {
const usersById = getGlobal().users.byId;
return userIds?.map((userId) => getUserFullName(usersById[userId])).join(', ');
}, [userIds]);
return (
<Modal
isOpen={Boolean(userIds && chatId)}
title={lang('SendInviteLink.InviteTitle')}
onClose={handleClose}
isSlim
>
<p className={styles.contentText}>
{renderText(lang('SendInviteLink.TextAvailableSingleUser', userNames), ['simple_markdown'])}
</p>
<Picker
itemIds={userIds!}
selectedIds={selectedMemberIds ?? []}
onSelectedIdsChange={setSelectedMemberIds}
/>
<div className="dialog-buttons">
<Button
className="confirm-dialog-button"
isText
onClick={handleClickSendInviteLink}
disabled={!selectedMemberIds?.length}
>
{lang('SendInviteLink.ActionInvite')}
</Button>
<Button
className="confirm-dialog-button"
isText
onClick={handleClickSkip}
>
{lang('SendInviteLink.ActionSkip')}
</Button>
</div>
</Modal>
);
};
export default memo(InviteViaLinkModal);

View File

@ -96,6 +96,7 @@ import DraftRecipientPicker from './DraftRecipientPicker.async';
import ForwardRecipientPicker from './ForwardRecipientPicker.async'; import ForwardRecipientPicker from './ForwardRecipientPicker.async';
import GameModal from './GameModal'; import GameModal from './GameModal';
import HistoryCalendar from './HistoryCalendar.async'; import HistoryCalendar from './HistoryCalendar.async';
import InviteViaLinkModal from './InviteViaLinkModal.async';
import NewContactModal from './NewContactModal.async'; import NewContactModal from './NewContactModal.async';
import Notifications from './Notifications.async'; import Notifications from './Notifications.async';
import PremiumLimitReachedModal from './premium/common/PremiumLimitReachedModal.async'; import PremiumLimitReachedModal from './premium/common/PremiumLimitReachedModal.async';
@ -161,6 +162,7 @@ type StateProps = {
noRightColumnAnimation?: boolean; noRightColumnAnimation?: boolean;
withInterfaceAnimations?: boolean; withInterfaceAnimations?: boolean;
isSynced?: boolean; isSynced?: boolean;
inviteViaLinkModal?: TabState['inviteViaLinkModal'];
}; };
const APP_OUTDATED_TIMEOUT_MS = 5 * 60 * 1000; // 5 min const APP_OUTDATED_TIMEOUT_MS = 5 * 60 * 1000; // 5 min
@ -222,6 +224,7 @@ const Main: FC<OwnProps & StateProps> = ({
boostModal, boostModal,
noRightColumnAnimation, noRightColumnAnimation,
isSynced, isSynced,
inviteViaLinkModal,
}) => { }) => {
const { const {
initMain, initMain,
@ -586,6 +589,7 @@ const Main: FC<OwnProps & StateProps> = ({
<ReceiptModal isOpen={isReceiptModalOpen} onClose={clearReceipt} /> <ReceiptModal isOpen={isReceiptModalOpen} onClose={clearReceipt} />
<DeleteFolderDialog folder={deleteFolderDialog} /> <DeleteFolderDialog folder={deleteFolderDialog} />
<ReactionPicker isOpen={isReactionPickerOpen} /> <ReactionPicker isOpen={isReactionPickerOpen} />
<InviteViaLinkModal userIds={inviteViaLinkModal?.restrictedUserIds} chatId={inviteViaLinkModal?.chatId} />
</div> </div>
); );
}; };
@ -629,6 +633,7 @@ export default memo(withGlobal<OwnProps>(
chatlistModal, chatlistModal,
boostModal, boostModal,
giftCodeModal, giftCodeModal,
inviteViaLinkModal,
} = selectTabState(global); } = selectTabState(global);
const { chatId: audioChatId, messageId: audioMessageId } = audioPlayer; const { chatId: audioChatId, messageId: audioMessageId } = audioPlayer;
@ -696,6 +701,7 @@ export default memo(withGlobal<OwnProps>(
giftCodeModal, giftCodeModal,
noRightColumnAnimation, noRightColumnAnimation,
isSynced: global.isSynced, isSynced: global.isSynced,
inviteViaLinkModal,
}; };
}, },
)(Main)); )(Main));

View File

@ -53,6 +53,7 @@ import {
addMessages, addMessages,
addUsers, addUsers,
addUserStatuses, addUserStatuses,
addUsersToRestrictedInviteList,
deleteTopic, deleteTopic,
leaveChat, leaveChat,
replaceChatFullInfo, replaceChatFullInfo,
@ -436,9 +437,11 @@ addActionHandler('createChannel', async (global, actions, payload): Promise<void
setGlobal(global); setGlobal(global);
let createdChannel: ApiChat | undefined; let createdChannel: ApiChat | undefined;
let restrictedUserIds: string[] | undefined;
try { try {
createdChannel = await callApi('createChannel', { title, about, users }); const result = await callApi('createChannel', { title, about, users });
createdChannel = result?.channel;
restrictedUserIds = result?.restrictedUserIds;
} catch (error) { } catch (error) {
global = getGlobal(); global = getGlobal();
@ -474,6 +477,12 @@ addActionHandler('createChannel', async (global, actions, payload): Promise<void
setGlobal(global); setGlobal(global);
actions.openChat({ id: channelId, shouldReplaceHistory: true, tabId }); actions.openChat({ id: channelId, shouldReplaceHistory: true, tabId });
if (restrictedUserIds) {
global = getGlobal();
global = addUsersToRestrictedInviteList(global, restrictedUserIds, channelId, tabId);
setGlobal(global);
}
if (channelId && accessHash && photo) { if (channelId && accessHash && photo) {
await callApi('editChatPhoto', { chatId: channelId, accessHash, photo }); await callApi('editChatPhoto', { chatId: channelId, accessHash, photo });
} }
@ -593,17 +602,19 @@ addActionHandler('createGroupChat', async (global, actions, payload): Promise<vo
}, tabId); }, tabId);
setGlobal(global); setGlobal(global);
let createdChatId: string | undefined;
try { try {
const createdChat = await callApi('createGroupChat', { const { chat: createdChat, restrictedUserIds } = await callApi('createGroupChat', {
title, title,
users, users,
}); }) ?? {};
if (!createdChat) { if (!createdChat) {
return; return;
} }
const { id: chatId } = createdChat; const { id: chatId } = createdChat;
createdChatId = chatId;
global = getGlobal(); global = getGlobal();
global = updateChat(global, chatId, createdChat); global = updateChat(global, chatId, createdChat);
@ -619,6 +630,11 @@ addActionHandler('createGroupChat', async (global, actions, payload): Promise<vo
shouldReplaceHistory: true, shouldReplaceHistory: true,
tabId, tabId,
}); });
if (restrictedUserIds) {
global = getGlobal();
global = addUsersToRestrictedInviteList(global, restrictedUserIds, chatId, tabId);
setGlobal(global);
}
if (chatId && photo) { if (chatId && photo) {
await callApi('editChatPhoto', { await callApi('editChatPhoto', {
@ -626,8 +642,8 @@ addActionHandler('createGroupChat', async (global, actions, payload): Promise<vo
photo, photo,
}); });
} }
} catch (e: any) { } catch (err) {
if (e.message === 'USERS_TOO_FEW') { if ((err as ApiError).message === 'USERS_TOO_FEW') {
global = getGlobal(); global = getGlobal();
global = updateTabState(global, { global = updateTabState(global, {
chatCreation: { chatCreation: {
@ -637,6 +653,10 @@ addActionHandler('createGroupChat', async (global, actions, payload): Promise<vo
}, },
}, tabId); }, tabId);
setGlobal(global); setGlobal(global);
} else if ((err as ApiError).message === 'USER_PRIVACY_RESTRICTED') {
global = getGlobal();
global = addUsersToRestrictedInviteList(global, users.map(({ id }) => id), createdChatId!, tabId);
setGlobal(global);
} }
} }
}); });
@ -1645,7 +1665,12 @@ addActionHandler('addChatMembers', async (global, actions, payload): Promise<voi
} }
actions.setNewChatMembersDialogState({ newChatMembersProgress: NewChatMembersProgress.Loading, tabId }); actions.setNewChatMembersDialogState({ newChatMembersProgress: NewChatMembersProgress.Loading, tabId });
await callApi('addChatMembers', chat, users); const restrictedUserIds = await callApi('addChatMembers', chat, users);
if (restrictedUserIds) {
global = getGlobal();
global = addUsersToRestrictedInviteList(global, restrictedUserIds, chat.id, tabId);
setGlobal(global);
}
actions.setNewChatMembersDialogState({ newChatMembersProgress: NewChatMembersProgress.Closed, tabId }); actions.setNewChatMembersDialogState({ newChatMembersProgress: NewChatMembersProgress.Closed, tabId });
global = getGlobal(); global = getGlobal();
loadFullChat(global, actions, chat, tabId); loadFullChat(global, actions, chat, tabId);

View File

@ -79,6 +79,7 @@ import {
import { updateTabState } from '../../reducers/tabs'; import { updateTabState } from '../../reducers/tabs';
import { import {
selectChat, selectChat,
selectChatFullInfo,
selectChatMessage, selectChatMessage,
selectCurrentChat, selectCurrentChat,
selectCurrentMessageList, selectCurrentMessageList,
@ -360,6 +361,33 @@ addActionHandler('sendMessage', (global, actions, payload): ActionReturnType =>
return undefined; return undefined;
}); });
addActionHandler('sendInviteMessages', async (global, actions, payload): Promise<void> => {
const { chatId, userIds, tabId = getCurrentTabId() } = payload;
const chatFullInfo = selectChatFullInfo(global, chatId);
if (!chatFullInfo?.inviteLink) {
return undefined;
}
const userFullNames: string[] = [];
await Promise.all(userIds.map((userId) => {
const chat = selectChat(global, userId);
if (!chat) {
return undefined;
}
const userFullName = getUserFullName(selectUser(global, userId));
if (userFullName) {
userFullNames.push(userFullName);
}
return sendMessage(global, {
chat,
text: chatFullInfo.inviteLink,
});
}));
return actions.showNotification({
message: translate('Conversation.ShareLinkTooltip.Chat.One', userFullNames.join(', ')),
tabId,
});
});
addActionHandler('editMessage', (global, actions, payload): ActionReturnType => { addActionHandler('editMessage', (global, actions, payload): ActionReturnType => {
const { const {
messageList, text, entities, tabId = getCurrentTabId(), messageList, text, entities, tabId = getCurrentTabId(),

View File

@ -723,6 +723,13 @@ addActionHandler('updatePageTitle', (global, actions, payload): ActionReturnType
setPageTitleInstant(IS_ELECTRON ? '' : PAGE_TITLE); setPageTitleInstant(IS_ELECTRON ? '' : PAGE_TITLE);
}); });
addActionHandler('closeInviteViaLinkModal', (global, actions, payload): ActionReturnType => {
const { tabId = getCurrentTabId() } = payload ?? {};
return updateTabState(global, {
inviteViaLinkModal: undefined,
}, tabId);
});
let prevIsScreenLocked: boolean | undefined; let prevIsScreenLocked: boolean | undefined;
let prevBlurredTabsCount: number = 0; let prevBlurredTabsCount: number = 0;
let onlineTimeout: number | undefined; let onlineTimeout: number | undefined;

View File

@ -3,7 +3,7 @@ import type { GlobalState, TabArgs, TabState } from '../types';
import { areDeepEqual } from '../../util/areDeepEqual'; import { areDeepEqual } from '../../util/areDeepEqual';
import { getCurrentTabId } from '../../util/establishMultitabRole'; import { getCurrentTabId } from '../../util/establishMultitabRole';
import { omit, pick } from '../../util/iteratees'; import { omit, pick, unique } from '../../util/iteratees';
import { MEMO_EMPTY_ARRAY } from '../../util/memo'; import { MEMO_EMPTY_ARRAY } from '../../util/memo';
import { selectTabState } from '../selectors'; import { selectTabState } from '../selectors';
import { updateChat } from './chats'; import { updateChat } from './chats';
@ -263,3 +263,19 @@ export function closeNewContactDialog<T extends GlobalState>(
newContact: undefined, newContact: undefined,
}, tabId); }, tabId);
} }
export function addUsersToRestrictedInviteList<T extends GlobalState>(
global: T,
userIds: string[],
chatId: string,
...[tabId = getCurrentTabId()]: TabArgs<T>
): T {
const { inviteViaLinkModal } = selectTabState(global, tabId);
return updateTabState(global, {
inviteViaLinkModal: {
...inviteViaLinkModal,
restrictedUserIds: unique([...inviteViaLinkModal?.restrictedUserIds ?? [], ...userIds]),
chatId,
},
}, tabId);
}

View File

@ -641,6 +641,11 @@ export type TabState = {
slug: string; slug: string;
info: ApiCheckedGiftCode; info: ApiCheckedGiftCode;
}; };
inviteViaLinkModal?: {
restrictedUserIds: string[];
chatId: string;
};
}; };
export type GlobalState = { export type GlobalState = {
@ -1278,6 +1283,10 @@ export interface ActionPayloads {
messageList?: MessageList; messageList?: MessageList;
isReaction?: true; // Reaction to the story are sent in the form of a message isReaction?: true; // Reaction to the story are sent in the form of a message
} & WithTabId; } & WithTabId;
sendInviteMessages: {
chatId: string;
userIds: string[];
} & WithTabId;
cancelSendingMessage: { cancelSendingMessage: {
chatId: string; chatId: string;
messageId: number; messageId: number;
@ -2578,6 +2587,7 @@ export interface ActionPayloads {
dismissNotification: { localId: string } & WithTabId; dismissNotification: { localId: string } & WithTabId;
updatePageTitle: WithTabId | undefined; updatePageTitle: WithTabId | undefined;
closeInviteViaLinkModal: WithTabId | undefined;
// Calls // Calls
joinGroupCall: { joinGroupCall: {