using System; using System.Collections.Generic; using System.Linq; using Jellyfin.Data; using Jellyfin.Database.Implementations.Entities; using Jellyfin.Database.Implementations.Enums; using Jellyfin.Plugin.WatchedTogether.Configuration; using MediaBrowser.Controller.Library; using Microsoft.Extensions.Logging; namespace Jellyfin.Plugin.WatchedTogether.Services; /// /// Reads group membership from plugin configuration and resolves it against live user records. /// public class GroupService : IGroupService { private readonly IUserManager _userManager; private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// /// The user manager. /// The logger. public GroupService(IUserManager userManager, ILogger logger) { _userManager = userManager; _logger = logger; } /// public SharedGroup? GetGroupForSharedUser(Guid sharedUserId) { var config = Plugin.Instance?.Configuration; if (config is null) { return null; } var group = config.Groups.FirstOrDefault(g => g.SharedUserId == sharedUserId); return group is null || group.IsDisabled ? null : group; } /// public IReadOnlyList GetEligibleMembers(SharedGroup group) { ArgumentNullException.ThrowIfNull(group); var members = new List(group.MemberUserIds.Count); foreach (var memberId in group.MemberUserIds) { var member = _userManager.GetUserById(memberId); if (member is null) { // Stale entry; PruneDeletedUser clears these when the deletion is observed. continue; } // A disabled member should no longer be able to unlock the shared account, and should // not receive its watched state either. if (member.HasPermission(PermissionKind.IsDisabled)) { continue; } members.Add(member); } return members; } /// public bool IsSharedAccount(Guid userId) { var config = Plugin.Instance?.Configuration; return config is not null && config.Groups.Any(g => g.SharedUserId == userId); } /// public void PruneDeletedUser(Guid userId) { var plugin = Plugin.Instance; if (plugin is null) { return; } var config = plugin.Configuration; var changed = false; // Drop groups whose shared account itself was deleted - there is nothing left to log into. var orphaned = config.Groups.Where(g => g.SharedUserId == userId).ToList(); foreach (var group in orphaned) { config.Groups.Remove(group); changed = true; _logger.LogInformation("Removed group for deleted shared account {SharedUserId}", userId); } foreach (var group in config.Groups) { if (!group.MemberUserIds.Remove(userId)) { continue; } changed = true; _logger.LogInformation( "Removed deleted member {MemberId} from group {SharedUserId}", userId, group.SharedUserId); // A group needs at least two members to mean anything; suspend rather than delete so // an admin can add a replacement member and re-enable it. if (group.MemberUserIds.Count < 2 && !group.IsDisabled) { group.IsDisabled = true; _logger.LogWarning( "Group {SharedUserId} disabled: fewer than two members remain", group.SharedUserId); } } if (changed) { plugin.UpdateConfiguration(config); } } }