Hydrate generic media previews before notifying
This commit is contained in:
@@ -45,7 +45,12 @@ public sealed class MaxBridgeSyncService(
|
||||
var update = await maxBridgeClient.FetchChatHistoryAsync(externalChatId, chatUrl, cancellationToken);
|
||||
return update is null
|
||||
? 0
|
||||
: await ApplyUpdatesAsync([update], incrementUnread: false, sendPushNotifications: false, cancellationToken);
|
||||
: await ApplyUpdatesAsync(
|
||||
[update],
|
||||
incrementUnread: false,
|
||||
sendPushNotifications: false,
|
||||
cancellationToken,
|
||||
allowGenericMediaHistoryFetch: false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -58,7 +63,8 @@ public sealed class MaxBridgeSyncService(
|
||||
IReadOnlyList<MaxChatUpdate> updates,
|
||||
bool incrementUnread,
|
||||
bool sendPushNotifications,
|
||||
CancellationToken cancellationToken)
|
||||
CancellationToken cancellationToken,
|
||||
bool allowGenericMediaHistoryFetch = true)
|
||||
{
|
||||
if (updates.Count == 0)
|
||||
{
|
||||
@@ -74,6 +80,7 @@ public sealed class MaxBridgeSyncService(
|
||||
var createdMessages = new List<(Chat Chat, Message Message)>();
|
||||
var updatedMessages = new List<(Chat Chat, Message Message)>();
|
||||
var previewNotificationMessages = new List<(Chat Chat, Message Message)>();
|
||||
var genericMediaHistoryRequests = new List<(string ExternalChatId, string? ChatUrl, DateTimeOffset PreviewAt)>();
|
||||
var trackedChatsByExternalId = new Dictionary<string, Chat>(StringComparer.Ordinal);
|
||||
|
||||
foreach (var update in updates)
|
||||
@@ -196,6 +203,14 @@ public sealed class MaxBridgeSyncService(
|
||||
previewNotificationMessages.Add((chat, previewResult.NotificationMessage));
|
||||
}
|
||||
|
||||
if (allowGenericMediaHistoryFetch &&
|
||||
previewResult.ShouldFetchHistory &&
|
||||
previewResult.PreviewAt is { } previewAt &&
|
||||
!await HasMessageAtOrAfterAsync(db, chat.Id, previewAt, cancellationToken))
|
||||
{
|
||||
genericMediaHistoryRequests.Add((update.ExternalId, nextWebUrl ?? chat.WebUrl, previewAt));
|
||||
}
|
||||
|
||||
if (previewResult.Changed)
|
||||
{
|
||||
changed++;
|
||||
@@ -387,6 +402,29 @@ public sealed class MaxBridgeSyncService(
|
||||
await hubContext.Clients.All.SendAsync("ChatListInvalidated", cancellationToken);
|
||||
}
|
||||
|
||||
foreach (var request in genericMediaHistoryRequests
|
||||
.GroupBy(x => x.ExternalChatId, StringComparer.Ordinal)
|
||||
.Select(x => x.OrderByDescending(item => item.PreviewAt).First()))
|
||||
{
|
||||
try
|
||||
{
|
||||
var history = await maxBridgeClient.FetchChatHistoryAsync(request.ExternalChatId, request.ChatUrl, cancellationToken);
|
||||
if (history is not null)
|
||||
{
|
||||
changed += await ApplyUpdatesAsync(
|
||||
[history],
|
||||
incrementUnread,
|
||||
sendPushNotifications,
|
||||
cancellationToken,
|
||||
allowGenericMediaHistoryFetch: false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "MAX media preview history fetch failed for {ExternalChatId}.", request.ExternalChatId);
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
@@ -400,12 +438,12 @@ public sealed class MaxBridgeSyncService(
|
||||
var preview = MessageTextSanitizer.CleanChatPreview(update.LastMessagePreview);
|
||||
if (string.IsNullOrWhiteSpace(preview))
|
||||
{
|
||||
return new ListPreviewApplyResult(false, null, null);
|
||||
return new ListPreviewApplyResult(false, null, null, false, null);
|
||||
}
|
||||
|
||||
if (IsPresencePreview(preview))
|
||||
{
|
||||
return new ListPreviewApplyResult(false, null, null);
|
||||
return new ListPreviewApplyResult(false, null, null, false, null);
|
||||
}
|
||||
|
||||
var previousPreview = chat.LastMessagePreview;
|
||||
@@ -413,15 +451,19 @@ public sealed class MaxBridgeSyncService(
|
||||
var previewAt = ResolveListPreviewAt(update, previousPreviewAt ?? chat.UpdatedAt);
|
||||
if (chat.HistoryClearedAt is { } historyClearedAt && previewAt <= historyClearedAt)
|
||||
{
|
||||
return new ListPreviewApplyResult(false, null, null);
|
||||
return new ListPreviewApplyResult(false, null, null, false, null);
|
||||
}
|
||||
|
||||
var isGenericMediaPreview = MessageTextSanitizer.IsGenericMediaLabel(preview);
|
||||
var shouldFetchHistory = isGenericMediaPreview &&
|
||||
update.Messages.Count == 0 &&
|
||||
HasListClockTime(update.LastMessageTimeText);
|
||||
var samePreview = string.Equals(previousPreview, preview, StringComparison.Ordinal);
|
||||
var samePreviewAt = previousPreviewAt is not null &&
|
||||
Math.Abs((previousPreviewAt.Value - previewAt).TotalSeconds) < 1;
|
||||
if (samePreview && samePreviewAt)
|
||||
{
|
||||
return new ListPreviewApplyResult(false, null, null);
|
||||
return new ListPreviewApplyResult(false, null, null, shouldFetchHistory, previewAt);
|
||||
}
|
||||
|
||||
chat.LastMessagePreview = preview;
|
||||
@@ -438,18 +480,15 @@ public sealed class MaxBridgeSyncService(
|
||||
var shouldNotify = shouldNotifyCandidate &&
|
||||
RememberPreviewNotification(chat.Id, preview, previewAt);
|
||||
|
||||
if (incrementUnread && shouldNotify)
|
||||
if (incrementUnread && shouldNotify && !isGenericMediaPreview)
|
||||
{
|
||||
chat.UnreadCount++;
|
||||
}
|
||||
|
||||
var message = shouldNotify && !MessageTextSanitizer.IsGenericMediaLabel(preview)
|
||||
var message = shouldNotify && !isGenericMediaPreview
|
||||
? CreatePreviewMessage(chat, preview, previewAt)
|
||||
: null;
|
||||
var notificationMessage = shouldNotify && message is null && MessageTextSanitizer.IsGenericMediaLabel(preview)
|
||||
? CreatePreviewMessage(chat, preview, previewAt)
|
||||
: null;
|
||||
return new ListPreviewApplyResult(true, message, notificationMessage);
|
||||
return new ListPreviewApplyResult(true, message, null, shouldFetchHistory, previewAt);
|
||||
}
|
||||
|
||||
private static bool IsKnownOutgoingPreviewEcho(
|
||||
@@ -628,7 +667,12 @@ public sealed class MaxBridgeSyncService(
|
||||
text.Contains("file", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private sealed record ListPreviewApplyResult(bool Changed, Message? Message, Message? NotificationMessage);
|
||||
private sealed record ListPreviewApplyResult(
|
||||
bool Changed,
|
||||
Message? Message,
|
||||
Message? NotificationMessage,
|
||||
bool ShouldFetchHistory,
|
||||
DateTimeOffset? PreviewAt);
|
||||
|
||||
private sealed record MessageMergeResult(bool Changed, IReadOnlyList<MessageAttachment> AddedAttachments);
|
||||
|
||||
@@ -715,6 +759,22 @@ public sealed class MaxBridgeSyncService(
|
||||
Math.Abs((candidate.SentAt.ToUniversalTime() - previewMessage.SentAt.ToUniversalTime()).TotalMinutes) <= 30);
|
||||
}
|
||||
|
||||
private static async Task<bool> HasMessageAtOrAfterAsync(
|
||||
QMaxDbContext db,
|
||||
Guid chatId,
|
||||
DateTimeOffset previewAt,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var cutoff = previewAt.ToUniversalTime().AddSeconds(-1);
|
||||
var sentAtValues = await db.Messages
|
||||
.AsNoTracking()
|
||||
.Where(x => x.ChatId == chatId && x.DeletedAt == null)
|
||||
.Select(x => x.SentAt)
|
||||
.ToArrayAsync(cancellationToken);
|
||||
|
||||
return sentAtValues.Any(sentAt => sentAt.ToUniversalTime() >= cutoff);
|
||||
}
|
||||
|
||||
private static string PreviewExternalId(Guid chatId, string preview, DateTimeOffset previewAt)
|
||||
{
|
||||
var previewMinute = previewAt.ToUniversalTime().Ticks / TimeSpan.TicksPerMinute;
|
||||
|
||||
Reference in New Issue
Block a user