From 62aa5f30756aa08b34ffca1ab6790aaff528660c Mon Sep 17 00:00:00 2001 From: pancakes Date: Thu, 24 Oct 2024 19:26:12 +1000 Subject: [PATCH] [backend/api] Add follow list import endpoint --- .../Controllers/Web/SettingsController.cs | 25 +++++++++++++++++- .../Core/Services/UserService.cs | 26 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/Iceshrimp.Backend/Controllers/Web/SettingsController.cs b/Iceshrimp.Backend/Controllers/Web/SettingsController.cs index 867da6a6..b3e413e6 100644 --- a/Iceshrimp.Backend/Controllers/Web/SettingsController.cs +++ b/Iceshrimp.Backend/Controllers/Web/SettingsController.cs @@ -1,9 +1,11 @@ using System.Net; using System.Net.Mime; +using AngleSharp.Text; using Iceshrimp.Backend.Controllers.Shared.Attributes; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Middleware; +using Iceshrimp.Backend.Core.Services; using Iceshrimp.Shared.Schemas.Web; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.RateLimiting; @@ -16,7 +18,7 @@ namespace Iceshrimp.Backend.Controllers.Web; [EnableRateLimiting("sliding")] [Route("/api/iceshrimp/settings")] [Produces(MediaTypeNames.Application.Json)] -public class SettingsController(DatabaseContext db) : ControllerBase +public class SettingsController(DatabaseContext db, UserService userSvc) : ControllerBase { [HttpGet] [ProducesResults(HttpStatusCode.OK)] @@ -63,4 +65,25 @@ public class SettingsController(DatabaseContext db) : ControllerBase await db.ReloadEntityAsync(settings); return settings; } + + [HttpPost("import/following")] + [ProducesResults(HttpStatusCode.Accepted)] + public async Task ImportFollowing(IFormFile file) + { + var user = HttpContext.GetUserOrFail(); + + var reader = new StreamReader(file.OpenReadStream()); + var contents = await reader.ReadToEndAsync(); + + var fqns = contents + .Split("\n") + .Where(line => !string.IsNullOrWhiteSpace(line)) + .Select(line => line.SplitCommas().First()) + .Where(fqn => fqn.Contains('@')) + .ToList(); + + await userSvc.ImportFollowingAsync(user, fqns); + + return Accepted(); + } } \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Services/UserService.cs b/Iceshrimp.Backend/Core/Services/UserService.cs index 3041dbbc..21af6d7d 100644 --- a/Iceshrimp.Backend/Core/Services/UserService.cs +++ b/Iceshrimp.Backend/Core/Services/UserService.cs @@ -39,6 +39,7 @@ public class UserService( QueueService queueSvc, EventService eventSvc, WebFingerService webFingerSvc, + CacheService cacheSvc, ActivityPub.FederationControlService fedCtrlSvc ) { @@ -945,6 +946,31 @@ public class UserService( await db.UserListMembers.Where(p => p.UserList.User == user && p.User == followee).ExecuteDeleteAsync(); } + public async Task ImportFollowingAsync(User user, List fqns) + { + foreach (var fqn in fqns.Select(fqn => fqn.Split("@"))) + { + var followee = await db.Users + .IncludeCommonProperties() + .FirstOrDefaultAsync(p => fqn[0].ToLower() == p.UsernameLower && + fqn[1] == (p.Host ?? instance.Value.AccountDomain)); + + if (followee == null) continue; + + try + { + await FollowUserAsync(user, followee); + } + catch (Exception e) + { + logger.LogWarning("Failed to import follow {followee} for user {follower}: {error}", + followee.Id, user.Id, e); + } + } + + await QueryableTimelineExtensions.ResetHeuristic(user, cacheSvc); + } + [SuppressMessage("ReSharper", "SuggestBaseTypeForParameter", Justification = "Method only makes sense for users")] private void UpdateUserPinnedNotesInBackground(ASActor actor, User user, bool force = false) {