Message: Generate sharing link on server (#4405)

This commit is contained in:
Alexander Zinchuk 2024-03-22 13:06:14 +01:00
parent b3e21e293a
commit c0e82bdd0f
9 changed files with 100 additions and 30 deletions

View File

@ -35,7 +35,7 @@ export {
reportMessages, sendMessageAction, fetchSeenBy, fetchSponsoredMessages, viewSponsoredMessage, fetchSendAs, reportMessages, sendMessageAction, fetchSeenBy, fetchSponsoredMessages, viewSponsoredMessage, fetchSendAs,
saveDefaultSendAs, fetchUnreadReactions, readAllReactions, fetchUnreadMentions, readAllMentions, transcribeAudio, saveDefaultSendAs, fetchUnreadReactions, readAllReactions, fetchUnreadMentions, readAllMentions, transcribeAudio,
closePoll, fetchExtendedMedia, translateText, fetchMessageViews, fetchDiscussionMessage, clickSponsoredMessage, closePoll, fetchExtendedMedia, translateText, fetchMessageViews, fetchDiscussionMessage, clickSponsoredMessage,
fetchOutboxReadDate, fetchOutboxReadDate, exportMessageLink,
deleteSavedHistory, deleteSavedHistory,
} from './messages'; } from './messages';

View File

@ -1927,3 +1927,21 @@ export async function fetchOutboxReadDate({ chat, messageId }: { chat: ApiChat;
return { date: result.date }; return { date: result.date };
} }
export async function exportMessageLink({
id, chat, shouldIncludeThread, shouldIncludeGrouped,
}: {
id: number;
chat: ApiChat;
shouldIncludeThread?: boolean;
shouldIncludeGrouped?: boolean;
}) {
const result = await invokeRequest(new GramJs.channels.ExportMessageLink({
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
id,
thread: shouldIncludeThread || undefined,
grouped: shouldIncludeGrouped || undefined,
}));
return result?.link;
}

View File

@ -4,11 +4,18 @@ import React, {
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../../global'; import { getActions, getGlobal, withGlobal } from '../../../global';
import type {
ApiAvailableReaction, ApiChatReactions, ApiMessage, ApiReaction, ApiStickerSet, ApiStickerSetInfo, ApiThreadInfo,
} from '../../../api/types';
import type { MessageListType, TabState } from '../../../global/types'; import type { MessageListType, TabState } from '../../../global/types';
import type { IAlbum, IAnchorPosition } from '../../../types'; import type { IAlbum, IAnchorPosition, ThreadId } from '../../../types';
import {
type ApiAvailableReaction,
type ApiChatReactions,
type ApiMessage,
type ApiReaction,
type ApiStickerSet,
type ApiStickerSetInfo,
type ApiThreadInfo,
MAIN_THREAD_ID,
} from '../../../api/types';
import { PREVIEW_AVATAR_COUNT, SERVICE_NOTIFICATIONS_USER_ID } from '../../../config'; import { PREVIEW_AVATAR_COUNT, SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
import { import {
@ -38,7 +45,6 @@ import {
selectIsPremiumPurchaseBlocked, selectIsPremiumPurchaseBlocked,
selectIsReactionPickerOpen, selectIsReactionPickerOpen,
selectMessageCustomEmojiSets, selectMessageCustomEmojiSets,
selectMessageLink,
selectMessageTranslations, selectMessageTranslations,
selectRequestedChatTranslationLanguage, selectRequestedChatTranslationLanguage,
selectRequestedMessageTranslationLanguage, selectRequestedMessageTranslationLanguage,
@ -77,6 +83,7 @@ export type OwnProps = {
}; };
type StateProps = { type StateProps = {
threadId?: ThreadId;
availableReactions?: ApiAvailableReaction[]; availableReactions?: ApiAvailableReaction[];
topReactions?: ApiReaction[]; topReactions?: ApiReaction[];
defaultTagReactions?: ApiReaction[]; defaultTagReactions?: ApiReaction[];
@ -120,13 +127,13 @@ type StateProps = {
maxUniqueReactions?: number; maxUniqueReactions?: number;
canPlayAnimatedEmojis?: boolean; canPlayAnimatedEmojis?: boolean;
isReactionPickerOpen?: boolean; isReactionPickerOpen?: boolean;
messageLink?: string;
isInSavedMessages?: boolean; isInSavedMessages?: boolean;
}; };
const selection = window.getSelection(); const selection = window.getSelection();
const ContextMenuContainer: FC<OwnProps & StateProps> = ({ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
threadId,
availableReactions, availableReactions,
topReactions, topReactions,
defaultTagReactions, defaultTagReactions,
@ -178,7 +185,6 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
canShowOriginal, canShowOriginal,
canSelectLanguage, canSelectLanguage,
isReactionPickerOpen, isReactionPickerOpen,
messageLink,
isInSavedMessages, isInSavedMessages,
onClose, onClose,
onCloseAnimationEnd, onCloseAnimationEnd,
@ -213,6 +219,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
openMessageReactionPicker, openMessageReactionPicker,
openPremiumModal, openPremiumModal,
loadOutboxReadDate, loadOutboxReadDate,
copyMessageLink,
} = getActions(); } = getActions();
const lang = useLang(); const lang = useLang();
@ -459,7 +466,12 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
}); });
const handleCopyLink = useLastCallback(() => { const handleCopyLink = useLastCallback(() => {
copyTextToClipboard(messageLink!); copyMessageLink({
chatId: message.chatId,
messageId: message.id,
shouldIncludeThread: threadId !== MAIN_THREAD_ID,
shouldIncludeGrouped: true, // TODO: Provide correct value when ability to target specific message is added
});
closeMenu(); closeMenu();
}); });
@ -728,11 +740,11 @@ export default memo(withGlobal<OwnProps>(
: undefined; : undefined;
const canTranslate = !hasTranslation && selectCanTranslateMessage(global, message, detectedLanguage); const canTranslate = !hasTranslation && selectCanTranslateMessage(global, message, detectedLanguage);
const isChatTranslated = selectRequestedChatTranslationLanguage(global, message.chatId); const isChatTranslated = selectRequestedChatTranslationLanguage(global, message.chatId);
const messageLink = selectMessageLink(global, message.chatId, threadId, message.id);
const isInSavedMessages = selectIsChatWithSelf(global, message.chatId); const isInSavedMessages = selectIsChatWithSelf(global, message.chatId);
return { return {
threadId,
availableReactions, availableReactions,
topReactions, topReactions,
defaultTagReactions: defaultTags, defaultTagReactions: defaultTags,
@ -777,7 +789,6 @@ export default memo(withGlobal<OwnProps>(
isMessageTranslated: hasTranslation, isMessageTranslated: hasTranslation,
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global), canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
isReactionPickerOpen: selectIsReactionPickerOpen(global), isReactionPickerOpen: selectIsReactionPickerOpen(global),
messageLink,
isInSavedMessages, isInSavedMessages,
}; };
}, },

View File

@ -103,11 +103,7 @@ export function getMessageCopyOptions(
options.push({ options.push({
label: 'lng_context_copy_message_link', label: 'lng_context_copy_message_link',
icon: 'link', icon: 'link',
handler: () => { handler: onCopyLink,
onCopyLink();
afterEffect?.();
},
}); });
} }

View File

@ -33,6 +33,7 @@ import {
SUPPORTED_IMAGE_CONTENT_TYPES, SUPPORTED_IMAGE_CONTENT_TYPES,
SUPPORTED_VIDEO_CONTENT_TYPES, SUPPORTED_VIDEO_CONTENT_TYPES,
} from '../../../config'; } from '../../../config';
import { copyTextToClipboard } from '../../../util/clipboard';
import { isDeepLink } from '../../../util/deepLinkParser'; import { isDeepLink } from '../../../util/deepLinkParser';
import { ensureProtocol } from '../../../util/ensureProtocol'; import { ensureProtocol } from '../../../util/ensureProtocol';
import { getCurrentTabId } from '../../../util/establishMultitabRole'; import { getCurrentTabId } from '../../../util/establishMultitabRole';
@ -53,6 +54,7 @@ import {
getIsSavedDialog, getIsSavedDialog,
getUserFullName, getUserFullName,
isChatChannel, isChatChannel,
isChatSuperGroup,
isDeletedUser, isDeletedUser,
isMessageLocal, isMessageLocal,
isServiceNotificationMessage, isServiceNotificationMessage,
@ -1900,6 +1902,49 @@ addActionHandler('loadOutboxReadDate', async (global, actions, payload): Promise
} }
}); });
addActionHandler('copyMessageLink', async (global, actions, payload): Promise<void> => {
const {
chatId, messageId, shouldIncludeThread, shouldIncludeGrouped, tabId = getCurrentTabId(),
} = payload;
const chat = selectChat(global, chatId);
if (!chat) {
actions.showNotification({
message: translate('ErrorOccurred'),
tabId,
});
return;
}
if (!isChatChannel(chat) && !isChatSuperGroup(chat)) {
actions.showNotification({
message: translate('lng_filters_link_private_error'),
tabId,
});
return;
}
const link = await callApi('exportMessageLink', {
chat,
id: messageId,
shouldIncludeThread,
shouldIncludeGrouped,
});
if (!link) {
actions.showNotification({
message: translate('ErrorOccurred'),
tabId,
});
return;
}
copyTextToClipboard(link);
actions.showNotification({
message: translate('LinkCopied'),
tabId,
});
});
function countSortedIds(ids: number[], from: number, to: number) { function countSortedIds(ids: number[], from: number, to: number) {
let count = 0; let count = 0;

View File

@ -1414,29 +1414,21 @@ export function selectCanTranslateMessage<T extends GlobalState>(
&& !chatRequestedLanguage; && !chatRequestedLanguage;
} }
export function selectMessageLink<T extends GlobalState>( export function selectTopicLink<T extends GlobalState>(
global: T, chatId: string, threadId?: ThreadId, messageId?: number, global: T, chatId: string, topicId?: ThreadId,
) { ) {
const chat = selectChat(global, chatId); const chat = selectChat(global, chatId);
if (!chat) { if (!chat || !chat?.isForum) {
return undefined; return undefined;
} }
const chatUsername = getMainUsername(chat); const chatUsername = getMainUsername(chat);
const isChannelId = isChatChannel(chat) || isChatSuperGroup(chat); const normalizedId = chatId.replace('-100', '');
const normalizedId = isChannelId ? chatId.replace('-100', '') : chatId.replace('-', '');
const chatPart = chatUsername || `c/${normalizedId}`; const chatPart = chatUsername || `c/${normalizedId}`;
const threadPart = threadId && threadId !== MAIN_THREAD_ID ? `/${threadId}` : ''; const topicPart = topicId && topicId !== MAIN_THREAD_ID ? `/${topicId}` : '';
const messagePart = messageId ? `/${messageId}` : ''; return `${TME_LINK_PREFIX}${chatPart}${topicPart}`;
return `${TME_LINK_PREFIX}${chatPart}${threadPart}${messagePart}`;
}
export function selectTopicLink<T extends GlobalState>(
global: T, chatId: string, topicId?: ThreadId,
) {
return selectMessageLink(global, chatId, topicId);
} }
export function selectMessageReplyInfo<T extends GlobalState>( export function selectMessageReplyInfo<T extends GlobalState>(

View File

@ -2054,6 +2054,12 @@ export interface ActionPayloads {
markMentionsRead: { markMentionsRead: {
messageIds: number[]; messageIds: number[];
} & WithTabId; } & WithTabId;
copyMessageLink: {
chatId: string;
messageId: number;
shouldIncludeThread?: boolean;
shouldIncludeGrouped?: boolean;
} & WithTabId;
sendPollVote: { sendPollVote: {
chatId: string; chatId: string;

View File

@ -1455,6 +1455,7 @@ channels.joinChannel#24b524c5 channel:InputChannel = Updates;
channels.leaveChannel#f836aa95 channel:InputChannel = Updates; channels.leaveChannel#f836aa95 channel:InputChannel = Updates;
channels.inviteToChannel#199f3a6c channel:InputChannel users:Vector<InputUser> = Updates; channels.inviteToChannel#199f3a6c channel:InputChannel users:Vector<InputUser> = Updates;
channels.deleteChannel#c0111fe3 channel:InputChannel = Updates; channels.deleteChannel#c0111fe3 channel:InputChannel = Updates;
channels.exportMessageLink#e63fadeb flags:# grouped:flags.0?true thread:flags.1?true channel:InputChannel id:int = ExportedMessageLink;
channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates; channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates;
channels.editBanned#96e6cd81 channel:InputChannel participant:InputPeer banned_rights:ChatBannedRights = Updates; channels.editBanned#96e6cd81 channel:InputChannel participant:InputPeer banned_rights:ChatBannedRights = Updates;
channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector<int> = Bool; channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector<int> = Bool;

View File

@ -202,6 +202,7 @@
"channels.leaveChannel", "channels.leaveChannel",
"channels.inviteToChannel", "channels.inviteToChannel",
"channels.deleteChannel", "channels.deleteChannel",
"channels.exportMessageLink",
"channels.toggleSignatures", "channels.toggleSignatures",
"channels.editBanned", "channels.editBanned",
"channels.readMessageContents", "channels.readMessageContents",