Skip to content

Commit dfafb96

Browse files
committed
Add missing message attachments from attributed body
When attachments are requested, add any attachments referenced by file-transfer GUIDs in attributedBody that are not already present on the message. Resolve those GUIDs through the existing attachment lookup. This fixes affected group-chat image messages observed with BlueBubbles Server 1.9.9 backing a self-hosted Beeper iMessage bridge on macOS Sequoia 15.7.7, where the message/chat APIs returned attachments: [] even though the attachment GUID was present in attributedBody and resolvable by the attachment API.
1 parent 95204ac commit dfafb96

1 file changed

Lines changed: 48 additions & 3 deletions

File tree

  • packages/server/src/server/databases/imessage

packages/server/src/server/databases/imessage/index.ts

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ export class MessageRepository extends Loggable {
152152
* @param attachmentGuid A specific attachment identifier to get
153153
* @param withMessages Whether to include the participants or not
154154
*/
155-
async getAttachment(attachmentGuid: string, withMessages = false) {
155+
async getAttachment(attachmentGuid: string, withMessages = false): Promise<Attachment | null> {
156156
const query = this.db.getRepository(Attachment).createQueryBuilder("attachment");
157157

158158
if (withMessages) query.leftJoinAndSelect("attachment.messages", "message");
@@ -202,6 +202,7 @@ export class MessageRepository extends Loggable {
202202
query.andWhere("message.guid = :guid", { guid });
203203

204204
const message = await query.getOne();
205+
if (withAttachments && message) await this.addMissingAttachmentsFromAttributedBody([message]);
205206
return message;
206207
}
207208

@@ -302,7 +303,9 @@ export class MessageRepository extends Loggable {
302303
query.skip(offset);
303304
query.take(limit);
304305

305-
return await query.getManyAndCount();
306+
const [messages, totalCount] = await query.getManyAndCount();
307+
if (withAttachments) await this.addMissingAttachmentsFromAttributedBody(messages);
308+
return [messages, totalCount];
306309
}
307310

308311
/**
@@ -459,7 +462,49 @@ export class MessageRepository extends Loggable {
459462
query.skip(offset);
460463
query.take(limit);
461464

462-
return await query.getMany();
465+
const messages = await query.getMany();
466+
if (withAttachments) await this.addMissingAttachmentsFromAttributedBody(messages);
467+
return messages;
468+
}
469+
470+
private getAttributedBodyAttachmentGuids(message: Message): string[] {
471+
const guids = new Set<string>();
472+
for (const item of message.attributedBody ?? []) {
473+
for (const run of item?.runs ?? []) {
474+
const guid = run?.attributes?.__kIMFileTransferGUIDAttributeName;
475+
if (typeof guid === "string" && guid.length > 0) guids.add(guid);
476+
}
477+
}
478+
479+
return Array.from(guids);
480+
}
481+
482+
private async addMissingAttachmentsFromAttributedBody(messages: Message[]): Promise<void> {
483+
const attachmentCache = new Map<string, Attachment | null>();
484+
485+
for (const message of messages) {
486+
const guids = this.getAttributedBodyAttachmentGuids(message);
487+
if (guids.length === 0) continue;
488+
489+
if (!message.attachments) message.attachments = [];
490+
491+
for (const guid of guids) {
492+
const existing = message.attachments.some(attachment => {
493+
return attachment.guid === guid || attachment.originalGuid === guid;
494+
});
495+
if (existing) continue;
496+
497+
if (!attachmentCache.has(guid)) {
498+
attachmentCache.set(guid, await this.getAttachment(guid));
499+
}
500+
501+
const attachment = attachmentCache.get(guid);
502+
if (!attachment) continue;
503+
504+
const alreadyAdded = message.attachments.some(item => item.ROWID === attachment.ROWID);
505+
if (!alreadyAdded) message.attachments.push(attachment);
506+
}
507+
}
463508
}
464509

465510
/**

0 commit comments

Comments
 (0)