♻️ Better local actor

This commit is contained in:
2025-12-30 00:31:09 +08:00
parent f556313f1d
commit 72b0739f41
10 changed files with 2810 additions and 78 deletions

View File

@@ -41,4 +41,6 @@ public class SnFediverseActor : ModelBase
public Instant? LastFetchedAt { get; set; } public Instant? LastFetchedAt { get; set; }
public Instant? LastActivityAt { get; set; } public Instant? LastActivityAt { get; set; }
public Guid? PublisherId { get; set; }
} }

View File

@@ -11,11 +11,8 @@ public class SnFediverseRelationship : ModelBase
public Guid Id { get; set; } = Guid.NewGuid(); public Guid Id { get; set; } = Guid.NewGuid();
public Guid ActorId { get; set; } public Guid ActorId { get; set; }
[JsonIgnore]
public SnFediverseActor Actor { get; set; } = null!; public SnFediverseActor Actor { get; set; } = null!;
public Guid TargetActorId { get; set; } public Guid TargetActorId { get; set; }
[JsonIgnore]
public SnFediverseActor TargetActor { get; set; } = null!; public SnFediverseActor TargetActor { get; set; } = null!;
public RelationshipState State { get; set; } = RelationshipState.Pending; public RelationshipState State { get; set; } = RelationshipState.Pending;
@@ -29,13 +26,7 @@ public class SnFediverseRelationship : ModelBase
public Instant? FollowedAt { get; set; } public Instant? FollowedAt { get; set; }
public Instant? FollowedBackAt { get; set; } public Instant? FollowedBackAt { get; set; }
[MaxLength(4096)] [MaxLength(4096)] public string? RejectReason { get; set; }
public string? RejectReason { get; set; }
public bool IsLocalActor { get; set; }
public Guid? LocalAccountId { get; set; }
public Guid? LocalPublisherId { get; set; }
} }
public enum RelationshipState public enum RelationshipState

Binary file not shown.

View File

@@ -95,16 +95,22 @@ public class ActivityPubActivityProcessor(
return false; return false;
} }
var localActor = await deliveryService.GetOrCreateLocalActorAsync(targetPublisher);
if (localActor == null)
{
logger.LogWarning("Target publisher has no actor...");
return false;
}
logger.LogInformation("Target publisher found: {PublisherName} (ID: {Id})", logger.LogInformation("Target publisher found: {PublisherName} (ID: {Id})",
targetPublisher.Name, targetPublisher.Id); targetPublisher.Name, targetPublisher.Id);
var existingRelationship = await db.FediverseRelationships var existingRelationship = await db.FediverseRelationships
.FirstOrDefaultAsync(r => .FirstOrDefaultAsync(r =>
r.ActorId == actor.Id && r.ActorId == actor.Id &&
r.TargetActorId == actor.Id && r.TargetActorId == actor.Id);
r.IsLocalActor);
if (existingRelationship != null && existingRelationship.State == RelationshipState.Accepted) if (existingRelationship is { State: RelationshipState.Accepted })
{ {
logger.LogInformation("Follow relationship already exists and is accepted. ActorId: {ActorId}, PublisherId: {PublisherId}", logger.LogInformation("Follow relationship already exists and is accepted. ActorId: {ActorId}, PublisherId: {PublisherId}",
actor.Id, targetPublisher.Id); actor.Id, targetPublisher.Id);
@@ -116,9 +122,7 @@ public class ActivityPubActivityProcessor(
existingRelationship = new SnFediverseRelationship existingRelationship = new SnFediverseRelationship
{ {
ActorId = actor.Id, ActorId = actor.Id,
TargetActorId = actor.Id, TargetActorId = localActor.Id,
IsLocalActor = true,
LocalPublisherId = targetPublisher.Id,
State = RelationshipState.Pending, State = RelationshipState.Pending,
IsFollowing = false, IsFollowing = false,
IsFollowedBy = true IsFollowedBy = true
@@ -158,7 +162,6 @@ public class ActivityPubActivityProcessor(
.Include(r => r.Actor) .Include(r => r.Actor)
.Include(r => r.TargetActor) .Include(r => r.TargetActor)
.FirstOrDefaultAsync(r => .FirstOrDefaultAsync(r =>
r.IsLocalActor &&
r.TargetActorId == actor.Id && r.TargetActorId == actor.Id &&
r.State == RelationshipState.Pending); r.State == RelationshipState.Pending);
@@ -188,7 +191,6 @@ public class ActivityPubActivityProcessor(
var relationship = await db.FediverseRelationships var relationship = await db.FediverseRelationships
.FirstOrDefaultAsync(r => .FirstOrDefaultAsync(r =>
r.IsLocalActor &&
r.TargetActorId == actor.Id); r.TargetActorId == actor.Id);
if (relationship == null) if (relationship == null)
@@ -213,21 +215,18 @@ public class ActivityPubActivityProcessor(
if (objectValue == null) if (objectValue == null)
return false; return false;
var objectDict = objectValue as Dictionary<string, object>; if (objectValue is not Dictionary<string, object> objectDict) return false;
if (objectDict != null) var objectType = objectDict.GetValueOrDefault("type")?.ToString();
switch (objectType)
{ {
var objectType = objectDict.GetValueOrDefault("type")?.ToString(); case "Follow":
switch (objectType) return await UndoFollowAsync(actorUri, objectDict.GetValueOrDefault("id")?.ToString());
{ case "Like":
case "Follow": return await UndoLikeAsync(actorUri, objectDict.GetValueOrDefault("id")?.ToString());
return await UndoFollowAsync(actorUri, objectDict.GetValueOrDefault("id")?.ToString()); case "Announce":
case "Like": return await UndoAnnounceAsync(actorUri, objectDict.GetValueOrDefault("id")?.ToString());
return await UndoLikeAsync(actorUri, objectDict.GetValueOrDefault("id")?.ToString()); default:
case "Announce": return false;
return await UndoAnnounceAsync(actorUri, objectDict.GetValueOrDefault("id")?.ToString());
default:
return false;
}
} }
return false; return false;

View File

@@ -191,7 +191,7 @@ public class ActivityPubController(
var relationshipsQuery = db.FediverseRelationships var relationshipsQuery = db.FediverseRelationships
.Include(r => r.Actor) .Include(r => r.Actor)
.Where(r => r.LocalPublisherId == publisher.Id && r.IsFollowedBy); .Where(r => r.Actor.PublisherId == publisher.Id && r.IsFollowedBy);
var totalItems = await relationshipsQuery.CountAsync(); var totalItems = await relationshipsQuery.CountAsync();
@@ -257,7 +257,7 @@ public class ActivityPubController(
var relationshipsQuery = db.FediverseRelationships var relationshipsQuery = db.FediverseRelationships
.Include(r => r.TargetActor) .Include(r => r.TargetActor)
.Where(r => r.LocalPublisherId == publisher.Id && r.IsFollowing); .Where(r => r.Actor.PublisherId == publisher.Id && r.IsFollowing);
var totalItems = await relationshipsQuery.CountAsync(); var totalItems = await relationshipsQuery.CountAsync();

View File

@@ -87,16 +87,28 @@ public class ActivityPubDeliveryService(
["object"] = targetActorUri ["object"] = targetActorUri
}; };
await db.FediverseRelationships.AddAsync(new SnFediverseRelationship var existingRelationship = await db.FediverseRelationships
.FirstOrDefaultAsync(r =>
r.ActorId == localActor.Id &&
r.TargetActorId == targetActor.Id);
if (existingRelationship == null)
{ {
IsLocalActor = true, existingRelationship = new SnFediverseRelationship
LocalPublisherId = publisher.Id, {
ActorId = localActor.Id, ActorId = localActor.Id,
TargetActorId = targetActor.Id, TargetActorId = targetActor.Id,
State = RelationshipState.Pending, State = RelationshipState.Pending,
IsFollowing = true, IsFollowing = true,
IsFollowedBy = false IsFollowedBy = false
}); };
db.FediverseRelationships.Add(existingRelationship);
}
else
{
existingRelationship.IsFollowing = true;
existingRelationship.State = RelationshipState.Pending;
}
await db.SaveChangesAsync(); await db.SaveChangesAsync();
@@ -139,7 +151,7 @@ public class ActivityPubDeliveryService(
} }
}; };
var followers = await GetRemoteFollowersAsync(publisher.Id); var followers = await GetRemoteFollowersAsync();
var successCount = 0; var successCount = 0;
foreach (var follower in followers) foreach (var follower in followers)
@@ -200,7 +212,7 @@ public class ActivityPubDeliveryService(
return false; return false;
var actorUrl = $"https://{Domain}/activitypub/actors/{publisher.Name}"; var actorUrl = $"https://{Domain}/activitypub/actors/{publisher.Name}";
var followers = await GetRemoteFollowersAsync(publisher.Id); var followers = await GetRemoteFollowersAsync();
var activity = new Dictionary<string, object> var activity = new Dictionary<string, object>
{ {
@@ -297,19 +309,16 @@ public class ActivityPubDeliveryService(
} }
} }
private async Task<List<SnFediverseActor>> GetRemoteFollowersAsync(Guid publisherId) private async Task<List<SnFediverseActor>> GetRemoteFollowersAsync()
{ {
return await db.FediverseRelationships return await db.FediverseRelationships
.Include(r => r.TargetActor) .Include(r => r.TargetActor)
.Where(r => .Where(r => r.IsFollowedBy)
r.LocalPublisherId == publisherId &&
r.IsFollowedBy &&
r.IsLocalActor)
.Select(r => r.TargetActor) .Select(r => r.TargetActor)
.ToListAsync(); .ToListAsync();
} }
private async Task<SnFediverseActor?> GetOrCreateLocalActorAsync(SnPublisher publisher) public async Task<SnFediverseActor?> GetOrCreateLocalActorAsync(SnPublisher publisher)
{ {
var actorUrl = $"https://{Domain}/activitypub/actors/{publisher.Name}"; var actorUrl = $"https://{Domain}/activitypub/actors/{publisher.Name}";
@@ -347,7 +356,8 @@ public class ActivityPubDeliveryService(
FollowingUri = $"{actorUrl}/following", FollowingUri = $"{actorUrl}/following",
AvatarUrl = publisher.Picture != null ? $"{assetsBaseUrl}/{publisher.Picture.Id}" : null, AvatarUrl = publisher.Picture != null ? $"{assetsBaseUrl}/{publisher.Picture.Id}" : null,
HeaderUrl = publisher.Background != null ? $"{assetsBaseUrl}/{publisher.Background.Id}" : null, HeaderUrl = publisher.Background != null ? $"{assetsBaseUrl}/{publisher.Background.Id}" : null,
InstanceId = instance.Id InstanceId = instance.Id,
PublisherId = publisher.Id,
}; };
db.FediverseActors.Add(localActor); db.FediverseActors.Add(localActor);

View File

@@ -110,8 +110,7 @@ public class ActivityPubFollowController(
.Include(r => r.TargetActor) .Include(r => r.TargetActor)
.ThenInclude(a => a.Instance) .ThenInclude(a => a.Instance)
.Where(r => .Where(r =>
r.IsLocalActor && r.Actor.PublisherId == publisher.Id &&
r.LocalPublisherId == publisher.Id &&
r.IsFollowing && r.IsFollowing &&
r.State == RelationshipState.Accepted) r.State == RelationshipState.Accepted)
.OrderByDescending(r => r.FollowedAt) .OrderByDescending(r => r.FollowedAt)
@@ -143,8 +142,7 @@ public class ActivityPubFollowController(
.Include(r => r.Actor) .Include(r => r.Actor)
.ThenInclude(a => a.Instance) .ThenInclude(a => a.Instance)
.Where(r => .Where(r =>
!r.IsLocalActor && r.Actor.PublisherId == publisher.Id &&
r.LocalPublisherId == publisher.Id &&
r.IsFollowedBy && r.IsFollowedBy &&
r.State == RelationshipState.Accepted) r.State == RelationshipState.Accepted)
.OrderByDescending(r => r.FollowedAt ?? r.CreatedAt) .OrderByDescending(r => r.FollowedAt ?? r.CreatedAt)
@@ -188,27 +186,24 @@ public class ActivityPubFollowController(
var followingCount = await db.FediverseRelationships var followingCount = await db.FediverseRelationships
.CountAsync(r => .CountAsync(r =>
r.IsLocalActor && r.Actor.PublisherId == publisher.Id &&
r.LocalPublisherId == publisher.Id &&
r.IsFollowing && r.IsFollowing &&
r.State == RelationshipState.Accepted); r.State == RelationshipState.Accepted);
var followersCount = await db.FediverseRelationships var followersCount = await db.FediverseRelationships
.CountAsync(r => .CountAsync(r =>
!r.IsLocalActor && r.Actor.PublisherId == publisher.Id &&
r.LocalPublisherId == publisher.Id &&
r.IsFollowedBy && r.IsFollowedBy &&
r.State == RelationshipState.Accepted); r.State == RelationshipState.Accepted);
var pendingCount = await db.FediverseRelationships var pendingCount = await db.FediverseRelationships
.CountAsync(r => .CountAsync(r =>
r.IsLocalActor && r.Actor.PublisherId == publisher.Id &&
r.LocalPublisherId == publisher.Id &&
r.State == RelationshipState.Pending); r.State == RelationshipState.Pending);
var relationships = await db.FediverseRelationships var relationships = await db.FediverseRelationships
.Include(r => r.TargetActor) .Include(r => r.TargetActor)
.Where(r => r.IsLocalActor && r.LocalPublisherId == publisher.Id) .Where(r => r.Actor.PublisherId == publisher.Id)
.OrderByDescending(r => r.FollowedAt ?? r.CreatedAt) .OrderByDescending(r => r.FollowedAt ?? r.CreatedAt)
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,60 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace DysonNetwork.Sphere.Migrations
{
/// <inheritdoc />
public partial class BetterLocalActor : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "is_local_actor",
table: "fediverse_relationships");
migrationBuilder.DropColumn(
name: "local_account_id",
table: "fediverse_relationships");
migrationBuilder.DropColumn(
name: "local_publisher_id",
table: "fediverse_relationships");
migrationBuilder.AddColumn<Guid>(
name: "publisher_id",
table: "fediverse_actors",
type: "uuid",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "publisher_id",
table: "fediverse_actors");
migrationBuilder.AddColumn<bool>(
name: "is_local_actor",
table: "fediverse_relationships",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<Guid>(
name: "local_account_id",
table: "fediverse_relationships",
type: "uuid",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "local_publisher_id",
table: "fediverse_relationships",
type: "uuid",
nullable: true);
}
}
}

View File

@@ -495,6 +495,10 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("character varying(2048)") .HasColumnType("character varying(2048)")
.HasColumnName("public_key_id"); .HasColumnName("public_key_id");
b.Property<Guid?>("PublisherId")
.HasColumnType("uuid")
.HasColumnName("publisher_id");
b.Property<string>("Type") b.Property<string>("Type")
.IsRequired() .IsRequired()
.HasMaxLength(2048) .HasMaxLength(2048)
@@ -877,22 +881,10 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("is_following"); .HasColumnName("is_following");
b.Property<bool>("IsLocalActor")
.HasColumnType("boolean")
.HasColumnName("is_local_actor");
b.Property<bool>("IsMuting") b.Property<bool>("IsMuting")
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("is_muting"); .HasColumnName("is_muting");
b.Property<Guid?>("LocalAccountId")
.HasColumnType("uuid")
.HasColumnName("local_account_id");
b.Property<Guid?>("LocalPublisherId")
.HasColumnType("uuid")
.HasColumnName("local_publisher_id");
b.Property<string>("RejectReason") b.Property<string>("RejectReason")
.HasMaxLength(4096) .HasMaxLength(4096)
.HasColumnType("character varying(4096)") .HasColumnType("character varying(4096)")