mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-04-02 00:07:56 +01:00
More backup export improvements
This commit is contained in:
@@ -1376,7 +1376,7 @@ export class BackupExportStream extends Readable {
|
|||||||
labelEmoji: null,
|
labelEmoji: null,
|
||||||
labelString: null,
|
labelString: null,
|
||||||
}),
|
}),
|
||||||
role: member.role,
|
role: member.role || SignalService.Member.Role.DEFAULT,
|
||||||
userId: this.#aciToBytes(member.aci),
|
userId: this.#aciToBytes(member.aci),
|
||||||
};
|
};
|
||||||
}) ?? null,
|
}) ?? null,
|
||||||
@@ -1385,7 +1385,7 @@ export class BackupExportStream extends Readable {
|
|||||||
return {
|
return {
|
||||||
member: {
|
member: {
|
||||||
userId: this.#serviceIdToBytes(member.serviceId),
|
userId: this.#serviceIdToBytes(member.serviceId),
|
||||||
role: member.role,
|
role: member.role || SignalService.Member.Role.DEFAULT,
|
||||||
joinedAtVersion: 0,
|
joinedAtVersion: 0,
|
||||||
labelEmoji: null,
|
labelEmoji: null,
|
||||||
labelString: null,
|
labelString: null,
|
||||||
@@ -1478,8 +1478,12 @@ export class BackupExportStream extends Readable {
|
|||||||
|
|
||||||
let authorId: bigint | undefined;
|
let authorId: bigint | undefined;
|
||||||
|
|
||||||
const isOutgoing = message.type === 'outgoing';
|
const me = this.#getOrPushPrivateRecipient({
|
||||||
const isIncoming = message.type === 'incoming';
|
serviceId: aboutMe.aci,
|
||||||
|
});
|
||||||
|
|
||||||
|
let isOutgoing = message.type === 'outgoing';
|
||||||
|
let isIncoming = message.type === 'incoming';
|
||||||
|
|
||||||
if (message.sourceServiceId && isAciString(message.sourceServiceId)) {
|
if (message.sourceServiceId && isAciString(message.sourceServiceId)) {
|
||||||
authorId = this.#getOrPushPrivateRecipient({
|
authorId = this.#getOrPushPrivateRecipient({
|
||||||
@@ -1491,23 +1495,6 @@ export class BackupExportStream extends Readable {
|
|||||||
serviceId: message.sourceServiceId,
|
serviceId: message.sourceServiceId,
|
||||||
e164: message.source,
|
e164: message.source,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
|
||||||
isIncoming &&
|
|
||||||
conversation &&
|
|
||||||
isDirectConversation(conversation.attributes)
|
|
||||||
) {
|
|
||||||
const convoAuthor = this.#getOrPushPrivateRecipient({
|
|
||||||
id: conversation.attributes.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fix conversation id for misattributed e164-only incoming 1:1
|
|
||||||
// messages.
|
|
||||||
if (authorId !== convoAuthor) {
|
|
||||||
authorId = convoAuthor;
|
|
||||||
this.#stats.fixedDirectMessages += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
strictAssert(!isIncoming, 'Incoming message must have source');
|
strictAssert(!isIncoming, 'Incoming message must have source');
|
||||||
|
|
||||||
@@ -1517,6 +1504,39 @@ export class BackupExportStream extends Readable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark incoming messages from self as outgoing
|
||||||
|
if (isIncoming && authorId === me) {
|
||||||
|
log.warn(
|
||||||
|
`${message.sent_at}: Found incoming message with author self, updating to outgoing`
|
||||||
|
);
|
||||||
|
isOutgoing = true;
|
||||||
|
isIncoming = false;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
message.type = 'outgoing';
|
||||||
|
|
||||||
|
this.#stats.fixedDirectMessages += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix authorId for misattributed e164-only incoming 1:1
|
||||||
|
// messages.
|
||||||
|
if (
|
||||||
|
isIncoming &&
|
||||||
|
!message.sourceServiceId &&
|
||||||
|
message.source &&
|
||||||
|
conversation &&
|
||||||
|
isDirectConversation(conversation.attributes)
|
||||||
|
) {
|
||||||
|
const convoAuthor = this.#getOrPushPrivateRecipient({
|
||||||
|
id: conversation.attributes.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (authorId !== convoAuthor) {
|
||||||
|
authorId = convoAuthor;
|
||||||
|
this.#stats.fixedDirectMessages += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isOutgoing || isIncoming) {
|
if (isOutgoing || isIncoming) {
|
||||||
strictAssert(authorId, 'Incoming/outgoing messages require an author');
|
strictAssert(authorId, 'Incoming/outgoing messages require an author');
|
||||||
}
|
}
|
||||||
@@ -1570,9 +1590,6 @@ export class BackupExportStream extends Readable {
|
|||||||
authorId,
|
authorId,
|
||||||
'Incoming/outgoing non-bubble messages require an author'
|
'Incoming/outgoing non-bubble messages require an author'
|
||||||
);
|
);
|
||||||
const me = this.#getOrPushPrivateRecipient({
|
|
||||||
serviceId: aboutMe.aci,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (authorId === me) {
|
if (authorId === me) {
|
||||||
directionalDetails = {
|
directionalDetails = {
|
||||||
@@ -1849,10 +1866,16 @@ export class BackupExportStream extends Readable {
|
|||||||
} else if (message.isErased) {
|
} else if (message.isErased) {
|
||||||
return undefined;
|
return undefined;
|
||||||
} else {
|
} else {
|
||||||
|
const standardMessage = await this.#toStandardMessage({
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!standardMessage) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
item = {
|
item = {
|
||||||
standardMessage: await this.#toStandardMessage({
|
standardMessage,
|
||||||
message,
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
revisions = await this.#toChatItemRevisions(base, item, message);
|
revisions = await this.#toChatItemRevisions(base, item, message);
|
||||||
@@ -3339,7 +3362,7 @@ export class BackupExportStream extends Readable {
|
|||||||
| 'received_at'
|
| 'received_at'
|
||||||
| 'timestamp'
|
| 'timestamp'
|
||||||
>;
|
>;
|
||||||
}): Promise<Backups.StandardMessage.Params> {
|
}): Promise<Backups.StandardMessage.Params | undefined> {
|
||||||
if (
|
if (
|
||||||
message.body &&
|
message.body &&
|
||||||
isBodyTooLong(message.body, MAX_BACKUP_MESSAGE_BODY_BYTE_LENGTH)
|
isBodyTooLong(message.body, MAX_BACKUP_MESSAGE_BODY_BYTE_LENGTH)
|
||||||
@@ -3347,7 +3370,7 @@ export class BackupExportStream extends Readable {
|
|||||||
log.warn(`${message.timestamp}: Message body is too long; will truncate`);
|
log.warn(`${message.timestamp}: Message body is too long; will truncate`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const result = {
|
||||||
quote: await this.#toQuote({
|
quote: await this.#toQuote({
|
||||||
message,
|
message,
|
||||||
}),
|
}),
|
||||||
@@ -3384,6 +3407,12 @@ export class BackupExportStream extends Readable {
|
|||||||
: null,
|
: null,
|
||||||
reactions: this.#getMessageReactions(message),
|
reactions: this.#getMessageReactions(message),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!result.attachments?.length && !result.text) {
|
||||||
|
log.warn('toStandardMessage: had neither text nor attachments, dropping');
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #toDirectStoryReplyMessage({
|
async #toDirectStoryReplyMessage({
|
||||||
@@ -3488,7 +3517,7 @@ export class BackupExportStream extends Readable {
|
|||||||
|
|
||||||
const isOutgoing = message.type === 'outgoing';
|
const isOutgoing = message.type === 'outgoing';
|
||||||
|
|
||||||
return Promise.all(
|
const revisions = await Promise.all(
|
||||||
editHistory
|
editHistory
|
||||||
// The first history is the copy of the current message
|
// The first history is the copy of the current message
|
||||||
.slice(1)
|
.slice(1)
|
||||||
@@ -3526,10 +3555,16 @@ export class BackupExportStream extends Readable {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
const standardMessage = await this.#toStandardMessage({
|
||||||
|
message: history,
|
||||||
|
});
|
||||||
|
if (!standardMessage) {
|
||||||
|
log.warn('Chat revision was invalid, dropping');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
item = {
|
item = {
|
||||||
standardMessage: await this.#toStandardMessage({
|
standardMessage,
|
||||||
message: history,
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { ...base, item };
|
return { ...base, item };
|
||||||
@@ -3537,6 +3572,7 @@ export class BackupExportStream extends Readable {
|
|||||||
// Backups use oldest to newest order
|
// Backups use oldest to newest order
|
||||||
.reverse()
|
.reverse()
|
||||||
);
|
);
|
||||||
|
return revisions.filter(isNotNil);
|
||||||
}
|
}
|
||||||
|
|
||||||
#toCustomChatColors(): Array<Backups.ChatStyle.CustomChatColor.Params> {
|
#toCustomChatColors(): Array<Backups.ChatStyle.CustomChatColor.Params> {
|
||||||
|
|||||||
@@ -187,6 +187,85 @@ describe('backup/bubble messages', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('drops messages with neither text nor attachments', async () => {
|
||||||
|
await asymmetricRoundtripHarness(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
conversationId: contactA.id,
|
||||||
|
id: generateGuid(),
|
||||||
|
type: 'incoming',
|
||||||
|
received_at: 3,
|
||||||
|
received_at_ms: 3,
|
||||||
|
sent_at: 3,
|
||||||
|
timestamp: 3,
|
||||||
|
sourceServiceId: CONTACT_A,
|
||||||
|
readStatus: ReadStatus.Unread,
|
||||||
|
seenStatus: SeenStatus.Unseen,
|
||||||
|
unidentifiedDeliveryReceived: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('drops edited revisions with neither text nor attachments', async () => {
|
||||||
|
const message: MessageAttributesType = {
|
||||||
|
conversationId: contactA.id,
|
||||||
|
id: generateGuid(),
|
||||||
|
type: 'incoming',
|
||||||
|
received_at: 3,
|
||||||
|
received_at_ms: 3,
|
||||||
|
sent_at: 3,
|
||||||
|
timestamp: 3,
|
||||||
|
sourceServiceId: CONTACT_A,
|
||||||
|
body: 'd',
|
||||||
|
readStatus: ReadStatus.Unread,
|
||||||
|
seenStatus: SeenStatus.Unseen,
|
||||||
|
unidentifiedDeliveryReceived: true,
|
||||||
|
editMessageTimestamp: 5,
|
||||||
|
editMessageReceivedAtMs: 5,
|
||||||
|
editHistory: [
|
||||||
|
{
|
||||||
|
body: 'd',
|
||||||
|
timestamp: 5,
|
||||||
|
received_at: 5,
|
||||||
|
received_at_ms: 5,
|
||||||
|
readStatus: ReadStatus.Unread,
|
||||||
|
unidentifiedDeliveryReceived: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamp: 4,
|
||||||
|
received_at: 4,
|
||||||
|
received_at_ms: 4,
|
||||||
|
readStatus: ReadStatus.Unread,
|
||||||
|
unidentifiedDeliveryReceived: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: 'b',
|
||||||
|
timestamp: 3,
|
||||||
|
received_at: 3,
|
||||||
|
received_at_ms: 3,
|
||||||
|
readStatus: ReadStatus.Read,
|
||||||
|
unidentifiedDeliveryReceived: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
strictAssert(message.editHistory, 'edit history exists');
|
||||||
|
const [currentRevision, , oldestRevision] = message.editHistory;
|
||||||
|
strictAssert(currentRevision, 'current revision exists');
|
||||||
|
strictAssert(oldestRevision, 'oldest revision exists');
|
||||||
|
|
||||||
|
await asymmetricRoundtripHarness(
|
||||||
|
[message],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
...message,
|
||||||
|
editHistory: [currentRevision, oldestRevision],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('roundtrips unopened gift badge', async () => {
|
it('roundtrips unopened gift badge', async () => {
|
||||||
await symmetricRoundtripHarness([
|
await symmetricRoundtripHarness([
|
||||||
{
|
{
|
||||||
@@ -275,6 +354,48 @@ describe('backup/bubble messages', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('updates incoming messages authored by self to outgoing', async () => {
|
||||||
|
const ourConversation = window.ConversationController.get(OUR_ACI);
|
||||||
|
strictAssert(ourConversation, 'our conversation exists');
|
||||||
|
|
||||||
|
await asymmetricRoundtripHarness(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
conversationId: contactA.id,
|
||||||
|
id: generateGuid(),
|
||||||
|
type: 'incoming',
|
||||||
|
received_at: 3,
|
||||||
|
received_at_ms: 3,
|
||||||
|
sent_at: 3,
|
||||||
|
sourceServiceId: OUR_ACI,
|
||||||
|
readStatus: ReadStatus.Unread,
|
||||||
|
seenStatus: SeenStatus.Unseen,
|
||||||
|
unidentifiedDeliveryReceived: true,
|
||||||
|
timestamp: 3,
|
||||||
|
body: 'hello',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
conversationId: contactA.id,
|
||||||
|
id: generateGuid(),
|
||||||
|
type: 'outgoing',
|
||||||
|
received_at: 3,
|
||||||
|
received_at_ms: 3,
|
||||||
|
sent_at: 3,
|
||||||
|
sourceServiceId: OUR_ACI,
|
||||||
|
readStatus: ReadStatus.Read,
|
||||||
|
seenStatus: SeenStatus.Seen,
|
||||||
|
timestamp: 3,
|
||||||
|
body: 'hello',
|
||||||
|
sendStateByConversationId: {
|
||||||
|
[ourConversation.id]: { status: SendStatus.Read, updatedAt: 3 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
describe('quotes', () => {
|
describe('quotes', () => {
|
||||||
it('roundtrips gift badge quote', async () => {
|
it('roundtrips gift badge quote', async () => {
|
||||||
await symmetricRoundtripHarness([
|
await symmetricRoundtripHarness([
|
||||||
|
|||||||
@@ -877,19 +877,15 @@ const bodyRangeOffsetSchema = z.number().int().min(0);
|
|||||||
const bodyRangeStyleSchema = z.nativeEnum(signalservice.BodyRange.Style);
|
const bodyRangeStyleSchema = z.nativeEnum(signalservice.BodyRange.Style);
|
||||||
|
|
||||||
export const bodyRangeSchema = z.union([
|
export const bodyRangeSchema = z.union([
|
||||||
z
|
z.object({
|
||||||
.object({
|
start: bodyRangeOffsetSchema,
|
||||||
start: bodyRangeOffsetSchema,
|
length: bodyRangeOffsetSchema,
|
||||||
length: bodyRangeOffsetSchema,
|
mentionAci: aciSchema,
|
||||||
mentionAci: aciSchema,
|
}),
|
||||||
})
|
z.object({
|
||||||
.strict(),
|
start: bodyRangeOffsetSchema,
|
||||||
z
|
length: bodyRangeOffsetSchema,
|
||||||
.object({
|
style: bodyRangeStyleSchema,
|
||||||
start: bodyRangeOffsetSchema,
|
spoilerId: z.number().optional(),
|
||||||
length: bodyRangeOffsetSchema,
|
}),
|
||||||
style: bodyRangeStyleSchema,
|
|
||||||
spoilerId: z.number().optional(),
|
|
||||||
})
|
|
||||||
.strict(),
|
|
||||||
]);
|
]);
|
||||||
|
|||||||
Reference in New Issue
Block a user