Fix async chat actions and channel sync

This commit is contained in:
sevenhill
2026-07-06 21:38:35 +03:00
parent 84bf7e96c9
commit 7aae5acda3
6 changed files with 1220 additions and 89 deletions
@@ -80,12 +80,13 @@ 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 genericMediaHistoryRequests = new List<(string ExternalChatId, string? ChatUrl, DateTimeOffset PreviewAt, bool IncrementUnread)>();
var trackedChatsByExternalId = new Dictionary<string, Chat>(StringComparer.Ordinal);
foreach (var update in updates)
{
var nextWebUrl = CleanWebUrl(update.ChatUrl);
var nextKind = ResolveChatKind(update.Kind);
if (!trackedChatsByExternalId.TryGetValue(update.ExternalId, out var chat))
{
chat = await db.Chats
@@ -95,6 +96,10 @@ public sealed class MaxBridgeSyncService(
chat = await db.Chats
.FirstOrDefaultAsync(x => x.WebUrl == nextWebUrl, cancellationToken);
}
if (chat is null)
{
chat = await FindDeletedChatTombstoneAsync(db, update, nextWebUrl, cancellationToken);
}
}
if (chat?.DeletedAt is not null)
@@ -112,7 +117,7 @@ public sealed class MaxBridgeSyncService(
Title = update.Title,
AvatarUrl = update.AvatarUrl,
WebUrl = nextWebUrl,
Kind = ChatKind.MaxDialog
Kind = nextKind ?? ChatKind.MaxDialog
};
db.Chats.Add(chat);
changed++;
@@ -149,6 +154,12 @@ public sealed class MaxBridgeSyncService(
metadataChanged = true;
}
if (nextKind is { } resolvedKind && chat.Kind != resolvedKind)
{
chat.Kind = resolvedKind;
metadataChanged = true;
}
if (metadataChanged)
{
chat.UpdatedAt = DateTimeOffset.UtcNow;
@@ -189,7 +200,9 @@ public sealed class MaxBridgeSyncService(
chat,
chatWasCreated,
incrementUnread,
lastKnownMessage);
lastKnownMessage,
createPreviewMessages: chat.Kind != ChatKind.Channel,
incrementGenericMediaUnread: chat.Kind == ChatKind.Channel);
if (previewResult.Message is not null)
{
if (!await HasPreviewPlaceholderAsync(db, chat.Id, previewResult.Message, cancellationToken))
@@ -203,12 +216,21 @@ public sealed class MaxBridgeSyncService(
previewNotificationMessages.Add((chat, previewResult.NotificationMessage));
}
var shouldFetchHistory = previewResult.ShouldFetchHistory ||
(chat.Kind == ChatKind.Channel &&
previewResult.PreviewAt is not null &&
update.Messages.Count == 0 &&
HasListClockTime(update.LastMessageTimeText));
if (allowGenericMediaHistoryFetch &&
previewResult.ShouldFetchHistory &&
shouldFetchHistory &&
previewResult.PreviewAt is { } previewAt &&
!await HasMessageAtOrAfterAsync(db, chat.Id, previewAt, cancellationToken))
{
genericMediaHistoryRequests.Add((update.ExternalId, nextWebUrl ?? chat.WebUrl, previewAt));
genericMediaHistoryRequests.Add((
update.ExternalId,
nextWebUrl ?? chat.WebUrl,
previewAt,
IncrementUnread: chat.Kind != ChatKind.Channel));
}
if (previewResult.Changed)
@@ -372,7 +394,7 @@ public sealed class MaxBridgeSyncService(
{
var dto = projection.ToDto(message);
await hubContext.Clients.Group($"chat:{chat.Id}").SendAsync("MessageCreated", dto, cancellationToken);
if (sendPushNotifications && message.Direction == MessageDirection.Incoming)
if (sendPushNotifications && message.Direction == MessageDirection.Incoming && chat.Kind != ChatKind.Channel)
{
await pushNotifications.SendNewMessageAsync(chat, message, cancellationToken);
}
@@ -380,7 +402,7 @@ public sealed class MaxBridgeSyncService(
foreach (var (chat, message) in previewNotificationMessages)
{
if (sendPushNotifications && message.Direction == MessageDirection.Incoming)
if (sendPushNotifications && message.Direction == MessageDirection.Incoming && chat.Kind != ChatKind.Channel)
{
await pushNotifications.SendNewMessageAsync(chat, message, cancellationToken);
}
@@ -413,7 +435,7 @@ public sealed class MaxBridgeSyncService(
{
changed += await ApplyUpdatesAsync(
[history],
incrementUnread,
incrementUnread && request.IncrementUnread,
sendPushNotifications,
cancellationToken,
allowGenericMediaHistoryFetch: false);
@@ -433,7 +455,9 @@ public sealed class MaxBridgeSyncService(
Chat chat,
bool chatWasCreated,
bool incrementUnread,
LastKnownMessageSnapshot? lastKnownMessage)
LastKnownMessageSnapshot? lastKnownMessage,
bool createPreviewMessages,
bool incrementGenericMediaUnread)
{
var preview = MessageTextSanitizer.CleanChatPreview(update.LastMessagePreview);
if (string.IsNullOrWhiteSpace(preview))
@@ -471,8 +495,7 @@ public sealed class MaxBridgeSyncService(
chat.UpdatedAt = DateTimeOffset.UtcNow;
var echoesKnownOutgoing = IsKnownOutgoingPreviewEcho(lastKnownMessage, preview, previewAt);
var shouldNotifyCandidate = !chatWasCreated &&
!string.IsNullOrWhiteSpace(previousPreview) &&
var shouldNotifyCandidate = (chatWasCreated || !string.IsNullOrWhiteSpace(previousPreview)) &&
!echoesKnownOutgoing &&
update.LastMessageIsOutgoing != true &&
update.Messages.Count == 0 &&
@@ -480,12 +503,12 @@ public sealed class MaxBridgeSyncService(
var shouldNotify = shouldNotifyCandidate &&
RememberPreviewNotification(chat.Id, preview, previewAt);
if (incrementUnread && shouldNotify && !isGenericMediaPreview)
if (incrementUnread && shouldNotify && (!isGenericMediaPreview || incrementGenericMediaUnread))
{
chat.UnreadCount++;
}
var message = shouldNotify && !isGenericMediaPreview
var message = shouldNotify && !isGenericMediaPreview && createPreviewMessages
? CreatePreviewMessage(chat, preview, previewAt)
: null;
return new ListPreviewApplyResult(true, message, null, shouldFetchHistory, previewAt);
@@ -1298,6 +1321,49 @@ public sealed class MaxBridgeSyncService(
return string.IsNullOrWhiteSpace(trimmed) ? null : trimmed;
}
private static ChatKind? ResolveChatKind(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
return Enum.TryParse<ChatKind>(value.Trim(), ignoreCase: true, out var parsed)
? parsed
: null;
}
private static async Task<Chat?> FindDeletedChatTombstoneAsync(
QMaxDbContext db,
MaxChatUpdate update,
string? nextWebUrl,
CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(update.Title))
{
return null;
}
var deletedChats = db.Chats
.Where(x => x.DeletedAt != null && x.Title == update.Title);
if (!string.IsNullOrWhiteSpace(nextWebUrl))
{
return await deletedChats.FirstOrDefaultAsync(x => x.WebUrl == nextWebUrl, cancellationToken);
}
if (!string.IsNullOrWhiteSpace(update.AvatarUrl))
{
return await deletedChats.FirstOrDefaultAsync(x => x.AvatarUrl == update.AvatarUrl, cancellationToken);
}
var titleMatches = await deletedChats
.OrderByDescending(x => x.DeletedAt)
.Take(2)
.ToListAsync(cancellationToken);
return titleMatches.Count == 1 ? titleMatches[0] : null;
}
private static string? LastMessagePreview(string? text, IEnumerable<MessageAttachment> attachments)
{
var orderedAttachments = attachments.OrderBy(x => x.SortOrder).ToArray();
+1 -1
View File
@@ -68,7 +68,7 @@ public sealed class MaxOutboxService(
.Where(chat => IsChatActionDue(chat, now))
.OrderBy(chat => chat.PendingMaxActionRequestedAt ?? chat.UpdatedAt)
.ThenBy(chat => chat.Id)
.Take(1)
.Take(10)
.ToList();
var processed = 0;