Composer: Support sending message as channels (#1604)

This commit is contained in:
Alexander Zinchuk 2021-12-31 18:17:46 +01:00
parent a65e6c5af9
commit 72ae33139a
20 changed files with 452 additions and 18 deletions

View File

@ -22,6 +22,7 @@ import {
ApiThreadInfo,
ApiInvoice,
ApiGroupCall,
ApiUser,
ApiSponsoredMessage,
} from '../../types';
@ -857,6 +858,7 @@ export function buildLocalMessage(
poll?: ApiNewPoll,
groupedId?: string,
scheduledAt?: number,
sendAs?: ApiChat | ApiUser,
serverTimeOffset = 0,
): ApiMessage {
const localId = localMessageCounter++;
@ -880,7 +882,7 @@ export function buildLocalMessage(
},
date: scheduledAt || Math.round(Date.now() / 1000) + serverTimeOffset,
isOutgoing: !isChannel,
senderId: currentUserId,
senderId: sendAs?.id || currentUserId,
...(replyingTo && { replyToMessageId: replyingTo }),
...(groupedId && {
groupedId,

View File

@ -97,12 +97,13 @@ export async function fetchInlineBotResults({
}
export async function sendInlineBotResult({
chat, resultId, queryId, replyingTo,
chat, resultId, queryId, replyingTo, sendAs,
}: {
chat: ApiChat;
resultId: string;
queryId: string;
replyingTo?: number;
sendAs?: ApiUser | ApiChat;
}) {
const randomId = generateRandomBigInt();
@ -113,6 +114,7 @@ export async function sendInlineBotResult({
peer: buildInputPeer(chat.id, chat.accessHash),
id: resultId,
...(replyingTo && { replyToMsgId: replyingTo }),
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
}), true);
}

View File

@ -402,6 +402,7 @@ async function getFullChannelInfo(
hiddenPrehistory,
call,
botInfo,
defaultSendAs,
} = result.fullChat;
const inviteLink = exportedInvite instanceof GramJs.ChatInviteExported
@ -452,6 +453,7 @@ async function getFullChannelInfo(
groupCallId: call ? String(call.id) : undefined,
linkedChatId: linkedChatId ? buildApiPeerId(linkedChatId, 'chat') : undefined,
botCommands,
sendAsId: defaultSendAs ? getApiChatIdFromMtpPeer(defaultSendAs) : undefined,
},
users: [...(users || []), ...(bannedUsers || []), ...(adminUsers || [])],
groupCall: call ? {

View File

@ -22,7 +22,8 @@ export {
markMessageListRead, markMessagesRead, requestThreadInfoUpdate, searchMessagesLocal, searchMessagesGlobal,
fetchWebPagePreview, editMessage, forwardMessages, loadPollOptionResults, sendPollVote, findFirstMessageIdAfterDate,
fetchPinnedMessages, fetchScheduledHistory, sendScheduledMessages, rescheduleMessage, deleteScheduledMessages,
reportMessages, sendMessageAction, fetchSeenBy, fetchSponsoredMessages, viewSponsoredMessage,
reportMessages, sendMessageAction, fetchSeenBy, fetchSponsoredMessages, viewSponsoredMessage, fetchSendAs,
saveDefaultSendAs,
} from './messages';
export {

View File

@ -52,10 +52,15 @@ import {
import localDb from '../localDb';
import { buildApiChatFromPreview } from '../apiBuilders/chats';
import { fetchFile } from '../../../util/files';
import { addMessageToLocalDb, deserializeBytes, resolveMessageApiChatId } from '../helpers';
import {
addEntitiesWithPhotosToLocalDb,
addMessageToLocalDb,
deserializeBytes,
resolveMessageApiChatId,
} from '../helpers';
import { interpolateArray } from '../../../util/waveform';
import { requestChatUpdate } from './chats';
import { buildApiPeerId } from '../apiBuilders/peers';
import { buildApiPeerId, getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
const FAST_SEND_TIMEOUT = 1000;
const INPUT_WAVEFORM_LENGTH = 63;
@ -200,6 +205,7 @@ export function sendMessage(
scheduledAt,
groupedId,
noWebPage,
sendAs,
serverTimeOffset,
}: {
chat: ApiChat;
@ -214,12 +220,14 @@ export function sendMessage(
scheduledAt?: number;
groupedId?: string;
noWebPage?: boolean;
sendAs?: ApiUser | ApiChat;
serverTimeOffset?: number;
},
onProgress?: ApiOnProgress,
) {
const localMessage = buildLocalMessage(
chat, text, entities, replyingTo, attachment, sticker, gif, poll, groupedId, scheduledAt, serverTimeOffset,
chat, text, entities, replyingTo, attachment, sticker, gif, poll, groupedId, scheduledAt,
sendAs, serverTimeOffset,
);
onUpdate({
'@type': localMessage.isScheduled ? 'newScheduledMessage' : 'newMessage',
@ -289,6 +297,7 @@ export function sendMessage(
...(replyingTo && { replyToMsgId: replyingTo }),
...(media && { media }),
...(noWebPage && { noWebpage: noWebPage }),
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
}), true);
})();
@ -310,6 +319,7 @@ function sendGroupedMedia(
groupedId,
isSilent,
scheduledAt,
sendAs,
}: {
chat: ApiChat;
text?: string;
@ -319,6 +329,7 @@ function sendGroupedMedia(
groupedId: string;
isSilent?: boolean;
scheduledAt?: number;
sendAs?: ApiUser | ApiChat;
},
randomId: GramJs.long,
localMessage: ApiMessage,
@ -391,6 +402,7 @@ function sendGroupedMedia(
replyToMsgId: replyingTo,
...(isSilent && { silent: isSilent }),
...(scheduledAt && { scheduleDate: scheduledAt }),
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
}), true);
})();
@ -1052,6 +1064,7 @@ export async function forwardMessages({
serverTimeOffset,
isSilent,
scheduledAt,
sendAs,
}: {
fromChat: ApiChat;
toChat: ApiChat;
@ -1059,6 +1072,7 @@ export async function forwardMessages({
serverTimeOffset: number;
isSilent?: boolean;
scheduledAt?: number;
sendAs?: ApiUser | ApiChat;
}) {
const messageIds = messages.map(({ id }) => id);
const randomIds = messages.map(generateRandomBigInt);
@ -1082,6 +1096,7 @@ export async function forwardMessages({
id: messageIds,
...(isSilent && { sil2ent: isSilent }),
...(scheduledAt && { scheduleDate: scheduledAt }),
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
}), true);
}
@ -1208,6 +1223,43 @@ export async function fetchSeenBy({ chat, messageId }: { chat: ApiChat; messageI
return result ? result.map(String) : undefined;
}
export async function fetchSendAs({
chat,
}: {
chat: ApiChat;
}) {
const result = await invokeRequest(new GramJs.channels.GetSendAs({
peer: buildInputPeer(chat.id, chat.accessHash),
}));
if (!result) {
return undefined;
}
addEntitiesWithPhotosToLocalDb(result.users);
addEntitiesWithPhotosToLocalDb(result.chats);
const users = result.users.map(buildApiUser).filter<ApiUser>(Boolean as any);
const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter<ApiChat>(Boolean as any);
return {
users,
chats,
ids: result.peers.map(getApiChatIdFromMtpPeer),
};
}
export function saveDefaultSendAs({
sendAs, chat,
}: {
sendAs: ApiChat | ApiUser; chat: ApiChat;
}) {
return invokeRequest(new GramJs.messages.SaveDefaultSendAs({
peer: buildInputPeer(chat.id, chat.accessHash),
sendAs: buildInputPeer(sendAs.id, sendAs.accessHash),
}));
}
export async function fetchSponsoredMessages({ chat }: { chat: ApiChat }) {
const result = await invokeRequest(new GramJs.channels.GetSponsoredMessages({
channel: buildInputPeer(chat.id, chat.accessHash),

View File

@ -55,6 +55,8 @@ export interface ApiChat {
fullInfo?: ApiChatFullInfo;
// Obtained with UpdateUserTyping or UpdateChatUserTyping updates
typingStatus?: ApiTypingStatus;
sendAsIds?: string[];
}
export interface ApiTypingStatus {
@ -83,6 +85,7 @@ export interface ApiChatFullInfo {
};
linkedChatId?: string;
botCommands?: ApiBotCommand[];
sendAsId?: string;
}
export interface ApiChatMember {

View File

@ -40,6 +40,7 @@ export { default as DropArea } from '../components/middle/composer/DropArea';
export { default as TextFormatter } from '../components/middle/composer/TextFormatter';
export { default as EmojiTooltip } from '../components/middle/composer/EmojiTooltip';
export { default as InlineBotTooltip } from '../components/middle/composer/InlineBotTooltip';
export { default as SendAsMenu } from '../components/middle/composer/SendAsMenu';
export { default as RightSearch } from '../components/right/RightSearch';
export { default as StickerSearch } from '../components/right/StickerSearch';

View File

@ -33,6 +33,22 @@
--border-width: 0;
}
@keyframes show-send-as-button {
from {
width: 1rem;
transform: scale(0);
}
to {
width: 3.5rem;
transform: scale(1);
}
}
body:not(.animation-level-0) & .send-as-button {
animation: 0.25s ease-in-out forwards show-send-as-button;
}
> .Button {
flex-shrink: 0;
margin-left: .5rem;
@ -118,6 +134,10 @@
}
}
.send-as-button {
z-index: 1;
}
.mobile-symbol-menu-button {
width: 2.875rem;
height: 2.875rem;

View File

@ -96,6 +96,8 @@ import DropArea, { DropAreaState } from './DropArea.async';
import WebPagePreview from './WebPagePreview';
import Portal from '../../ui/Portal';
import CalendarModal from '../../common/CalendarModal.async';
import SendAsMenu from './SendAsMenu.async';
import Avatar from '../../common/Avatar';
import './Composer.scss';
@ -140,6 +142,9 @@ type StateProps =
inlineBots?: Record<string, false | InlineBotSettings>;
botCommands?: ApiBotCommand[] | false;
chatBotCommands?: ApiBotCommand[];
sendAsUser?: ApiUser;
sendAsChat?: ApiChat;
sendAsId?: string;
}
& Pick<GlobalState, 'connectionState'>;
@ -199,6 +204,9 @@ const Composer: FC<OwnProps & StateProps> = ({
isInlineBotLoading,
botCommands,
chatBotCommands,
sendAsUser,
sendAsChat,
sendAsId,
}) => {
const {
sendMessage,
@ -213,8 +221,8 @@ const Composer: FC<OwnProps & StateProps> = ({
openChat,
addRecentEmoji,
sendInlineBotResult,
loadSendAs,
} = getDispatch();
const lang = useLang();
// eslint-disable-next-line no-null/no-null
@ -227,6 +235,7 @@ const Composer: FC<OwnProps & StateProps> = ({
scheduledMessageArgs, setScheduledMessageArgs,
] = useState<GlobalState['messages']['contentToBeScheduled'] | undefined>();
const { width: windowWidth } = windowSize.get();
const sendAsIds = chat?.sendAsIds;
const sendMessageAction = useSendMessageAction(chatId, threadId);
// Cache for frequently updated state
@ -245,6 +254,12 @@ const Composer: FC<OwnProps & StateProps> = ({
}
}, [isReady, chatId, loadScheduledHistory, lastSyncTime, threadId]);
useEffect(() => {
if (chatId && lastSyncTime && !sendAsIds && isReady) {
loadSendAs({ chatId });
}
}, [chatId, isReady, lastSyncTime, loadSendAs, sendAsIds]);
useLayoutEffect(() => {
if (!appendixRef.current) return;
@ -264,6 +279,7 @@ const Composer: FC<OwnProps & StateProps> = ({
const [isBotCommandMenuOpen, openBotCommandMenu, closeBotCommandMenu] = useFlag();
const [isAttachMenuOpen, openAttachMenu, closeAttachMenu] = useFlag();
const [isSymbolMenuOpen, openSymbolMenu, closeSymbolMenu] = useFlag();
const [isSendAsMenuOpen, openSendAsMenu, closeSendAsMenu] = useFlag();
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
const [isSymbolMenuLoaded, onSymbolMenuLoadingComplete] = useFlag();
const [isHoverDisabled, disableHover, enableHover] = useFlag();
@ -566,8 +582,9 @@ const Composer: FC<OwnProps & StateProps> = ({
const handleActivateSymbolMenu = useCallback(() => {
closeBotCommandMenu();
closeSendAsMenu();
openSymbolMenu();
}, [closeBotCommandMenu, openSymbolMenu]);
}, [closeBotCommandMenu, closeSendAsMenu, openSymbolMenu]);
const handleStickerSelect = useCallback((sticker: ApiSticker, shouldPreserveInput = false) => {
sticker = {
@ -701,6 +718,22 @@ const Composer: FC<OwnProps & StateProps> = ({
}, MOBILE_KEYBOARD_HIDE_DELAY_MS);
}, [openSymbolMenu, closeBotCommandMenu]);
const handleSendAsMenuOpen = useCallback(() => {
const messageInput = document.getElementById(EDITABLE_INPUT_ID)!;
if (!IS_SINGLE_COLUMN_LAYOUT || messageInput !== document.activeElement) {
openSendAsMenu();
return;
}
messageInput.blur();
setTimeout(() => {
closeBotCommandMenu();
closeSymbolMenu();
openSendAsMenu();
}, MOBILE_KEYBOARD_HIDE_DELAY_MS);
}, [closeBotCommandMenu, closeSymbolMenu, openSendAsMenu]);
const handleAllScheduledClick = useCallback(() => {
openChat({ id: chatId, threadId, type: 'scheduled' });
}, [openChat, chatId, threadId]);
@ -834,6 +867,13 @@ const Composer: FC<OwnProps & StateProps> = ({
message={renderedEditedMessage}
/>
)}
<SendAsMenu
isOpen={isSendAsMenuOpen}
onClose={closeSendAsMenu}
chatId={chatId}
selectedSendAsId={sendAsId}
sendAsIds={sendAsIds}
/>
<MentionTooltip
isOpen={isMentionTooltipOpen}
onClose={closeMentionTooltip}
@ -881,6 +921,21 @@ const Composer: FC<OwnProps & StateProps> = ({
<i className="icon-bot-commands-filled" />
</ResponsiveHoverButton>
)}
{sendAsIds && (sendAsUser || sendAsChat) && (
<Button
round
color="translucent"
onClick={isSendAsMenuOpen ? closeSendAsMenu : handleSendAsMenuOpen}
ariaLabel={lang('SendMessageAsTitle')}
className="send-as-button"
>
<Avatar
user={sendAsUser}
chat={sendAsChat}
size="tiny"
/>
</Button>
)}
{IS_SINGLE_COLUMN_LAYOUT ? (
<Button
className={symbolMenuButtonClassName}
@ -1079,6 +1134,12 @@ export default memo(withGlobal<OwnProps>(
const emojiKeywords = language !== BASE_EMOJI_KEYWORD_LANG ? global.emojiKeywords[language] : undefined;
const botKeyboardMessageId = messageWithActualBotKeyboard ? messageWithActualBotKeyboard.id : undefined;
const keyboardMessage = botKeyboardMessageId ? selectChatMessage(global, chatId, botKeyboardMessageId) : undefined;
const usersById = global.users.byId;
const chatsById = global.chats.byId;
const { currentUserId } = global;
const sendAsId = chat?.fullInfo ? chat?.fullInfo?.sendAsId || currentUserId : undefined;
const sendAsUser = sendAsId ? usersById?.[sendAsId] : undefined;
const sendAsChat = !sendAsUser && sendAsId ? chatsById?.[sendAsId] : undefined;
return {
editingMessage: selectEditingMessage(global, chatId, threadId, messageListType),
@ -1106,8 +1167,8 @@ export default memo(withGlobal<OwnProps>(
stickersForEmoji: global.stickers.forEmoji.stickers,
groupChatMembers: chat?.fullInfo?.members,
topInlineBotIds: global.topInlineBots?.userIds,
currentUserId: global.currentUserId,
usersById: global.users.byId,
currentUserId,
usersById,
lastSyncTime: global.lastSyncTime,
contentToBeScheduled: global.messages.contentToBeScheduled,
shouldSuggestStickers,
@ -1119,6 +1180,9 @@ export default memo(withGlobal<OwnProps>(
isInlineBotLoading: global.inlineBots.isLoading,
chatBotCommands: chat && chat.fullInfo && chat.fullInfo.botCommands,
botCommands: chatBot && chatBot.fullInfo ? (chatBot.fullInfo.botCommands || false) : undefined,
sendAsUser,
sendAsChat,
sendAsId,
};
},
)(Composer));

View File

@ -0,0 +1,15 @@
import React, { FC, memo } from '../../../lib/teact/teact';
import { OwnProps } from './SendAsMenu';
import { Bundles } from '../../../util/moduleLoader';
import useModuleLoader from '../../../hooks/useModuleLoader';
const SendAsMenuAsync: FC<OwnProps> = (props) => {
const { isOpen } = props;
const SendAsMenu = useModuleLoader(Bundles.Extra, 'SendAsMenu', !isOpen);
// eslint-disable-next-line react/jsx-props-no-spreading
return SendAsMenu ? <SendAsMenu {...props} /> : undefined;
};
export default memo(SendAsMenuAsync);

View File

@ -0,0 +1,74 @@
.SendAsMenu {
.send-as-title {
font-weight: 500;
line-height: 1.25rem;
word-break: break-word;
margin-inline-start: 1rem;
margin-block: 0.5rem;
}
.menu-container {
width: calc(100% - 4rem);
max-width: 20rem;
max-height: 40vh;
overflow: auto;
flex-direction: column;
@media (max-width: 600px) {
width: calc(100% - 3rem);
}
}
.is-pointer-env & {
> .backdrop {
z-index: 2;
position: absolute;
top: -1rem;
left: 0;
right: auto;
width: 3.5rem;
height: 4.5rem;
}
}
}
.SendAsItem {
margin: 0 !important;
$border-size: 2px;
.Avatar.selected {
margin-right: 0.75rem;
position: relative;
width: calc(2.125rem - #{$border-size * 2});
height: calc(2.125rem - #{$border-size * 2});
&::before {
display: block;
content: "";
position: absolute;
top: #{-$border-size * 2};
left: #{-$border-size * 2};
border: $border-size solid var(--color-primary);
width: calc(100% + #{$border-size * 4});
height: calc(100% + #{$border-size * 4});
border-radius: 50%;
}
}
.ListItem-button {
padding: 0.5625rem 1rem !important;
border-radius: 0;
align-items: center;
}
.info {
margin-inline-start: 0.5rem;
}
.subtitle {
font-size: .9375rem;
color: var(--color-text-secondary);
line-height: 1.3125;
}
}

View File

@ -0,0 +1,125 @@
import React, {
FC, useCallback, useEffect, useRef, memo,
} from '../../../lib/teact/teact';
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
import { IS_TOUCH_ENV } from '../../../util/environment';
import renderText from '../../common/helpers/renderText';
import { getUserFullName, isUserId } from '../../../modules/helpers';
import useMouseInside from '../../../hooks/useMouseInside';
import useLang from '../../../hooks/useLang';
import buildClassName from '../../../util/buildClassName';
import { getDispatch, getGlobal } from '../../../lib/teact/teactn';
import ListItem from '../../ui/ListItem';
import Avatar from '../../common/Avatar';
import Menu from '../../ui/Menu';
import './SendAsMenu.scss';
export type OwnProps = {
isOpen: boolean;
onClose: () => void;
chatId?: string;
selectedSendAsId?: string;
sendAsIds?: string[];
};
const SendAsMenu: FC<OwnProps> = ({
isOpen,
onClose,
chatId,
selectedSendAsId,
sendAsIds,
}) => {
const { saveDefaultSendAs } = getDispatch();
// No need for expensive global updates on users and chats, so we avoid them
const usersById = getGlobal().users.byId;
const chatsById = getGlobal().chats.byId;
const lang = useLang();
// eslint-disable-next-line no-null/no-null
const containerRef = useRef<HTMLDivElement>(null);
const [handleMouseEnter, handleMouseLeave, markMouseInside] = useMouseInside(isOpen, onClose, undefined);
useEffect(() => {
if (isOpen) {
markMouseInside();
}
}, [isOpen, markMouseInside]);
const handleUserSelect = useCallback((id: string) => {
onClose();
saveDefaultSendAs({ chatId, sendAsId: id });
}, [chatId, onClose, saveDefaultSendAs]);
const selectedSendAsIndex = useKeyboardNavigation({
isActive: isOpen,
items: sendAsIds,
onSelect: handleUserSelect,
shouldSelectOnTab: true,
shouldSaveSelectionOnUpdateItems: true,
onClose,
});
useEffect(() => {
setTooltipItemVisible('.chat-item-clickable', selectedSendAsIndex, containerRef);
}, [selectedSendAsIndex]);
useEffect(() => {
if (sendAsIds && !sendAsIds.length) {
onClose();
}
}, [sendAsIds, onClose]);
return (
<Menu
isOpen={isOpen}
positionX="left"
positionY="bottom"
onClose={onClose}
className="SendAsMenu"
onCloseAnimationEnd={onClose}
onMouseEnter={!IS_TOUCH_ENV ? handleMouseEnter : undefined}
onMouseLeave={!IS_TOUCH_ENV ? handleMouseLeave : undefined}
noCloseOnBackdrop={!IS_TOUCH_ENV}
>
<div className="send-as-title" dir="auto">{lang('SendMessageAsTitle')}</div>
{usersById && chatsById && sendAsIds?.map((id, index) => {
const user = isUserId(id) ? usersById[id] : undefined;
const chat = !user ? chatsById[id] : undefined;
const fullName = user ? getUserFullName(user) : chat?.title;
return (
<ListItem
key={id}
className="SendAsItem chat-item-clickable scroll-item with-avatar"
onClick={() => handleUserSelect(id)}
focus={selectedSendAsIndex === index}
>
<Avatar
size="small"
user={user}
chat={chat}
className={buildClassName(selectedSendAsId === id && 'selected')}
/>
<div className="info">
<div className="title">
<h3 dir="auto">{fullName && renderText(fullName)}</h3>
</div>
<span className="subtitle">{user
? lang('VoipGroupPersonalAccount')
: lang('Subscribers', chat?.membersCount, 'i')}
</span>
</div>
</ListItem>
);
})}
</Menu>
);
};
export default memo(SendAsMenu);

View File

@ -98,7 +98,7 @@
}
}
.Button.bot-commands ~ & {
.Button.bot-commands ~ &, .Button.send-as-button ~ & {
.is-pointer-env & > .backdrop {
left: 3rem;
width: 3.25rem;

View File

@ -505,7 +505,7 @@ export type ActionTypes = (
'setReplyingToId' | 'setEditingId' | 'editLastMessage' | 'saveDraft' | 'clearDraft' | 'loadPinnedMessages' |
'toggleMessageWebPage' | 'replyToNextMessage' | 'deleteChatUser' | 'deleteChat' |
'reportMessages' | 'sendMessageAction' | 'focusNextReply' | 'openChatByInvite' | 'loadSeenBy' |
'loadSponsoredMessages' | 'viewSponsoredMessage' |
'loadSponsoredMessages' | 'viewSponsoredMessage' | 'loadSendAs' | 'saveDefaultSendAs' |
// downloads
'downloadSelectedMessages' | 'downloadMessageMedia' | 'cancelMessageMediaDownload' |
// scheduled messages

View File

@ -10,6 +10,10 @@ export default function useMouseInside(
) {
const isMouseInside = useRef(false);
const markMouseInside = useCallback(() => {
isMouseInside.current = true;
}, []);
useEffect(() => {
if (closeTimeout) {
clearTimeout(closeTimeout);
@ -44,5 +48,5 @@ export default function useMouseInside(
}, menuCloseTimeout);
}, [menuCloseTimeout, onClose]);
return [handleMouseEnter, handleMouseLeave];
return [handleMouseEnter, handleMouseLeave, markMouseInside];
}

View File

@ -1080,6 +1080,7 @@ messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Boo
messages.unpinAllMessages#f025bc8b peer:InputPeer = messages.AffectedHistory;
messages.deleteChat#5bd0ee50 chat_id:long = Bool;
messages.getMessageReadParticipants#2c6f97b7 peer:InputPeer msg_id:int = Vector<long>;
messages.saveDefaultSendAs#ccfddf96 peer:InputPeer send_as:InputPeer = Bool;
updates.getState#edd4882a = updates.State;
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
@ -1117,6 +1118,7 @@ channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector<int> = Bool
channels.togglePreHistoryHidden#eabbb94c channel:InputChannel enabled:Bool = Updates;
channels.getGroupsForDiscussion#f5dad378 = messages.Chats;
channels.setDiscussionGroup#40582bb2 broadcast:InputChannel group:InputChannel = Bool;
channels.getSendAs#dc770ee peer:InputPeer = channels.SendAsPeers;
channels.viewSponsoredMessage#beaedb94 channel:InputChannel random_id:bytes = Bool;
channels.getSponsoredMessages#ec210fbf channel:InputChannel = messages.SponsoredMessages;
payments.getPaymentForm#8a333c8d flags:# peer:InputPeer msg_id:int theme_params:flags.0?DataJSON = payments.PaymentForm;

View File

@ -1081,6 +1081,7 @@ messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Boo
messages.unpinAllMessages#f025bc8b peer:InputPeer = messages.AffectedHistory;
messages.deleteChat#5bd0ee50 chat_id:long = Bool;
messages.getMessageReadParticipants#2c6f97b7 peer:InputPeer msg_id:int = Vector<long>;
messages.saveDefaultSendAs#ccfddf96 peer:InputPeer send_as:InputPeer = Bool;
updates.getState#edd4882a = updates.State;
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
@ -1118,6 +1119,7 @@ channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector<int> = Bool
channels.togglePreHistoryHidden#eabbb94c channel:InputChannel enabled:Bool = Updates;
channels.getGroupsForDiscussion#f5dad378 = messages.Chats;
channels.setDiscussionGroup#40582bb2 broadcast:InputChannel group:InputChannel = Bool;
channels.getSendAs#dc770ee peer:InputPeer = channels.SendAsPeers;
channels.viewSponsoredMessage#beaedb94 channel:InputChannel random_id:bytes = Bool;
channels.getSponsoredMessages#ec210fbf channel:InputChannel = messages.SponsoredMessages;
payments.getPaymentForm#8a333c8d flags:# peer:InputPeer msg_id:int theme_params:flags.0?DataJSON = payments.PaymentForm;

View File

@ -2,7 +2,7 @@ import {
addReducer, getDispatch, getGlobal, setGlobal,
} from '../../../lib/teact/teactn';
import { ApiChat } from '../../../api/types';
import { ApiChat, ApiUser } from '../../../api/types';
import { InlineBotSettings } from '../../../types';
import {
@ -11,7 +11,7 @@ import {
import { callApi } from '../../../api/gramjs';
import {
selectChat, selectChatBot, selectChatMessage, selectCurrentChat, selectCurrentMessageList,
selectReplyingToId, selectUser,
selectReplyingToId, selectSendAs, selectUser,
} from '../../selectors';
import { addChats, addUsers, removeBlockedContact } from '../../reducers';
import { buildCollectionByKey } from '../../../util/iteratees';
@ -81,7 +81,9 @@ addReducer('sendBotCommand', (global, actions, payload) => {
actions.setReplyingToId({ messageId: undefined });
actions.clearWebPagePreview({ chatId: chat.id, threadId, value: false });
void sendBotCommand(chat, currentUserId, command, selectReplyingToId(global, chat.id, threadId));
void sendBotCommand(
chat, currentUserId, command, selectReplyingToId(global, chat.id, threadId), selectSendAs(global, chatId),
);
});
addReducer('restartBot', (global, actions, payload) => {
@ -100,7 +102,7 @@ addReducer('restartBot', (global, actions, payload) => {
}
setGlobal(removeBlockedContact(getGlobal(), bot.id));
void sendBotCommand(chat, currentUserId, '/start');
void sendBotCommand(chat, currentUserId, '/start', undefined, selectSendAs(global, chatId));
})();
});
@ -204,6 +206,7 @@ addReducer('sendInlineBotResult', (global, actions, payload) => {
resultId: id,
queryId,
replyingTo: selectReplyingToId(global, chatId, threadId),
sendAs: selectSendAs(global, chatId),
});
});
@ -305,11 +308,14 @@ async function searchInlineBot({
setGlobal(global);
}
async function sendBotCommand(chat: ApiChat, currentUserId: string, command: string, replyingTo?: number) {
async function sendBotCommand(
chat: ApiChat, currentUserId: string, command: string, replyingTo?: number, sendAs?: ApiChat | ApiUser,
) {
await callApi('sendMessage', {
chat,
text: command,
replyingTo,
sendAs,
});
}

View File

@ -9,6 +9,7 @@ import {
ApiNewPoll,
ApiOnProgress,
ApiSticker,
ApiUser,
ApiVideo,
MAIN_THREAD_ID,
MESSAGE_DELETED,
@ -56,6 +57,8 @@ import {
selectScheduledMessage,
selectNoWebPage,
selectFirstUnreadId,
selectUser,
selectSendAs,
selectSponsoredMessage,
} from '../../selectors';
import { debounce, rafPromise } from '../../../util/schedulers';
@ -204,6 +207,7 @@ addReducer('sendMessage', (global, actions, payload) => {
chat,
replyingTo: selectReplyingToId(global, chatId, threadId),
noWebPage: selectNoWebPage(global, chatId, threadId),
sendAs: selectSendAs(global, chatId),
};
const isSingle = !payload.attachments || payload.attachments.length <= 1;
@ -586,6 +590,7 @@ addReducer('forwardMessages', (global, action, payload) => {
}
const { isSilent, scheduledAt } = payload;
const sendAs = selectSendAs(global, toChatId!);
const realMessages = messages.filter((m) => !isServiceNotificationMessage(m));
if (realMessages.length) {
@ -596,6 +601,7 @@ addReducer('forwardMessages', (global, action, payload) => {
serverTimeOffset: getGlobal().serverTimeOffset,
isSilent,
scheduledAt,
sendAs,
});
}
@ -613,6 +619,7 @@ addReducer('forwardMessages', (global, action, payload) => {
poll,
isSilent,
scheduledAt,
sendAs,
});
});
@ -853,6 +860,7 @@ async function sendMessage(params: {
serverTimeOffset?: number;
isSilent?: boolean;
scheduledAt?: number;
sendAs?: ApiChat | ApiUser;
}) {
let localId: number | undefined;
const progressCallback = params.attachment ? (progress: number, messageLocalId: number) => {
@ -967,6 +975,47 @@ addReducer('loadSeenBy', (global, actions, payload) => {
})();
});
addReducer('saveDefaultSendAs', (global, actions, payload) => {
const { chatId, sendAsId } = payload;
const chat = selectChat(global, chatId);
const sendAsChat = selectChat(global, sendAsId) || selectUser(global, sendAsId);
if (!chat || !sendAsChat) {
return undefined;
}
void callApi('saveDefaultSendAs', { sendAs: sendAsChat, chat });
return updateChat(global, chatId, {
fullInfo: {
...chat.fullInfo,
sendAsId,
},
});
});
addReducer('loadSendAs', (global, actions, payload) => {
const { chatId } = payload;
const chat = selectChat(global, chatId);
if (!chat) {
return;
}
(async () => {
const result = await callApi('fetchSendAs', { chat });
if (!result) {
return;
}
global = getGlobal();
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
global = addChats(global, buildCollectionByKey(result.chats, 'id'));
global = updateChat(global, chatId, {
sendAsIds: result.ids,
});
setGlobal(global);
})();
});
async function loadPinnedMessages(chat: ApiChat) {
const result = await callApi('fetchPinnedMessages', { chat });
if (!result) {

View File

@ -185,3 +185,13 @@ export function selectCountNotMutedUnread(global: GlobalState) {
export function selectIsServiceChatReady(global: GlobalState) {
return Boolean(selectChat(global, SERVICE_NOTIFICATIONS_USER_ID));
}
export function selectSendAs(global: GlobalState, chatId: string) {
const chat = selectChat(global, chatId);
if (!chat) return undefined;
const id = chat?.fullInfo?.sendAsId;
if (!id) return undefined;
return selectUser(global, id) || selectChat(global, id);
}