Fix group sync not finding members that are not cached, enable chunking by default. Add GroupSyncResult for being able to interact with role

This commit is contained in:
Vankka 2023-06-18 22:36:59 +03:00
parent 67c609aa4f
commit 37476fd8f9
No known key found for this signature in database
GPG Key ID: 6E50CB7A29B96AD0
7 changed files with 136 additions and 102 deletions

View File

@ -54,6 +54,13 @@ public interface DiscordGuild extends JDAEntity<Guild>, Snowflake {
@Placeholder("server_member_count") @Placeholder("server_member_count")
int getMemberCount(); int getMemberCount();
/**
* Gets the bot's membership in the server.
* @return the connected bot's member
*/
@NotNull
DiscordGuildMember getSelfMember();
/** /**
* Retrieves a Discord guild member from Discord by id. * Retrieves a Discord guild member from Discord by id.
* @param id the id for the Discord guild member * @param id the id for the Discord guild member

View File

@ -76,6 +76,13 @@ public interface DiscordGuildMember extends JDAEntity<Member>, Mentionable {
*/ */
boolean hasRole(@NotNull DiscordRole role); boolean hasRole(@NotNull DiscordRole role);
/**
* If this member can interact (edit, add/take from members) with the specified role.
* @param role the role
* @return {@code true} if the member has a role above the specified role or is the server owner
*/
boolean canInteract(@NotNull DiscordRole role);
/** /**
* Gives the given role to this member. * Gives the given role to this member.
* @param role the role to give * @param role the role to give

View File

@ -34,7 +34,7 @@ public class MemberCachingConfig {
public boolean all = false; public boolean all = false;
@Comment("If members should be cached at startup, this requires the \"Server Members Intent\"") @Comment("If members should be cached at startup, this requires the \"Server Members Intent\"")
public boolean chunk = false; public boolean chunk = true;
@Comment("Filter for which servers should be cached at startup") @Comment("Filter for which servers should be cached at startup")
public GuildFilter chunkingServerFilter = new GuildFilter(); public GuildFilter chunkingServerFilter = new GuildFilter();

View File

@ -56,6 +56,11 @@ public class DiscordGuildImpl implements DiscordGuild {
return guild.getMemberCount(); return guild.getMemberCount();
} }
@Override
public @NotNull DiscordGuildMember getSelfMember() {
return discordSRV.discordAPI().getGuildMember(guild.getSelfMember());
}
@Override @Override
public @NotNull CompletableFuture<DiscordGuildMember> retrieveMemberById(long id) { public @NotNull CompletableFuture<DiscordGuildMember> retrieveMemberById(long id) {
return discordSRV.discordAPI().mapExceptions(() -> guild.retrieveMemberById(id) return discordSRV.discordAPI().mapExceptions(() -> guild.retrieveMemberById(id)

View File

@ -87,6 +87,11 @@ public class DiscordGuildMemberImpl implements DiscordGuildMember {
return roles.stream().anyMatch(role::equals); return roles.stream().anyMatch(role::equals);
} }
@Override
public boolean canInteract(@NotNull DiscordRole role) {
return member.canInteract(role.asJDA());
}
@Override @Override
public CompletableFuture<Void> addRole(@NotNull DiscordRole role) { public CompletableFuture<Void> addRole(@NotNull DiscordRole role) {
return discordSRV.discordAPI().mapExceptions(() -> return discordSRV.discordAPI().mapExceptions(() ->

View File

@ -18,7 +18,6 @@
package com.discordsrv.common.groupsync; package com.discordsrv.common.groupsync;
import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
import com.discordsrv.api.discord.entity.guild.DiscordRole; import com.discordsrv.api.discord.entity.guild.DiscordRole;
import com.discordsrv.api.discord.events.member.role.DiscordMemberRoleAddEvent; import com.discordsrv.api.discord.events.member.role.DiscordMemberRoleAddEvent;
import com.discordsrv.api.discord.events.member.role.DiscordMemberRoleRemoveEvent; import com.discordsrv.api.discord.events.member.role.DiscordMemberRoleRemoveEvent;
@ -316,85 +315,90 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
return CompletableFuture.completedFuture(GroupSyncResult.ROLE_DOESNT_EXIST); return CompletableFuture.completedFuture(GroupSyncResult.ROLE_DOESNT_EXIST);
} }
DiscordGuildMember member = role.getGuild().getMemberById(userId); if (!role.getGuild().getSelfMember().canInteract(role)) {
if (member == null) { return CompletableFuture.completedFuture(GroupSyncResult.ROLE_CANNOT_INTERACT);
return CompletableFuture.completedFuture(GroupSyncResult.NOT_A_GUILD_MEMBER);
} }
boolean hasRole = member.hasRole(role); return role.getGuild().retrieveMemberById(userId).thenCompose(member -> {
String groupName = pair.groupName; if (member == null) {
CompletableFuture<GroupSyncResult> resultFuture = new CompletableFuture<>(); return CompletableFuture.completedFuture(GroupSyncResult.NOT_A_GUILD_MEMBER);
hasGroup(player, groupName, pair.serverContext).whenComplete((hasGroup, t) -> {
if (t != null) {
discordSRV.logger().error("Failed to check if player " + player + " has group " + groupName, t);
resultFuture.complete(GroupSyncResult.PERMISSION_BACKEND_FAIL_CHECK);
return;
} }
if (hasRole == hasGroup) { boolean hasRole = member.hasRole(role);
resultFuture.complete(hasRole ? GroupSyncResult.BOTH_TRUE : GroupSyncResult.BOTH_FALSE); String groupName = pair.groupName;
// We're all good CompletableFuture<GroupSyncResult> resultFuture = new CompletableFuture<>();
return;
}
GroupSyncSide side = pair.tieBreaker(); hasGroup(player, groupName, pair.serverContext).whenComplete((hasGroup, t) -> {
GroupSyncDirection direction = pair.direction(); if (t != null) {
CompletableFuture<Void> future; discordSRV.logger().error("Failed to check if player " + player + " has group " + groupName, t);
GroupSyncResult result; resultFuture.complete(GroupSyncResult.PERMISSION_BACKEND_FAIL_CHECK);
if (hasRole) {
if (side == GroupSyncSide.DISCORD) {
// Has role, add group
if (direction == GroupSyncDirection.MINECRAFT_TO_DISCORD) {
resultFuture.complete(GroupSyncResult.WRONG_DIRECTION);
return;
}
result = GroupSyncResult.ADD_GROUP;
future = addGroup(player, groupName, pair.serverContext);
} else {
// Doesn't have group, remove role
if (direction == GroupSyncDirection.DISCORD_TO_MINECRAFT) {
resultFuture.complete(GroupSyncResult.WRONG_DIRECTION);
return;
}
result = GroupSyncResult.REMOVE_ROLE;
future = member.removeRole(role);
}
} else {
if (side == GroupSyncSide.DISCORD) {
// Doesn't have role, remove group
if (direction == GroupSyncDirection.MINECRAFT_TO_DISCORD) {
resultFuture.complete(GroupSyncResult.WRONG_DIRECTION);
return;
}
result = GroupSyncResult.REMOVE_GROUP;
future = removeGroup(player, groupName, pair.serverContext);
} else {
// Has group, add role
if (direction == GroupSyncDirection.DISCORD_TO_MINECRAFT) {
resultFuture.complete(GroupSyncResult.WRONG_DIRECTION);
return;
}
result = GroupSyncResult.ADD_ROLE;
future = member.addRole(role);
}
}
future.whenComplete((v, t2) -> {
if (t2 != null) {
discordSRV.logger().error("Failed to " + result + " to " + player + "/" + Long.toUnsignedString(userId), t2);
resultFuture.complete(GroupSyncResult.UPDATE_FAILED);
return; return;
} }
resultFuture.complete(result); if (hasRole == hasGroup) {
}); resultFuture.complete(hasRole ? GroupSyncResult.BOTH_TRUE : GroupSyncResult.BOTH_FALSE);
}); // We're all good
return;
}
return resultFuture; GroupSyncSide side = pair.tieBreaker();
GroupSyncDirection direction = pair.direction();
CompletableFuture<Void> future;
GroupSyncResult result;
if (hasRole) {
if (side == GroupSyncSide.DISCORD) {
// Has role, add group
if (direction == GroupSyncDirection.MINECRAFT_TO_DISCORD) {
resultFuture.complete(GroupSyncResult.WRONG_DIRECTION);
return;
}
result = GroupSyncResult.ADD_GROUP;
future = addGroup(player, groupName, pair.serverContext);
} else {
// Doesn't have group, remove role
if (direction == GroupSyncDirection.DISCORD_TO_MINECRAFT) {
resultFuture.complete(GroupSyncResult.WRONG_DIRECTION);
return;
}
result = GroupSyncResult.REMOVE_ROLE;
future = member.removeRole(role);
}
} else {
if (side == GroupSyncSide.DISCORD) {
// Doesn't have role, remove group
if (direction == GroupSyncDirection.MINECRAFT_TO_DISCORD) {
resultFuture.complete(GroupSyncResult.WRONG_DIRECTION);
return;
}
result = GroupSyncResult.REMOVE_GROUP;
future = removeGroup(player, groupName, pair.serverContext);
} else {
// Has group, add role
if (direction == GroupSyncDirection.DISCORD_TO_MINECRAFT) {
resultFuture.complete(GroupSyncResult.WRONG_DIRECTION);
return;
}
result = GroupSyncResult.ADD_ROLE;
future = member.addRole(role);
}
}
future.whenComplete((v, t2) -> {
if (t2 != null) {
discordSRV.logger().error("Failed to " + result + " to " + player + "/" + Long.toUnsignedString(userId), t2);
resultFuture.complete(GroupSyncResult.UPDATE_FAILED);
return;
}
resultFuture.complete(result);
});
});
return resultFuture;
});
} }
// Listeners & methods to indicate something changed // Listeners & methods to indicate something changed
@ -636,44 +640,49 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
return CompletableFuture.completedFuture(GroupSyncResult.ROLE_DOESNT_EXIST); return CompletableFuture.completedFuture(GroupSyncResult.ROLE_DOESNT_EXIST);
} }
DiscordGuildMember member = role.getGuild().getMemberById(userId); if (!role.getGuild().getSelfMember().canInteract(role)) {
if (member == null) { return CompletableFuture.completedFuture(GroupSyncResult.ROLE_CANNOT_INTERACT);
return CompletableFuture.completedFuture(GroupSyncResult.NOT_A_GUILD_MEMBER);
} }
Map<Long, Boolean> expected = expectedDiscordChanges.get(userId, key -> new ConcurrentHashMap<>()); return role.getGuild().retrieveMemberById(userId).thenCompose(member -> {
if (expected != null) { if (member == null) {
expected.put(roleId, remove); return CompletableFuture.completedFuture(GroupSyncResult.NOT_A_GUILD_MEMBER);
}
boolean hasRole = member.hasRole(role);
CompletableFuture<GroupSyncResult> future;
if (remove && hasRole) {
future = member.removeRole(role).thenApply(v -> GroupSyncResult.REMOVE_ROLE);
} else if (!remove && !hasRole) {
future = member.addRole(role).thenApply(v -> GroupSyncResult.ADD_ROLE);
} else {
if (expected != null) {
// Nothing needed to be changed, remove expectation
expected.remove(roleId);
} }
return CompletableFuture.completedFuture(GroupSyncResult.ALREADY_IN_SYNC);
}
CompletableFuture<GroupSyncResult> resultFuture = new CompletableFuture<>(); Map<Long, Boolean> expected = expectedDiscordChanges.get(userId, key -> new ConcurrentHashMap<>());
future.whenComplete((result, t) -> { if (expected != null) {
if (t != null) { expected.put(roleId, remove);
}
boolean hasRole = member.hasRole(role);
CompletableFuture<GroupSyncResult> future;
if (remove && hasRole) {
future = member.removeRole(role).thenApply(v -> GroupSyncResult.REMOVE_ROLE);
} else if (!remove && !hasRole) {
future = member.addRole(role).thenApply(v -> GroupSyncResult.ADD_ROLE);
} else {
if (expected != null) { if (expected != null) {
// Failed, remove expectation // Nothing needed to be changed, remove expectation
expected.remove(roleId); expected.remove(roleId);
} }
return CompletableFuture.completedFuture(GroupSyncResult.ALREADY_IN_SYNC);
resultFuture.complete(GroupSyncResult.UPDATE_FAILED);
discordSRV.logger().error("Failed to give/take role " + role + " to/from " + member, t);
return;
} }
resultFuture.complete(result);
CompletableFuture<GroupSyncResult> resultFuture = new CompletableFuture<>();
future.whenComplete((result, t) -> {
if (t != null) {
if (expected != null) {
// Failed, remove expectation
expected.remove(roleId);
}
resultFuture.complete(GroupSyncResult.UPDATE_FAILED);
discordSRV.logger().error("Failed to give/take role " + role + " to/from " + member, t);
return;
}
resultFuture.complete(result);
});
return resultFuture;
}); });
return resultFuture;
} }
} }

View File

@ -34,6 +34,7 @@ public enum GroupSyncResult {
// Errors // Errors
ROLE_DOESNT_EXIST("Role doesn't exist"), ROLE_DOESNT_EXIST("Role doesn't exist"),
ROLE_CANNOT_INTERACT("Bot doesn't have a role above the synced role (cannot interact)"),
NOT_A_GUILD_MEMBER("User is not part of the server the role is in"), NOT_A_GUILD_MEMBER("User is not part of the server the role is in"),
PERMISSION_BACKEND_FAIL_CHECK("Failed to check group status, error printed"), PERMISSION_BACKEND_FAIL_CHECK("Failed to check group status, error printed"),
UPDATE_FAILED("Failed to modify role/group, error printed"), UPDATE_FAILED("Failed to modify role/group, error printed"),