[backend/akko-client] Fix notifications, including emoji reaction notifications

This commit is contained in:
Kopper 2024-09-01 04:51:24 +03:00 committed by Iceshrimp development
parent 834661981a
commit 9d6d892091
9 changed files with 60 additions and 17 deletions

View file

@ -34,6 +34,8 @@ public class NotificationController(DatabaseContext db, NotificationRenderer not
) )
{ {
var user = HttpContext.GetUserOrFail(); var user = HttpContext.GetUserOrFail();
var isPleroma = HttpContext.GetOauthToken()!.IsPleroma;
return await db.Notifications return await db.Notifications
.IncludeCommonProperties() .IncludeCommonProperties()
.Where(p => p.Notifiee == user) .Where(p => p.Notifiee == user)
@ -54,7 +56,7 @@ public class NotificationController(DatabaseContext db, NotificationRenderer not
.FilterMutedThreads(user, db) .FilterMutedThreads(user, db)
.Paginate(p => p.MastoId, query, ControllerContext) .Paginate(p => p.MastoId, query, ControllerContext)
.PrecomputeNoteVisibilities(user) .PrecomputeNoteVisibilities(user)
.RenderAllForMastodonAsync(notificationRenderer, user); .RenderAllForMastodonAsync(notificationRenderer, user, isPleroma);
} }
[HttpGet("{id:long}")] [HttpGet("{id:long}")]
@ -64,6 +66,8 @@ public class NotificationController(DatabaseContext db, NotificationRenderer not
public async Task<NotificationEntity> GetNotification(long id) public async Task<NotificationEntity> GetNotification(long id)
{ {
var user = HttpContext.GetUserOrFail(); var user = HttpContext.GetUserOrFail();
var isPleroma = HttpContext.GetOauthToken()!.IsPleroma;
var notification = await db.Notifications var notification = await db.Notifications
.IncludeCommonProperties() .IncludeCommonProperties()
.Where(p => p.Notifiee == user && p.MastoId == id) .Where(p => p.Notifiee == user && p.MastoId == id)
@ -72,7 +76,7 @@ public class NotificationController(DatabaseContext db, NotificationRenderer not
.FirstOrDefaultAsync() ?? .FirstOrDefaultAsync() ??
throw GracefulException.RecordNotFound(); throw GracefulException.RecordNotFound();
var res = await notificationRenderer.RenderAsync(notification.EnforceRenoteReplyVisibility(p => p.Note), user); var res = await notificationRenderer.RenderAsync(notification.EnforceRenoteReplyVisibility(p => p.Note), user, isPleroma);
return res; return res;
} }
} }

View file

@ -1,14 +1,17 @@
using Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities; using Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions; using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.Backend.Core.Services;
using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Controllers.Mastodon.Renderers; namespace Iceshrimp.Backend.Controllers.Mastodon.Renderers;
public class NotificationRenderer(NoteRenderer noteRenderer, UserRenderer userRenderer) public class NotificationRenderer(DatabaseContext db, NoteRenderer noteRenderer, UserRenderer userRenderer)
{ {
public async Task<NotificationEntity> RenderAsync( public async Task<NotificationEntity> RenderAsync(
Notification notification, User user, List<AccountEntity>? accounts = null, Notification notification, User user, bool isPleroma, List<AccountEntity>? accounts = null,
IEnumerable<StatusEntity>? statuses = null IEnumerable<StatusEntity>? statuses = null
) )
{ {
@ -25,20 +28,35 @@ public class NotificationRenderer(NoteRenderer noteRenderer, UserRenderer userRe
var notifier = accounts?.FirstOrDefault(p => p.Id == dbNotifier.Id) ?? var notifier = accounts?.FirstOrDefault(p => p.Id == dbNotifier.Id) ??
await userRenderer.RenderAsync(dbNotifier); await userRenderer.RenderAsync(dbNotifier);
string? emojiUrl = null;
if (notification.Reaction != null && EmojiService.IsCustomEmoji(notification.Reaction)) {
var parts = notification.Reaction.Trim(':').Split('@');
emojiUrl = await db.Emojis.Where(e => e.Name == parts[0] && e.Host == (parts.Length > 1 ? parts[1] : null)).Select(e => e.PublicUrl).FirstOrDefaultAsync();
}
var res = new NotificationEntity var res = new NotificationEntity
{ {
Id = notification.MastoId.ToString(), Id = notification.MastoId.ToString(),
Type = NotificationEntity.EncodeType(notification.Type), Type = NotificationEntity.EncodeType(notification.Type, isPleroma),
Note = note, Note = note,
Notifier = notifier, Notifier = notifier,
CreatedAt = notification.CreatedAt.ToStringIso8601Like() CreatedAt = notification.CreatedAt.ToStringIso8601Like(),
Emoji = notification.Reaction,
EmojiUrl = emojiUrl,
Pleroma = new() {
// TODO: stub
IsMuted = false,
IsSeen = notification.IsRead
}
}; };
return res; return res;
} }
public async Task<IEnumerable<NotificationEntity>> RenderManyAsync( public async Task<IEnumerable<NotificationEntity>> RenderManyAsync(
IEnumerable<Notification> notifications, User user IEnumerable<Notification> notifications, User user, bool isPleroma
) )
{ {
var notificationList = notifications.ToList(); var notificationList = notifications.ToList();
@ -64,7 +82,7 @@ public class NotificationRenderer(NoteRenderer noteRenderer, UserRenderer userRe
user, Filter.FilterContext.Notifications, accounts); user, Filter.FilterContext.Notifications, accounts);
return await notificationList return await notificationList
.Select(p => RenderAsync(p, user, accounts, notes)) .Select(p => RenderAsync(p, user, isPleroma, accounts, notes))
.AwaitAllAsync(); .AwaitAllAsync();
} }
} }

View file

@ -2,6 +2,7 @@ using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Middleware;
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
using static Iceshrimp.Backend.Core.Database.Tables.Notification; using static Iceshrimp.Backend.Core.Database.Tables.Notification;
using Iceshrimp.Backend.Controllers.Pleroma.Schemas.Entities;
namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities; namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
@ -13,9 +14,12 @@ public class NotificationEntity : IEntity
[J("status")] public required StatusEntity? Note { get; set; } [J("status")] public required StatusEntity? Note { get; set; }
[J("id")] public required string Id { get; set; } [J("id")] public required string Id { get; set; }
//TODO: [J("reaction")] public required Reaction? Reaction { get; set; }
public static string EncodeType(NotificationType type) [J("emoji")] public string? Emoji { get; set; }
[J("emoji_url")] public string? EmojiUrl { get; set; }
[J("pleroma")] public required PleromaNotificationExtensions Pleroma { get; set; }
public static string EncodeType(NotificationType type, bool isPleroma)
{ {
return type switch return type switch
{ {
@ -28,7 +32,9 @@ public class NotificationEntity : IEntity
NotificationType.PollEnded => "poll", NotificationType.PollEnded => "poll",
NotificationType.FollowRequestReceived => "follow_request", NotificationType.FollowRequestReceived => "follow_request",
NotificationType.Edit => "update", NotificationType.Edit => "update",
NotificationType.Reaction => "reaction",
NotificationType.Reaction when isPleroma => "pleroma:emoji_reaction",
NotificationType.Reaction when !isPleroma => "reaction",
_ => throw new GracefulException($"Unsupported notification type: {type}") _ => throw new GracefulException($"Unsupported notification type: {type}")
}; };
@ -46,6 +52,7 @@ public class NotificationEntity : IEntity
"follow_request" => [NotificationType.FollowRequestReceived], "follow_request" => [NotificationType.FollowRequestReceived],
"update" => [NotificationType.Edit], "update" => [NotificationType.Edit],
"reaction" => [NotificationType.Reaction], "reaction" => [NotificationType.Reaction],
"pleroma:emoji_reaction" => [NotificationType.Reaction],
_ => [] _ => []
}; };
} }

View file

@ -183,7 +183,7 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly)
NotificationEntity rendered; NotificationEntity rendered;
try try
{ {
rendered = await renderer.RenderAsync(notification, connection.Token.User); rendered = await renderer.RenderAsync(notification, connection.Token.User, connection.Token.IsPleroma);
} }
catch (GracefulException) catch (GracefulException)
{ {

View file

@ -0,0 +1,9 @@
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Backend.Controllers.Pleroma.Schemas.Entities;
public class PleromaNotificationExtensions
{
[J("is_muted")] public required bool IsMuted { get; set; }
[J("is_seen")] public required bool IsSeen { get; set; }
}

View file

@ -523,13 +523,13 @@ public static class QueryableExtensions
} }
public static async Task<List<NotificationEntity>> RenderAllForMastodonAsync( public static async Task<List<NotificationEntity>> RenderAllForMastodonAsync(
this IQueryable<Notification> notifications, NotificationRenderer renderer, User user this IQueryable<Notification> notifications, NotificationRenderer renderer, User user, bool isPleroma
) )
{ {
var list = (await notifications.ToListAsync()) var list = (await notifications.ToListAsync())
.EnforceRenoteReplyVisibility(p => p.Note) .EnforceRenoteReplyVisibility(p => p.Note)
.ToList(); .ToList();
return (await renderer.RenderManyAsync(list, user)).ToList(); return (await renderer.RenderManyAsync(list, user, isPleroma)).ToList();
} }
public static IQueryable<Note> FilterByAccountStatusesRequest( public static IQueryable<Note> FilterByAccountStatusesRequest(

View file

@ -230,6 +230,8 @@ public partial class EmojiService(
return emoji; return emoji;
} }
public static bool IsCustomEmoji(string s) => CustomEmojiRegex().IsMatch(s);
[GeneratedRegex(@"^:?([\w+-]+)(?:@\.)?:?$", RegexOptions.Compiled)] [GeneratedRegex(@"^:?([\w+-]+)(?:@\.)?:?$", RegexOptions.Compiled)]
private static partial Regex CustomEmojiRegex(); private static partial Regex CustomEmojiRegex();

View file

@ -134,7 +134,8 @@ public class NotificationService(
Note = reaction.Note, Note = reaction.Note,
Notifiee = reaction.Note.User, Notifiee = reaction.Note.User,
Notifier = reaction.User, Notifier = reaction.User,
Type = Notification.NotificationType.Reaction Type = Notification.NotificationType.Reaction,
Reaction = reaction.Reaction
}; };
await db.AddAsync(notification); await db.AddAsync(notification);

View file

@ -33,12 +33,14 @@ public class PushService(
private async void MastodonPushHandler(object? _, Notification notification) private async void MastodonPushHandler(object? _, Notification notification)
{ {
// TODO: do not hardcode isPleroma
try try
{ {
await using var scope = scopeFactory.CreateAsyncScope(); await using var scope = scopeFactory.CreateAsyncScope();
await using var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>(); await using var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
var type = NotificationEntity.EncodeType(notification.Type); var type = NotificationEntity.EncodeType(notification.Type, isPleroma: false);
var subscriptions = await db.PushSubscriptions.Where(p => p.User == notification.Notifiee) var subscriptions = await db.PushSubscriptions.Where(p => p.User == notification.Notifiee)
.Include(pushSubscription => pushSubscription.OauthToken) .Include(pushSubscription => pushSubscription.OauthToken)
.Where(p => p.Types.Contains(type)) .Where(p => p.Types.Contains(type))
@ -74,7 +76,7 @@ public class PushService(
p.Followee == notification.Notifiee); p.Followee == notification.Notifiee);
var renderer = scope.ServiceProvider.GetRequiredService<NotificationRenderer>(); var renderer = scope.ServiceProvider.GetRequiredService<NotificationRenderer>();
var rendered = await renderer.RenderAsync(notification, notification.Notifiee); var rendered = await renderer.RenderAsync(notification, notification.Notifiee, isPleroma: false);
var name = rendered.Notifier.DisplayName; var name = rendered.Notifier.DisplayName;
var subject = rendered.Type switch var subject = rendered.Type switch
{ {