diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 2ae92d25c6..7babf274f6 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -6508,6 +6508,10 @@ "messageformat": "Unblock group", "description": "This is a button to unblock a group" }, + "icu:ConversationDetailsActions--report-spam": { + "messageformat": "Report spam", + "description": "This is a button to report spam" + }, "icu:ConversationDetailsActions--archive": { "messageformat": "Archive chat", "description": "This is a button to archive a chat" @@ -6576,6 +6580,26 @@ "messageformat": "Unblock", "description": "This is the modal button to confirm unblock of a group" }, + "icu:ConversationDetailsActions--report-spam-modal-title": { + "messageformat": "Report spam?", + "description": "This is the modal title for confirming reporting spam" + }, + "icu:ConversationDetailsActions--report-spam-modal-content-direct": { + "messageformat": "Signal will be notified that this person may be sending spam. Signal can’t see the content of any chats.", + "description": "This is the modal content for confirming reporting a 1-1 chat as spam" + }, + "icu:ConversationDetailsActions--report-spam-modal-content-group": { + "messageformat": "Signal will be notified that the person who invited you to this group may be sending spam. Signal can't see the content of any chats.", + "description": "This is the modal content for confirming reporting a group as spam" + }, + "icu:ConversationDetailsActions--report-spam-modal-report-spam": { + "messageformat": "Report spam", + "description": "This is the modal button to confirm reporting spam" + }, + "icu:ConversationDetailsActions--report-spam-modal-report-and-block": { + "messageformat": "Report and block", + "description": "This is the modal button to confirm reporting spam and block" + }, "icu:ConversationDetailsHeader--members": { "messageformat": "{number, plural, one {# member} other {# members}}", "description": "This is the number of members in a group" diff --git a/stylesheets/components/ConversationDetails.scss b/stylesheets/components/ConversationDetails.scss index e6dd7e799e..0e331a5fd9 100644 --- a/stylesheets/components/ConversationDetails.scss +++ b/stylesheets/components/ConversationDetails.scss @@ -89,6 +89,7 @@ } &__block-group, + &__report-spam, &__terminate-group, &__delete { color: variables.$color-accent-red; @@ -358,6 +359,16 @@ } } + &--spam { + &::after { + @include details-icon( + '../images/icons/v3/spam/spam.svg', + variables.$color-accent-red, + variables.$color-accent-red + ); + } + } + &--terminate { &::after { @include details-icon( diff --git a/ts/components/conversation/conversation-details/ConversationDetails.dom.stories.tsx b/ts/components/conversation/conversation-details/ConversationDetails.dom.stories.tsx index 1fa1162f27..52da7154b9 100644 --- a/ts/components/conversation/conversation-details/ConversationDetails.dom.stories.tsx +++ b/ts/components/conversation/conversation-details/ConversationDetails.dom.stories.tsx @@ -127,6 +127,7 @@ const createProps = ( setMuteExpiration: action('setMuteExpiration'), showToast: action('showToast'), userAvatarData: [], + reportSpam: action('reportSpam'), terminateGroup: action('terminateGroup'), toggleSafetyNumberModal: action('toggleSafetyNumberModal'), toggleAboutContactModal: action('toggleAboutContactModal'), diff --git a/ts/components/conversation/conversation-details/ConversationDetails.dom.tsx b/ts/components/conversation/conversation-details/ConversationDetails.dom.tsx index d2d88b149c..23a6d1050d 100644 --- a/ts/components/conversation/conversation-details/ConversationDetails.dom.tsx +++ b/ts/components/conversation/conversation-details/ConversationDetails.dom.tsx @@ -143,6 +143,7 @@ type ActionProps = { onOutgoingVideoCallInConversation: (conversationId: string) => unknown; pushPanelForConversation: PushPanelForConversationActionType; replaceAvatar: ReplaceAvatarActionType; + reportSpam: (id: string) => void; saveAvatarToDisk: SaveAvatarToDiskActionType; searchInConversation: (id: string) => unknown; setDisappearingMessages: (id: string, seconds: DurationInSeconds) => void; @@ -215,6 +216,7 @@ export function ConversationDetails({ renderChooseGroupMembersModal, renderConfirmAdditionsModal, replaceAvatar, + reportSpam, saveAvatarToDisk, searchInConversation, selectedNavTab, @@ -882,11 +884,17 @@ export function ConversationDetails({ isBlocked={Boolean(conversation.isBlocked)} isGroup={isGroup} isGroupTerminated={isGroupTerminated} + isSignalConversation={isSignalConversation} left={Boolean(conversation.left)} onArchive={onConversationArchive} onDelete={onConversationDeleteMessages} onUnarchive={onConversationUnarchive} onLeave={() => leaveGroup(conversation.id)} + onReportSpam={() => reportSpam(conversation.id)} + onReportSpamAndBlock={() => { + reportSpam(conversation.id); + blockConversation(conversation.id); + }} onTerminateGroup={() => terminateGroup(conversation.id)} /> )} diff --git a/ts/components/conversation/conversation-details/ConversationDetailsActions.dom.stories.tsx b/ts/components/conversation/conversation-details/ConversationDetailsActions.dom.stories.tsx index 4db65a40fd..a6bc174b50 100644 --- a/ts/components/conversation/conversation-details/ConversationDetailsActions.dom.stories.tsx +++ b/ts/components/conversation/conversation-details/ConversationDetailsActions.dom.stories.tsx @@ -39,10 +39,13 @@ const createProps = (overrideProps: Partial = {}): Props => ({ isGroupTerminated: isBoolean(overrideProps.isGroupTerminated) ? overrideProps.isGroupTerminated : false, + isSignalConversation: false, left: isBoolean(overrideProps.left) ? overrideProps.left : false, onArchive: action('onArchive'), onDelete: action('onDelet'), onLeave: action('onLeave'), + onReportSpamAndBlock: action('onBlockAndReportSpam'), + onReportSpam: action('onReportSpam'), onTerminateGroup: action('onTerminateGroup'), onUnarchive: action('onUnarchive'), }); diff --git a/ts/components/conversation/conversation-details/ConversationDetailsActions.dom.tsx b/ts/components/conversation/conversation-details/ConversationDetailsActions.dom.tsx index c33770afe4..1efffe4733 100644 --- a/ts/components/conversation/conversation-details/ConversationDetailsActions.dom.tsx +++ b/ts/components/conversation/conversation-details/ConversationDetailsActions.dom.tsx @@ -29,11 +29,14 @@ export type Props = { isBlocked: boolean; isGroup: boolean; isGroupTerminated: boolean; + isSignalConversation: boolean; left: boolean; onArchive: () => void; onDelete: () => void; onUnarchive: () => void; onLeave: () => void; + onReportSpam: () => void; + onReportSpamAndBlock: () => void; onTerminateGroup: () => void; }; @@ -49,11 +52,14 @@ export function ConversationDetailsActions({ isBlocked, isGroup, isGroupTerminated, + isSignalConversation, left, onArchive, onDelete, onUnarchive, onLeave, + onReportSpamAndBlock, + onReportSpam, onTerminateGroup, }: Props): React.JSX.Element { const [confirmLeave, gLeave] = useState(false); @@ -61,6 +67,7 @@ export function ConversationDetailsActions({ const [confirmGroupUnblock, gGroupUnblock] = useState(false); const [confirmDirectBlock, gDirectBlock] = useState(false); const [confirmDirectUnblock, gDirectUnblock] = useState(false); + const [confirmReportSpam, gConfirmReportSpam] = useState(false); const [promptTerminateGroup, gPromptTerminateGroup] = useState(false); const [confirmTerminateGroup, gConfirmTerminateGroup] = @@ -184,6 +191,26 @@ export function ConversationDetailsActions({ ); } + let reportSpamNode: ReactNode; + if (!isSignalConversation) { + reportSpamNode = ( + gConfirmReportSpam(true)} + icon={ + + } + label={ +
+ {i18n('icu:ConversationDetailsActions--report-spam')} +
+ } + /> + ); + } + let terminateGroupNode: ReactNode; if (canTerminateGroup) { terminateGroupNode = ( @@ -267,6 +294,7 @@ export function ConversationDetailsActions({ {blockNode} {archiveNode} {deleteNode} + {reportSpamNode} {terminateGroupNode && {terminateGroupNode}} {confirmLeave && ( @@ -379,6 +407,43 @@ export function ConversationDetailsActions({ )} + {confirmReportSpam && ( + gConfirmReportSpam(false)} + title={i18n('icu:MessageRequests--ReportAndMaybeBlockModal-title')} + > + {isGroup + ? i18n( + 'icu:ConversationDetailsActions--report-spam-modal-content-group' + ) + : i18n( + 'icu:ConversationDetailsActions--report-spam-modal-content-direct' + )} + + )} + {promptTerminateGroup && ( { @@ -3120,7 +3122,7 @@ export async function maybeUpdateGroup( }); await updateGroup( - { conversation, receivedAt, sentAt, updates }, + { conversation, receivedAt, sentAt, serverGuid, updates }, { viaFirstStorageSync } ); } catch (error) { @@ -3147,11 +3149,13 @@ async function updateGroup( conversation, receivedAt, sentAt, + serverGuid, updates, }: { conversation: ConversationModel; receivedAt?: number; sentAt?: number; + serverGuid?: string; updates: UpdatesResultType; }, { viaFirstStorageSync = false } = {} @@ -3211,6 +3215,7 @@ async function updateGroup( conversationId: conversation.id, received_at_ms: syntheticSentAt, sent_at: syntheticSentAt, + serverGuid, timestamp, }; }); diff --git a/ts/jobs/helpers/addReportSpamJob.dom.ts b/ts/jobs/helpers/addReportSpamJob.dom.ts index 067c399705..4c7b903178 100644 --- a/ts/jobs/helpers/addReportSpamJob.dom.ts +++ b/ts/jobs/helpers/addReportSpamJob.dom.ts @@ -10,25 +10,32 @@ import type { ConversationType } from '../../state/ducks/conversations.preload.t const log = createLogger('addReportSpamJob'); +// The Aci being reported is always the directConversation's Aci. +// When groupConversationId is missing, then we report messages in the directConversation. +// When groupConversationId is specified, then we report messages or group updates +// from the Aci within that group. export async function addReportSpamJob({ - conversation, + directConversation, getMessageServerGuidsForSpam, + groupConversationId, jobQueue, }: Readonly<{ - conversation: Readonly< + directConversation: Readonly< Pick >; getMessageServerGuidsForSpam: ( - conversationId: string + conversationId: string, + sourceServiceId?: string ) => Promise>; + groupConversationId?: string; jobQueue: Pick; }>): Promise { assertDev( - isDirectConversation(conversation), + isDirectConversation(directConversation), 'addReportSpamJob: cannot report spam for non-direct conversations' ); - const { serviceId: aci } = conversation; + const { serviceId: aci, reportingToken: token } = directConversation; if (!aci || !isAciString(aci)) { log.info( 'got a conversation with no aci, which the server does not support. Doing nothing' @@ -36,7 +43,13 @@ export async function addReportSpamJob({ return; } - const serverGuids = await getMessageServerGuidsForSpam(conversation.id); + let serverGuids: Array = []; + if (groupConversationId) { + serverGuids = await getMessageServerGuidsForSpam(groupConversationId, aci); + } else { + serverGuids = await getMessageServerGuidsForSpam(directConversation.id); + } + if (!serverGuids.length) { // This can happen under normal conditions. We haven't always stored server GUIDs, so // a user might try to report spam for a conversation that doesn't have them. (It @@ -45,5 +58,5 @@ export async function addReportSpamJob({ return; } - await jobQueue.add({ aci, serverGuids, token: conversation.reportingToken }); + await jobQueue.add({ aci, serverGuids, token }); } diff --git a/ts/messages/handleDataMessage.preload.ts b/ts/messages/handleDataMessage.preload.ts index 1895ab1018..6269005769 100644 --- a/ts/messages/handleDataMessage.preload.ts +++ b/ts/messages/handleDataMessage.preload.ts @@ -284,6 +284,7 @@ export async function handleDataMessage( newRevision: revision, receivedAt: message.get('received_at'), sentAt: message.get('sent_at'), + serverGuid: message.get('serverGuid'), }); } catch (error) { const errorText = Errors.toLogFormat(error); diff --git a/ts/sql/Interface.std.ts b/ts/sql/Interface.std.ts index 560b4871b3..4dacd62a9d 100644 --- a/ts/sql/Interface.std.ts +++ b/ts/sql/Interface.std.ts @@ -1086,7 +1086,10 @@ type ReadableInterface = { limit: number, options: { maxVersion: number } ) => Array; - getMessageServerGuidsForSpam: (conversationId: string) => Array; + getMessageServerGuidsForSpam: ( + conversationId: string, + sourceServiceId?: string + ) => Array; getJobsInQueue(queueType: string): Array; diff --git a/ts/sql/Server.node.ts b/ts/sql/Server.node.ts index 88ee5625d0..5b81e80062 100644 --- a/ts/sql/Server.node.ts +++ b/ts/sql/Server.node.ts @@ -8728,26 +8728,50 @@ export function incrementMessagesMigrationAttempts( function getMessageServerGuidsForSpam( db: ReadableDB, - conversationId: string + conversationId: string, + sourceServiceId?: string ): Array { - // The server's maximum is 3, which is why you see `LIMIT 3` in this query. Note that we - // use `pluck` here to only get the first column! + // The server's maximum is 3. + const limit = 3; + + // Group reports -- sourceServiceId should be Aci matching addedBy of user who + // added you to a group + if (sourceServiceId != null) { + return db + .prepare( + ` + SELECT DISTINCT serverGuid + FROM messages + WHERE conversationId = $conversationId + AND sourceServiceId = $sourceServiceId + AND type IS NOT 'outgoing' + AND serverGuid IS NOT NULL + ORDER BY received_at DESC, sent_at DESC + LIMIT $limit; + `, + { + pluck: true, + } + ) + .all({ conversationId, sourceServiceId, limit }); + } + return db .prepare( ` - SELECT serverGuid - FROM messages - WHERE conversationId = $conversationId - AND type = 'incoming' - AND serverGuid IS NOT NULL - ORDER BY received_at DESC, sent_at DESC - LIMIT 3; - `, + SELECT DISTINCT serverGuid + FROM messages + WHERE conversationId = $conversationId + AND type IS NOT 'outgoing' + AND serverGuid IS NOT NULL + ORDER BY received_at DESC, sent_at DESC + LIMIT $limit; + `, { pluck: true, } ) - .all({ conversationId }); + .all({ conversationId, limit }); } function getExternalFilesForConversation( diff --git a/ts/state/ducks/conversations.preload.ts b/ts/state/ducks/conversations.preload.ts index eff3ea1dc3..05662f592b 100644 --- a/ts/state/ducks/conversations.preload.ts +++ b/ts/state/ducks/conversations.preload.ts @@ -196,7 +196,7 @@ import { SettingsPage, } from '../../types/Nav.std.ts'; import { sortByMessageOrder } from '../../types/ForwardDraft.std.ts'; -import { getAddedByForOurPendingInvitation } from '../../util/getAddedByForOurPendingInvitation.preload.ts'; +import { getAddedByForGroup } from '../../util/getAddedByForGroup.preload.ts'; import { getConversationIdForLogging, getMessageIdForLogging, @@ -3783,17 +3783,27 @@ async function syncMessageRequestResponse( } } -function getConversationForReportSpam( +function getDirectConversationForReportSpam( conversation: ConversationType ): ConversationType | null { + const ourAci = itemStorage.user.getAci(); + if (conversation.type === 'group') { - const addedBy = getAddedByForOurPendingInvitation(conversation); + const addedBy = getAddedByForGroup(conversation); if (addedBy == null) { log.error( - `getConversationForReportSpam: No addedBy found for ${conversation.id}` + `getDirectConversationForReportSpam: No addedBy found for ${conversation.id}` ); return null; } + + if (addedBy.serviceId === ourAci) { + log.warn( + "getDirectConversationForReportSpam: We added ourself to this group, but can't report ourself for spam." + ); + return null; + } + return addedBy; } @@ -3813,19 +3823,23 @@ function reportSpam( return; } - const conversation = getConversationForReportSpam(conversationOrGroup); + const conversationForSpam = + getDirectConversationForReportSpam(conversationOrGroup); const conversationModel = window.ConversationController.get( - conversation?.id + conversationOrGroup?.id ); - if (!conversation || !conversationModel) { + if (!conversationForSpam || !conversationModel) { log.error( - `reportSpam: Conversation for report spam not found ${conversation?.id}. Doing nothing.` + `reportSpam: Conversation for report spam not found ${conversationForSpam?.id}. Doing nothing.` ); return; } const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type; - const idForLogging = getConversationIdForLogging(conversation); + const idForLogging = getConversationIdForLogging(conversationForSpam); + const groupConversationId = isGroup(conversationOrGroup) + ? conversationOrGroup.id + : undefined; drop( longRunningTaskWrapper({ @@ -3838,9 +3852,10 @@ function reportSpam( messageRequestEnum.SPAM ), addReportSpamJob({ - conversation, + directConversation: conversationForSpam, getMessageServerGuidsForSpam: DataReader.getMessageServerGuidsForSpam, + groupConversationId, jobQueue: reportSpamJobQueue, }), ]); @@ -3871,7 +3886,7 @@ function blockAndReportSpam( } const conversationForSpam = - getConversationForReportSpam(conversationOrGroup); + getDirectConversationForReportSpam(conversationOrGroup); const conversationModel = window.ConversationController.get( conversationForSpam?.id ); @@ -3881,8 +3896,12 @@ function blockAndReportSpam( ); return; } + const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type; const idForLogging = getConversationIdForLogging(conversationOrGroup); + const groupConversationId = isGroup(conversationOrGroup) + ? conversationOrGroup.id + : undefined; if (conversationModel.getAci()) { drop( @@ -3895,13 +3914,13 @@ function blockAndReportSpam( conversationModel, messageRequestEnum.BLOCK_AND_SPAM ), - conversationForSpam != null && - addReportSpamJob({ - conversation: conversationForSpam, - getMessageServerGuidsForSpam: - DataReader.getMessageServerGuidsForSpam, - jobQueue: reportSpamJobQueue, - }), + addReportSpamJob({ + directConversation: conversationForSpam, + getMessageServerGuidsForSpam: + DataReader.getMessageServerGuidsForSpam, + groupConversationId, + jobQueue: reportSpamJobQueue, + }), ]); dispatch({ diff --git a/ts/state/smart/AboutContactModal.preload.tsx b/ts/state/smart/AboutContactModal.preload.tsx index 0f2fd2bf5d..9d1dbffed4 100644 --- a/ts/state/smart/AboutContactModal.preload.tsx +++ b/ts/state/smart/AboutContactModal.preload.tsx @@ -17,7 +17,7 @@ import type { ConversationType } from '../ducks/conversations.preload.ts'; import { useConversationsActions } from '../ducks/conversations.preload.ts'; import { useGlobalModalActions } from '../ducks/globalModals.preload.ts'; import { strictAssert } from '../../util/assert.std.ts'; -import { getAddedByForOurPendingInvitation } from '../../util/getAddedByForOurPendingInvitation.preload.ts'; +import { getAddedByForGroup } from '../../util/getAddedByForGroup.preload.ts'; import { getItems } from '../selectors/items.dom.ts'; import { isFeaturedEnabledSelector } from '../../util/isFeatureEnabled.dom.ts'; import { getCanAddLabel } from '../../types/GroupMemberLabels.std.ts'; @@ -38,7 +38,7 @@ function isFromOrAddedByTrustedContact( return Boolean(conversation.name) || Boolean(conversation.profileSharing); } - const addedByConv = getAddedByForOurPendingInvitation(conversation); + const addedByConv = getAddedByForGroup(conversation); if (!addedByConv) { return false; } diff --git a/ts/state/smart/CompositionArea.preload.tsx b/ts/state/smart/CompositionArea.preload.tsx index e7bb83fc1b..fb1742a6e7 100644 --- a/ts/state/smart/CompositionArea.preload.tsx +++ b/ts/state/smart/CompositionArea.preload.tsx @@ -11,7 +11,7 @@ import type { } from '../../types/BodyRange.std.ts'; import { hydrateRanges } from '../../util/BodyRange.node.ts'; import { strictAssert } from '../../util/assert.std.ts'; -import { getAddedByForOurPendingInvitation } from '../../util/getAddedByForOurPendingInvitation.preload.ts'; +import { getAddedByForGroup } from '../../util/getAddedByForGroup.preload.ts'; import { AutoSubstituteAsciiEmojis } from '../../quill/auto-substitute-ascii-emojis/index.dom.tsx'; import { imageToBlurHash } from '../../util/imageToBlurHash.dom.ts'; import { isConversationSMSOnly } from '../../util/isConversationSMSOnly.std.ts'; @@ -151,7 +151,7 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({ const addedBy = useMemo(() => { if (conversation.type === 'group') { - return getAddedByForOurPendingInvitation(conversation); + return getAddedByForGroup(conversation); } return null; }, [conversation]); diff --git a/ts/state/smart/ConversationDetails.preload.tsx b/ts/state/smart/ConversationDetails.preload.tsx index 24cf899fc3..3a9a7f9a6b 100644 --- a/ts/state/smart/ConversationDetails.preload.tsx +++ b/ts/state/smart/ConversationDetails.preload.tsx @@ -133,6 +133,7 @@ export const SmartConversationDetails = memo(function SmartConversationDetails({ onArchive, onMoveToInbox, replaceAvatar, + reportSpam, saveAvatarToDisk, setDisappearingMessages, setMuteExpiration, @@ -295,6 +296,7 @@ export const SmartConversationDetails = memo(function SmartConversationDetails({ renderChooseGroupMembersModal={renderChooseGroupMembersModal} renderConfirmAdditionsModal={renderConfirmAdditionsModal} replaceAvatar={replaceAvatar} + reportSpam={reportSpam} saveAvatarToDisk={saveAvatarToDisk} searchInConversation={searchInConversation} selectedNavTab={selectedNavTab} diff --git a/ts/state/smart/ConversationHeader.preload.tsx b/ts/state/smart/ConversationHeader.preload.tsx index 30e1b1cb6b..c362c1a27b 100644 --- a/ts/state/smart/ConversationHeader.preload.tsx +++ b/ts/state/smart/ConversationHeader.preload.tsx @@ -14,7 +14,7 @@ import { CallMode } from '../../types/CallDisposition.std.ts'; import { PanelType } from '../../types/Panels.std.ts'; import { StoryViewModeType } from '../../types/Stories.std.ts'; import { strictAssert } from '../../util/assert.std.ts'; -import { getAddedByForOurPendingInvitation } from '../../util/getAddedByForOurPendingInvitation.preload.ts'; +import { getAddedByForGroup } from '../../util/getAddedByForGroup.preload.ts'; import { getGroupMemberships } from '../../util/getGroupMemberships.dom.ts'; import { isConversationSMSOnly } from '../../util/isConversationSMSOnly.std.ts'; import { isGroupOrAdhocCallState } from '../../util/isGroupOrAdhocCall.std.ts'; @@ -175,7 +175,7 @@ export const SmartConversationHeader = memo(function SmartConversationHeader({ const addedBy = useMemo(() => { if (conversation.type === 'group') { - return getAddedByForOurPendingInvitation(conversation); + return getAddedByForGroup(conversation); } return null; }, [conversation]); diff --git a/ts/state/smart/HeroRow.preload.tsx b/ts/state/smart/HeroRow.preload.tsx index 09cde23fb0..a65841936c 100644 --- a/ts/state/smart/HeroRow.preload.tsx +++ b/ts/state/smart/HeroRow.preload.tsx @@ -20,7 +20,7 @@ import { } from '../ducks/conversations.preload.ts'; import { useGlobalModalActions } from '../ducks/globalModals.preload.ts'; import { useStoriesActions } from '../ducks/stories.preload.ts'; -import { getAddedByForOurPendingInvitation } from '../../util/getAddedByForOurPendingInvitation.preload.ts'; +import { getAddedByForGroup } from '../../util/getAddedByForGroup.preload.ts'; import { getGroupMemberships } from '../../util/getGroupMemberships.dom.ts'; import { useNavActions } from '../ducks/nav.std.ts'; @@ -35,7 +35,7 @@ function isFromOrAddedByTrustedContact( return Boolean(conversation.name) || Boolean(conversation.profileSharing); } - const addedByConv = getAddedByForOurPendingInvitation(conversation); + const addedByConv = getAddedByForGroup(conversation); if (!addedByConv) { return false; } diff --git a/ts/state/smart/MessageRequestActionsConfirmation.preload.tsx b/ts/state/smart/MessageRequestActionsConfirmation.preload.tsx index c5d7500607..a135dd6817 100644 --- a/ts/state/smart/MessageRequestActionsConfirmation.preload.tsx +++ b/ts/state/smart/MessageRequestActionsConfirmation.preload.tsx @@ -12,7 +12,7 @@ import { MessageRequestState, } from '../../components/conversation/MessageRequestActionsConfirmation.dom.tsx'; import { useContactNameData } from '../../components/conversation/ContactName.dom.tsx'; -import { getAddedByForOurPendingInvitation } from '../../util/getAddedByForOurPendingInvitation.preload.ts'; +import { getAddedByForGroup } from '../../util/getAddedByForGroup.preload.ts'; import { strictAssert } from '../../util/assert.std.ts'; import { useGlobalModalActions } from '../ducks/globalModals.preload.ts'; @@ -31,7 +31,7 @@ export const SmartMessageRequestActionsConfirmation = memo( const conversation = getConversation(conversationId); const addedBy = useMemo(() => { if (conversation.type === 'group') { - return getAddedByForOurPendingInvitation(conversation); + return getAddedByForGroup(conversation); } return null; }, [conversation]); diff --git a/ts/test-node/jobs/helpers/addReportSpamJob_test.dom.ts b/ts/test-node/jobs/helpers/addReportSpamJob_test.dom.ts index 53faa97cf6..ce2b18113f 100644 --- a/ts/test-node/jobs/helpers/addReportSpamJob_test.dom.ts +++ b/ts/test-node/jobs/helpers/addReportSpamJob_test.dom.ts @@ -32,7 +32,7 @@ describe('addReportSpamJob', () => { it('does nothing if the conversation lacks a serviceId', async () => { await addReportSpamJob({ - conversation: { + directConversation: { ...conversation, serviceId: undefined, }, @@ -48,7 +48,7 @@ describe('addReportSpamJob', () => { getMessageServerGuidsForSpam.resolves([]); await addReportSpamJob({ - conversation, + directConversation: conversation, getMessageServerGuidsForSpam, jobQueue, }); @@ -58,7 +58,7 @@ describe('addReportSpamJob', () => { it('enqueues a job without a token', async () => { await addReportSpamJob({ - conversation, + directConversation: conversation, getMessageServerGuidsForSpam, jobQueue, }); @@ -76,7 +76,7 @@ describe('addReportSpamJob', () => { it('enqueues a job with a token', async () => { await addReportSpamJob({ - conversation: { + directConversation: { ...conversation, reportingToken: 'uvw', }, @@ -94,4 +94,23 @@ describe('addReportSpamJob', () => { token: 'uvw', }); }); + + it('calls getMessageServerGuidsForSpam with groupConversationId', async () => { + await addReportSpamJob({ + directConversation: { + ...conversation, + reportingToken: 'uvw', + }, + getMessageServerGuidsForSpam, + groupConversationId: 'group', + jobQueue, + }); + + sinon.assert.calledOnce(getMessageServerGuidsForSpam); + sinon.assert.calledWith( + getMessageServerGuidsForSpam, + 'group', + conversation.serviceId + ); + }); }); diff --git a/ts/util/getAddedByForOurPendingInvitation.preload.ts b/ts/util/getAddedByForGroup.preload.ts similarity index 56% rename from ts/util/getAddedByForOurPendingInvitation.preload.ts rename to ts/util/getAddedByForGroup.preload.ts index 568429ca8a..7bfb6076e3 100644 --- a/ts/util/getAddedByForOurPendingInvitation.preload.ts +++ b/ts/util/getAddedByForGroup.preload.ts @@ -3,16 +3,23 @@ import type { ConversationType } from '../state/ducks/conversations.preload.ts'; import { itemStorage } from '../textsecure/Storage.preload.ts'; -export function getAddedByForOurPendingInvitation( +export function getAddedByForGroup( conversation: ConversationType ): ConversationType | null { const ourAci = itemStorage.user.getCheckedAci(); const ourPni = itemStorage.user.getPni(); - const addedBy = conversation.pendingMemberships?.find( + + let addedByAci; + addedByAci = conversation.pendingMemberships?.find( item => item.serviceId === ourAci || item.serviceId === ourPni )?.addedByUserId; - if (addedBy == null) { - return null; + + if (addedByAci == null) { + const conversationModel = window.ConversationController.get( + conversation.id + ); + addedByAci = conversationModel?.attributes.addedBy; } - return window.ConversationController.get(addedBy)?.format() ?? null; + + return window.ConversationController.get(addedByAci)?.format() ?? null; }