[backend/core] Federate blocks (ISH-169)
This commit is contained in:
parent
085671b00c
commit
3a866d04dd
6 changed files with 88 additions and 12 deletions
|
@ -469,7 +469,7 @@ public class User : IEntity
|
|||
|
||||
[InverseProperty(nameof(Tables.UserPublickey.User))]
|
||||
public virtual UserPublickey? UserPublickey { get; set; }
|
||||
|
||||
|
||||
[InverseProperty(nameof(Tables.UserSettings.User))]
|
||||
public virtual UserSettings? UserSettings { get; set; }
|
||||
|
||||
|
@ -592,4 +592,7 @@ public class User : IEntity
|
|||
: throw new Exception("Cannot access PublicUrl for remote user");
|
||||
|
||||
public string GetIdenticonUrl(string webDomain) => $"https://{webDomain}/identicon/{Id}";
|
||||
|
||||
public bool IsLocalUser => Host == null;
|
||||
public bool IsRemoteUser => Host != null;
|
||||
}
|
|
@ -154,6 +154,9 @@ public class ActivityHandlerService(
|
|||
case ASEmojiReact { Object: ASNote note } react:
|
||||
await noteSvc.RemoveReactionFromNoteAsync(note, resolvedActor, react.Content);
|
||||
return;
|
||||
case ASBlock { Object: ASActor blockee }:
|
||||
await UnblockAsync(resolvedActor, blockee);
|
||||
return;
|
||||
default:
|
||||
throw GracefulException.UnprocessableEntity("Undo activity object is invalid");
|
||||
}
|
||||
|
@ -266,6 +269,18 @@ public class ActivityHandlerService(
|
|||
await noteSvc.ReactToNoteAsync(note, resolvedActor, reaction.Content);
|
||||
return;
|
||||
}
|
||||
case ASBlock block:
|
||||
{
|
||||
if (block.Object is not ASActor blockee)
|
||||
throw GracefulException.UnprocessableEntity("Invalid or unsupported block target");
|
||||
var resolvedBlockee = await userResolver.ResolveAsync(blockee.Id, true);
|
||||
if (resolvedBlockee == null)
|
||||
throw GracefulException.UnprocessableEntity("Unknown block target");
|
||||
if (resolvedBlockee.Host != null)
|
||||
throw GracefulException.UnprocessableEntity("Refusing to process block between two remote users");
|
||||
await userSvc.BlockUserAsync(resolvedActor, resolvedBlockee);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
throw new NotImplementedException($"Activity type {activity.Type} is unknown");
|
||||
}
|
||||
|
@ -409,6 +424,17 @@ public class ActivityHandlerService(
|
|||
}
|
||||
}
|
||||
|
||||
private async Task UnblockAsync(User blocker, ASActor blockee)
|
||||
{
|
||||
var resolvedBlockee = await userResolver.ResolveAsync(blockee.Id, true);
|
||||
if (resolvedBlockee == null)
|
||||
throw GracefulException.UnprocessableEntity("Unknown block target");
|
||||
if (resolvedBlockee.Host != null)
|
||||
throw GracefulException
|
||||
.UnprocessableEntity("Refusing to process unblock between two remote users");
|
||||
await userSvc.UnblockUserAsync(blocker, resolvedBlockee);
|
||||
}
|
||||
|
||||
private async Task AcceptAsync(ASFollow obj, User actor)
|
||||
{
|
||||
var prefix = $"https://{config.Value.WebDomain}/follows/";
|
||||
|
|
|
@ -44,7 +44,9 @@ public class ActivityRenderer(
|
|||
|
||||
public ASDelete RenderDelete(ASActor actor, ASObject obj) => new()
|
||||
{
|
||||
Id = $"{obj.Id}#Delete", Actor = actor.Compact(), Object = obj
|
||||
Id = $"{obj.Id}#Delete",
|
||||
Actor = actor.Compact(),
|
||||
Object = obj
|
||||
};
|
||||
|
||||
public ASAccept RenderAccept(User followee, User follower, string requestId) => new()
|
||||
|
@ -56,7 +58,9 @@ public class ActivityRenderer(
|
|||
|
||||
public ASAccept RenderAccept(ASActor actor, ASObject obj) => new()
|
||||
{
|
||||
Id = GenerateActivityId(), Actor = actor.Compact(), Object = obj
|
||||
Id = GenerateActivityId(),
|
||||
Actor = actor.Compact(),
|
||||
Object = obj
|
||||
};
|
||||
|
||||
public ASLike RenderLike(NoteLike like)
|
||||
|
@ -93,7 +97,9 @@ public class ActivityRenderer(
|
|||
|
||||
var e = new ASEmoji
|
||||
{
|
||||
Id = emoji.PublicUrl, Name = name, Image = new ASImage { Url = new ASLink(emoji.PublicUrl) }
|
||||
Id = emoji.PublicUrl,
|
||||
Name = name,
|
||||
Image = new ASImage { Url = new ASLink(emoji.PublicUrl) }
|
||||
};
|
||||
|
||||
res.Tags = [e];
|
||||
|
@ -137,17 +143,23 @@ public class ActivityRenderer(
|
|||
|
||||
public static ASFollow RenderFollow(ASObject followerActor, ASObject followeeActor, string requestId) => new()
|
||||
{
|
||||
Id = requestId, Actor = ASActor.FromObject(followerActor), Object = ASActor.FromObject(followeeActor)
|
||||
Id = requestId,
|
||||
Actor = ASActor.FromObject(followerActor),
|
||||
Object = ASActor.FromObject(followeeActor)
|
||||
};
|
||||
|
||||
public ASUndo RenderUndo(ASActor actor, ASObject obj) => new()
|
||||
{
|
||||
Id = GenerateActivityId(), Actor = actor.Compact(), Object = obj
|
||||
Id = GenerateActivityId(),
|
||||
Actor = actor.Compact(),
|
||||
Object = obj
|
||||
};
|
||||
|
||||
public ASReject RenderReject(ASActor actor, ASObject obj) => new()
|
||||
{
|
||||
Id = GenerateActivityId(), Actor = actor.Compact(), Object = obj
|
||||
Id = GenerateActivityId(),
|
||||
Actor = actor.Compact(),
|
||||
Object = obj
|
||||
};
|
||||
|
||||
public ASReject RenderReject(User followee, User follower, string requestId) => new()
|
||||
|
@ -157,6 +169,13 @@ public class ActivityRenderer(
|
|||
Object = RenderFollow(userRenderer.RenderLite(follower), userRenderer.RenderLite(followee), requestId)
|
||||
};
|
||||
|
||||
public ASBlock RenderBlock(ASActor actor, ASActor obj, string blockId) => new()
|
||||
{
|
||||
Id = $"https://{config.Value.WebDomain}/blocks/{blockId}",
|
||||
Actor = actor.Compact(),
|
||||
Object = obj.Compact()
|
||||
};
|
||||
|
||||
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameter", Justification = "This only makes sense for users")]
|
||||
private string RenderFollowId(User follower, User followee) =>
|
||||
$"https://{config.Value.WebDomain}/follows/{follower.Id}/{followee.Id}";
|
||||
|
|
|
@ -33,6 +33,7 @@ public class ASActivity : ASObject
|
|||
public const string Reject = $"{Ns}#Reject";
|
||||
public const string Undo = $"{Ns}#Undo";
|
||||
public const string Like = $"{Ns}#Like";
|
||||
public const string Block = $"{Ns}#Block";
|
||||
|
||||
// Extensions
|
||||
public const string Bite = "https://ns.mia.jetzt/as#Bite";
|
||||
|
@ -116,14 +117,19 @@ public class ASUndo : ASActivity
|
|||
public ASUndo() => Type = Types.Undo;
|
||||
}
|
||||
|
||||
public class ASBlock : ASActivity
|
||||
{
|
||||
public ASBlock() => Type = Types.Block;
|
||||
}
|
||||
|
||||
public class ASLike : ASActivity
|
||||
{
|
||||
public ASLike() => Type = Types.Like;
|
||||
|
||||
|
||||
[J($"{Constants.MisskeyNs}#_misskey_reaction")]
|
||||
[JC(typeof(VC))]
|
||||
public string? MisskeyReaction { get; set; }
|
||||
|
||||
|
||||
[J($"{Constants.ActivityStreamsNs}#tag")]
|
||||
[JC(typeof(ASTagConverter))]
|
||||
public List<ASTag>? Tags { get; set; }
|
||||
|
@ -180,7 +186,7 @@ public class ASEmojiReact : ASActivity
|
|||
[J($"{Constants.ActivityStreamsNs}#content")]
|
||||
[JC(typeof(VC))]
|
||||
public required string Content { get; set; }
|
||||
|
||||
|
||||
[J($"{Constants.ActivityStreamsNs}#tag")]
|
||||
[JC(typeof(ASTagConverter))]
|
||||
public List<ASTag>? Tags { get; set; }
|
||||
|
|
|
@ -59,6 +59,7 @@ public class ASObject : ASObjectBase
|
|||
ASActivity.Types.Bite => token.ToObject<ASBite>(),
|
||||
ASActivity.Types.Announce => token.ToObject<ASAnnounce>(),
|
||||
ASActivity.Types.EmojiReact => token.ToObject<ASEmojiReact>(),
|
||||
ASActivity.Types.Block => token.ToObject<ASBlock>(),
|
||||
_ => token.ToObject<ASObject>()
|
||||
};
|
||||
case JTokenType.Array:
|
||||
|
|
|
@ -914,6 +914,14 @@ public class UserService(
|
|||
|
||||
await db.AddAsync(blocking);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
if (blocker.IsLocalUser && blockee.IsRemoteUser)
|
||||
{
|
||||
var actor = userRenderer.RenderLite(blocker);
|
||||
var obj = userRenderer.RenderLite(blockee);
|
||||
var activity = activityRenderer.RenderBlock(actor, obj, blocking.Id);
|
||||
await deliverSvc.DeliverToAsync(activity, blocker, blockee);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UnblockUserAsync(User blocker, User blockee)
|
||||
|
@ -921,8 +929,21 @@ public class UserService(
|
|||
if (!blockee.PrecomputedIsBlockedBy ?? false)
|
||||
return;
|
||||
|
||||
await db.Blockings.Where(p => p.Blocker == blocker && p.Blockee == blockee).ExecuteDeleteAsync();
|
||||
|
||||
blockee.PrecomputedIsBlockedBy = false;
|
||||
|
||||
var blocking = await db.Blockings.FirstOrDefaultAsync(p => p.Blocker == blocker && p.Blockee == blockee);
|
||||
if (blocking == null) return;
|
||||
|
||||
db.Remove(blocking);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
if (blocker.IsLocalUser && blockee.IsRemoteUser)
|
||||
{
|
||||
var actor = userRenderer.RenderLite(blocker);
|
||||
var obj = userRenderer.RenderLite(blockee);
|
||||
var block = activityRenderer.RenderBlock(actor, obj, blocking.Id);
|
||||
var activity = activityRenderer.RenderUndo(actor, block);
|
||||
await deliverSvc.DeliverToAsync(activity, blocker, blockee);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue