Invite restricted users via link (#3982)
This commit is contained in:
parent
0e5660abd7
commit
4d70ae306d
@ -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'));
|
||||||
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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';
|
||||||
|
|||||||
18
src/components/main/InviteViaLinkModal.async.tsx
Normal file
18
src/components/main/InviteViaLinkModal.async.tsx
Normal 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;
|
||||||
3
src/components/main/InviteViaLinkModal.module.scss
Normal file
3
src/components/main/InviteViaLinkModal.module.scss
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.contentText {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
89
src/components/main/InviteViaLinkModal.tsx
Normal file
89
src/components/main/InviteViaLinkModal.tsx
Normal 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);
|
||||||
@ -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));
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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(),
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
|
|||||||
@ -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: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user