Prevent empty MAX media placeholders
This commit is contained in:
@@ -327,7 +327,15 @@ public sealed class MaxBridgeSyncService(
|
||||
foreach (var incomingAttachment in DistinctAttachments(incoming.Attachments ?? []))
|
||||
{
|
||||
var attachment = await CreateAttachmentAsync(incomingAttachment, maxBridgeClient, storage, cancellationToken);
|
||||
message.Attachments.Add(attachment);
|
||||
if (attachment is not null)
|
||||
{
|
||||
message.Attachments.Add(attachment);
|
||||
}
|
||||
}
|
||||
|
||||
if (ShouldDeferMediaOnlyMessage(message, incoming.Attachments ?? []))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
db.Messages.Add(message);
|
||||
@@ -898,12 +906,25 @@ public sealed class MaxBridgeSyncService(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (HasAttachment(knownAttachments, incomingAttachment))
|
||||
var existingAttachment = FindMatchingAttachment(knownAttachments, incomingAttachment);
|
||||
if (existingAttachment is not null)
|
||||
{
|
||||
if (IsUncachedRemotePlaceholder(existingAttachment) &&
|
||||
RequiresCachedRemoteMedia(existingAttachment.Kind) &&
|
||||
await TryHydrateRemoteAttachmentAsync(existingAttachment, incomingAttachment, maxBridgeClient, storage, cancellationToken))
|
||||
{
|
||||
changed = true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var attachment = await CreateAttachmentAsync(incomingAttachment, maxBridgeClient, storage, cancellationToken);
|
||||
if (attachment is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
attachment.MessageId = message.Id;
|
||||
db.MessageAttachments.Add(attachment);
|
||||
knownAttachments.Add(attachment);
|
||||
@@ -997,7 +1018,7 @@ public sealed class MaxBridgeSyncService(
|
||||
return $"fallback:{attachment.Kind}:{attachment.FileName}:{attachment.SortOrder}:{attachment.TextContent?.Length ?? 0}";
|
||||
}
|
||||
|
||||
private static async Task<MessageAttachment> CreateAttachmentAsync(
|
||||
private static async Task<MessageAttachment?> CreateAttachmentAsync(
|
||||
MaxAttachmentUpdate incoming,
|
||||
IMaxBridgeClient maxBridgeClient,
|
||||
IAttachmentStorageService storage,
|
||||
@@ -1068,7 +1089,12 @@ public sealed class MaxBridgeSyncService(
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Keep the message visible even if a specific remote media item cannot be cached.
|
||||
// Non-visual files can stay as deferred downloads; inline media must be cached before it is shown.
|
||||
}
|
||||
|
||||
if (RequiresCachedRemoteMedia(kind))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1085,28 +1111,103 @@ public sealed class MaxBridgeSyncService(
|
||||
};
|
||||
}
|
||||
|
||||
private static bool HasAttachment(IEnumerable<MessageAttachment> attachments, MaxAttachmentUpdate incoming)
|
||||
private static MessageAttachment? FindMatchingAttachment(IEnumerable<MessageAttachment> attachments, MaxAttachmentUpdate incoming)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(incoming.ExternalId) &&
|
||||
attachments.Any(x => string.Equals(x.ExternalId, incoming.ExternalId, StringComparison.Ordinal)))
|
||||
attachments.FirstOrDefault(x => string.Equals(x.ExternalId, incoming.ExternalId, StringComparison.Ordinal)) is { } externalMatch)
|
||||
{
|
||||
return true;
|
||||
return externalMatch;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(incoming.RemoteUrl) &&
|
||||
attachments.Any(x => string.Equals(x.RemoteUrl, incoming.RemoteUrl, StringComparison.Ordinal)))
|
||||
attachments.FirstOrDefault(x => string.Equals(x.RemoteUrl, incoming.RemoteUrl, StringComparison.Ordinal)) is { } remoteMatch)
|
||||
{
|
||||
return true;
|
||||
return remoteMatch;
|
||||
}
|
||||
|
||||
var kind = ResolveKind(incoming) ?? AttachmentKind.File;
|
||||
var normalizedFileName = AttachmentStorageService.NormalizeRemoteFileName(incoming.FileName, incoming.ContentType, kind);
|
||||
return attachments.Any(x =>
|
||||
return attachments.FirstOrDefault(x =>
|
||||
x.SortOrder == incoming.SortOrder &&
|
||||
x.Kind == kind &&
|
||||
string.Equals(x.OriginalFileName, normalizedFileName, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static bool ShouldDeferMediaOnlyMessage(Message message, IReadOnlyList<MaxAttachmentUpdate> incomingAttachments)
|
||||
{
|
||||
return incomingAttachments.Count > 0 &&
|
||||
message.Attachments.Count == 0 &&
|
||||
string.IsNullOrWhiteSpace(message.Text) &&
|
||||
incomingAttachments.Any(attachment => RequiresCachedRemoteMedia(ResolveKind(attachment)));
|
||||
}
|
||||
|
||||
private static bool RequiresCachedRemoteMedia(AttachmentKind? kind)
|
||||
{
|
||||
return kind is AttachmentKind.Image or
|
||||
AttachmentKind.Gif or
|
||||
AttachmentKind.Video or
|
||||
AttachmentKind.VoiceNote or
|
||||
AttachmentKind.Sticker;
|
||||
}
|
||||
|
||||
private static bool IsUncachedRemotePlaceholder(MessageAttachment attachment)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(attachment.RemoteUrl) &&
|
||||
attachment.StorageFileName.StartsWith("remote-", StringComparison.Ordinal) &&
|
||||
(attachment.FileSizeBytes <= 0 || string.IsNullOrWhiteSpace(attachment.Sha256));
|
||||
}
|
||||
|
||||
private static async Task<bool> TryHydrateRemoteAttachmentAsync(
|
||||
MessageAttachment attachment,
|
||||
MaxAttachmentUpdate incoming,
|
||||
IMaxBridgeClient maxBridgeClient,
|
||||
IAttachmentStorageService storage,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var remoteUrl = string.IsNullOrWhiteSpace(incoming.RemoteUrl) ? attachment.RemoteUrl : incoming.RemoteUrl;
|
||||
if (string.IsNullOrWhiteSpace(remoteUrl))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var kind = ResolveKind(incoming) ?? attachment.Kind;
|
||||
try
|
||||
{
|
||||
var download = await maxBridgeClient.DownloadMediaAsync(remoteUrl, cancellationToken);
|
||||
if (download is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
await using (download.Stream)
|
||||
{
|
||||
var stored = await storage.SaveRemoteAsync(
|
||||
incoming.FileName,
|
||||
download.ContentType,
|
||||
download.Stream,
|
||||
download.ContentLength ?? incoming.FileSizeBytes,
|
||||
kind,
|
||||
cancellationToken);
|
||||
|
||||
attachment.ExternalId = string.IsNullOrWhiteSpace(incoming.ExternalId) ? attachment.ExternalId : incoming.ExternalId;
|
||||
attachment.OriginalFileName = stored.OriginalFileName;
|
||||
attachment.StorageFileName = stored.StorageFileName;
|
||||
attachment.ContentType = stored.ContentType;
|
||||
attachment.FileSizeBytes = stored.FileSizeBytes;
|
||||
attachment.Sha256 = stored.Sha256;
|
||||
attachment.Kind = stored.Kind;
|
||||
attachment.SortOrder = incoming.SortOrder;
|
||||
attachment.RemoteUrl = remoteUrl;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static AttachmentKind? ResolveKind(MaxAttachmentUpdate attachment)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(attachment.Kind) &&
|
||||
|
||||
Reference in New Issue
Block a user