using System.Text.Json; using Iceshrimp.Backend.Controllers.Web.Schemas; using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Extensions; using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.MfmSharp; using Iceshrimp.Shared.Configuration; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using static Iceshrimp.Backend.Core.Federation.ActivityPub.UserResolver; namespace Iceshrimp.Backend.Core.Services; public class ImportExportService( DatabaseContext db, ILogger logger, IOptions instance, CacheService cacheSvc, UserService userSvc, NoteService noteSvc, DriveService driveSvc, ActivityPub.UserResolver userResolver ) : IScopedService { public async Task ExportFollowingAsync(User user) { var followees = await db.Followings .Include(p => p.Followee) .Where(p => p.FollowerId == user.Id) .Select(p => p.Followee) .Where(p => !p.IsDeleted && !p.IsSystemUser && p.MovedToUri == null) .OrderBy(p => p.Host) .ThenBy(p => p.UsernameLower) .Select(p => p.GetFqn(instance.Value.AccountDomain)) .ToListAsync(); return string.Join("\n", followees); } public async Task ImportFollowingAsync(User user, List fqns) { foreach (var fqn in fqns) { try { var followee = await userResolver.ResolveAsync($"acct:{fqn}", ResolveFlags.Acct); await userSvc.FollowUserAsync(user, followee); } catch (Exception e) { logger.LogWarning("Failed to import follow {followee} for user {follower}: {error}", fqn, user.Id, e); } } await QueryableTimelineExtensions.ResetHeuristicAsync(user, cacheSvc); } public async Task ImportNotesAsync(User user, IFormFile file) { var notes = await JsonSerializer.DeserializeAsync>(file.OpenReadStream(), JsonSerialization.Options); if (notes == null) throw GracefulException.BadRequest("File is not an Iceshrimp note export"); notes = notes.FindAll(p => p is { LocalOnly: false, Poll: null, Visibility: Note.NoteVisibility.Public or Note.NoteVisibility.Home, VisibleUserIds: [] }); var importedNotes = new Dictionary(); foreach (var exportNote in notes) { if (exportNote.ReplyId != null && !importedNotes.ContainsKey(exportNote.ReplyId)) continue; if (exportNote.RenoteId != null && !importedNotes.ContainsKey(exportNote.RenoteId)) continue; var attachments = new List(); foreach (var exportNoteFile in exportNote.Files) { var attachment = await driveSvc.StoreFileAsync(exportNoteFile.Url, user, exportNoteFile.IsSensitive, exportNoteFile.Comment, exportNoteFile.Type); if (attachment != null) attachments.Add(attachment); } var note = new NoteService.NoteCreationData { User = user, Visibility = exportNote.Visibility, Text = exportNote.Text?.Replace("@", "@\\"), Cw = exportNote.Cw, Reply = exportNote.ReplyId != null ? importedNotes.GetValueOrDefault(exportNote.ReplyId) : null, Renote = exportNote.RenoteId != null ? importedNotes.GetValueOrDefault(exportNote.RenoteId) : null, Attachments = attachments.Count != 0 ? attachments : null, CreatedAt = exportNote.CreatedAt }; var newNote = await noteSvc.CreateNoteAsync(note); importedNotes.Add(exportNote.Id, newNote); } } }