LuckPerms

LuckPerms

41.4k Downloads

Group.getUsers() method

Dymeth opened this issue ยท 9 comments

commented

Hello. I am making a plugin for BungeeCord, which needs to get a list of all players in a particular group (including offline players). I did not find this function in the API. A similar situation is described here: spigotmc.org/threads/417525

Earlier, to solve my problem, I use the implementation code of the command /lp group <group> listmembers
(copy-paste from me.lucko.luckperms.common.commands.group.GroupListMembers with some reflection).
This was quite inconvenient, and besides, after a recent update, my code stopped working, and I could not update it to a working state.

It would be great to be able to query the data of all the players in the group. Or, at least their UUIDs or even nicknames. I would be fine with any option that does not require using the LuckPerms core instead of LP API.

It could be Group.getUsers() or something like that

commented

You can use the UserManager#searchAll(NodeMatcher) to find all users having the InheritanceNode representing the group. Check out the factory methods for NodeMatcher to see how to create one and pass to the searchAll method.

commented

So this is my solution. Ugh, it took about an hour to figure it out. It should be keep in mind that this method should be called asynchronously, since it loads data directly from storage (disk, database, etc.).

However, I think it would be great to see a simpler solution to this question in the API - I'm not the only one who needs this feature.

private List<User> getUsersInGroup(String groupName) {
    LuckPerms api = LuckPermsProvider.get();
    Group group = api.getGroupManager().getGroup(groupName);
    if (group == null) throw new IllegalArgumentException("Group " + groupName + " not found");
    UserManager userManager = api.getUserManager();
    List<User> users = new ArrayList<>();
    for (UUID uuid : userManager.searchAll(NodeMatcher.key(InheritanceNode.builder(group).build())).join().keySet()) {
        User user = userManager.isLoaded(uuid) ? userManager.getUser(uuid) : userManager.loadUser(uuid).join();
        if (user == null) throw new IllegalStateException("Could not load data of " + uuid);
        users.add(user);
    }
    return users;
}
commented

this method should be called asynchronously, since it loads data directly from storage

So would a possible Group#getMembers() method? Users aren't magically loaded into memory already.

You would still need to loop through all members to add the node you want etc.

commented

So would a possible Group#getMembers() method? Users aren't magically loaded into memory already.

You would still need to loop through all members to add the node you want etc.

This problem can be solved by returning CompletableFuture. Or by passing Callback/Consumer to the method. There are many options...

commented

UserManager#searchAll already returns a CompletableFuture...

commented

A bit more performant.. will load users in parallel.

I'm not sure whether this is something that I want to include as a utility method in the API itself - loading users is an expensive operation (relatively compared to the searchAll call) and so you should only do it if you really do need more than a UUID.

private CompletableFuture<List<User>> getUsersInGroup(String groupName) {
    NodeMatcher<InheritanceNode> matcher = NodeMatcher.key(InheritanceNode.builder(groupName).build());
    return luckPerms.getUserManager().searchAll(matcher).thenComposeAsync(results -> {
        List<CompletableFuture<User>> users = new ArrayList<>();
        return CompletableFuture.allOf(
                results.keySet().stream()
                        .map(uuid -> luckPerms.getUserManager().loadUser(uuid))
                        .peek(users::add)
                        .toArray(CompletableFuture[]::new)
        ).thenApply(x -> users.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList())
        );
    });
}
commented

A bit more performant.. will load users in parallel.

I'm not sure whether this is something that I want to include as a utility method in the API itself - loading users is an expensive operation (relatively compared to the searchAll call) and so you should only do it if you really do need more than a UUID.

Yes, I need exactly the User meta data.

But if we talk about the API, I think that getting the UUID will be enough. After receiving the UUID, everyone will be able to load user data if necessary. So the method I suggested could be called .getMemberIds()

commented

Is there a reason why you need a User object for each of them and not just a UUID?

commented

I think it's best if we leave this out of the API itself for now - of course the code I posted above will work for the time being.. if more people run into the same complication we can reconsider :)