mirror of
https://github.com/DiscordSRV/Ascension.git
synced 2024-11-22 11:55:54 +01:00
Group synchronization
This commit is contained in:
parent
f5acc00f8a
commit
634c123937
@ -27,6 +27,7 @@ import com.discordsrv.api.component.MinecraftComponentFactory;
|
||||
import com.discordsrv.api.discord.api.DiscordAPI;
|
||||
import com.discordsrv.api.discord.connection.DiscordConnectionDetails;
|
||||
import com.discordsrv.api.event.bus.EventBus;
|
||||
import com.discordsrv.api.linking.LinkingBackend;
|
||||
import com.discordsrv.api.placeholder.PlaceholderService;
|
||||
import com.discordsrv.api.player.DiscordSRVPlayer;
|
||||
import com.discordsrv.api.player.IPlayerProvider;
|
||||
@ -58,6 +59,8 @@ public interface DiscordSRVApi {
|
||||
@NotNull
|
||||
EventBus eventBus();
|
||||
|
||||
LinkingBackend linkingBackend();
|
||||
|
||||
/**
|
||||
* DiscordSRV's own placeholder service.
|
||||
* @return the {@link PlaceholderService} instance.
|
||||
|
@ -33,6 +33,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* A Discord server member.
|
||||
@ -60,6 +61,27 @@ public interface DiscordGuildMember extends DiscordUser, Mentionable {
|
||||
@NotNull
|
||||
List<DiscordRole> getRoles();
|
||||
|
||||
/**
|
||||
* Checks if the member has the given role.
|
||||
* @param role the role to check for
|
||||
* @return {@code true} if the member has the role
|
||||
*/
|
||||
boolean hasRole(@NotNull DiscordRole role);
|
||||
|
||||
/**
|
||||
* Gives the given role to this member.
|
||||
* @param role the role to give
|
||||
* @return a future
|
||||
*/
|
||||
CompletableFuture<Void> addRole(@NotNull DiscordRole role);
|
||||
|
||||
/**
|
||||
* Takes the given role from this member.
|
||||
* @param role the role to take
|
||||
* @return a future
|
||||
*/
|
||||
CompletableFuture<Void> removeRole(@NotNull DiscordRole role);
|
||||
|
||||
/**
|
||||
* Gets the effective name of this Discord server member.
|
||||
* @return the Discord server member's effective name
|
||||
|
@ -41,6 +41,13 @@ public interface DiscordRole extends Snowflake, Mentionable {
|
||||
*/
|
||||
Color DEFAULT_COLOR = new Color(0xFFFFFF);
|
||||
|
||||
/**
|
||||
* The Discord server this role is from.
|
||||
* @return the Discord server
|
||||
*/
|
||||
@NotNull
|
||||
DiscordGuild getGuild();
|
||||
|
||||
/**
|
||||
* Gets the name of the Discord role.
|
||||
* @return the role name
|
||||
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* This file is part of the DiscordSRV API, licensed under the MIT License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.discordsrv.api.discord.events;
|
||||
|
||||
import com.discordsrv.api.event.events.Event;
|
||||
|
||||
public interface DiscordEvent extends Event {
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This file is part of the DiscordSRV API, licensed under the MIT License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.discordsrv.api.discord.events.member;
|
||||
|
||||
import com.discordsrv.api.discord.api.entity.guild.DiscordGuildMember;
|
||||
import com.discordsrv.api.discord.events.DiscordEvent;
|
||||
|
||||
public abstract class AbstractDiscordMemberEvent implements DiscordEvent {
|
||||
|
||||
private final DiscordGuildMember member;
|
||||
|
||||
public AbstractDiscordMemberEvent(DiscordGuildMember member) {
|
||||
this.member = member;
|
||||
}
|
||||
|
||||
public DiscordGuildMember getMember() {
|
||||
return member;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* This file is part of the DiscordSRV API, licensed under the MIT License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.discordsrv.api.discord.events.member.role;
|
||||
|
||||
import com.discordsrv.api.discord.api.entity.guild.DiscordGuildMember;
|
||||
import com.discordsrv.api.discord.api.entity.guild.DiscordRole;
|
||||
import com.discordsrv.api.discord.events.member.AbstractDiscordMemberEvent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AbstractDiscordMemberRoleChangeEvent extends AbstractDiscordMemberEvent {
|
||||
|
||||
private final List<DiscordRole> roles;
|
||||
|
||||
public AbstractDiscordMemberRoleChangeEvent(DiscordGuildMember member, List<DiscordRole> roles) {
|
||||
super(member);
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
public List<DiscordRole> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* This file is part of the DiscordSRV API, licensed under the MIT License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.discordsrv.api.discord.events.member.role;
|
||||
|
||||
import com.discordsrv.api.discord.api.entity.guild.DiscordGuildMember;
|
||||
import com.discordsrv.api.discord.api.entity.guild.DiscordRole;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DiscordMemberRoleAddEvent extends AbstractDiscordMemberRoleChangeEvent {
|
||||
|
||||
public DiscordMemberRoleAddEvent(DiscordGuildMember member, List<DiscordRole> roles) {
|
||||
super(member, roles);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* This file is part of the DiscordSRV API, licensed under the MIT License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.discordsrv.api.discord.events.member.role;
|
||||
|
||||
import com.discordsrv.api.discord.api.entity.guild.DiscordGuildMember;
|
||||
import com.discordsrv.api.discord.api.entity.guild.DiscordRole;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DiscordMemberRoleRemoveEvent extends AbstractDiscordMemberRoleChangeEvent {
|
||||
|
||||
public DiscordMemberRoleRemoveEvent(DiscordGuildMember member, List<DiscordRole> roles) {
|
||||
super(member, roles);
|
||||
}
|
||||
}
|
@ -21,18 +21,18 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.discordsrv.api.discord.events;
|
||||
package com.discordsrv.api.discord.events.message;
|
||||
|
||||
import com.discordsrv.api.discord.api.entity.channel.DiscordDMChannel;
|
||||
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel;
|
||||
import com.discordsrv.api.discord.api.entity.channel.DiscordTextChannel;
|
||||
import com.discordsrv.api.discord.api.entity.channel.DiscordThreadChannel;
|
||||
import com.discordsrv.api.event.events.Event;
|
||||
import com.discordsrv.api.discord.events.DiscordEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class AbstractDiscordMessageEvent implements Event {
|
||||
public abstract class AbstractDiscordMessageEvent implements DiscordEvent {
|
||||
|
||||
private final DiscordMessageChannel channel;
|
||||
|
@ -21,7 +21,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.discordsrv.api.discord.events;
|
||||
package com.discordsrv.api.discord.events.message;
|
||||
|
||||
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel;
|
||||
|
@ -21,7 +21,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.discordsrv.api.discord.events;
|
||||
package com.discordsrv.api.discord.events.message;
|
||||
|
||||
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel;
|
||||
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessage;
|
@ -21,7 +21,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.discordsrv.api.discord.events;
|
||||
package com.discordsrv.api.discord.events.message;
|
||||
|
||||
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel;
|
||||
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessage;
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* This file is part of the DiscordSRV API, licensed under the MIT License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.discordsrv.api.linking;
|
||||
|
||||
import com.discordsrv.api.module.type.Module;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface LinkingBackend extends Module {
|
||||
|
||||
@Nullable
|
||||
UUID getLinkedAccount(long userId);
|
||||
|
||||
@Nullable
|
||||
Long getLinkedAccount(@NotNull UUID player);
|
||||
}
|
48
api/src/main/java/com/discordsrv/api/module/type/Module.java
Normal file
48
api/src/main/java/com/discordsrv/api/module/type/Module.java
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* This file is part of the DiscordSRV API, licensed under the MIT License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.discordsrv.api.module.type;
|
||||
|
||||
public interface Module {
|
||||
|
||||
default boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the priority of this Module given the lookup type.
|
||||
* @param type the type being looked up this could be an interface
|
||||
* @return the priority of this module, higher is more important. Default is 0
|
||||
*/
|
||||
@SuppressWarnings("unused") // API
|
||||
default int priority(Class<?> type) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
default void enable() {
|
||||
reload();
|
||||
}
|
||||
|
||||
default void disable() {}
|
||||
default void reload() {}
|
||||
}
|
@ -19,7 +19,9 @@
|
||||
package com.discordsrv.bukkit.integration;
|
||||
|
||||
import com.discordsrv.bukkit.BukkitDiscordSRV;
|
||||
import com.discordsrv.common.exception.MessageException;
|
||||
import com.discordsrv.common.function.CheckedSupplier;
|
||||
import com.discordsrv.common.future.util.CompletableFutureUtil;
|
||||
import com.discordsrv.common.module.type.PermissionDataProvider;
|
||||
import com.discordsrv.common.module.type.PluginIntegration;
|
||||
import net.milkbowl.vault.chat.Chat;
|
||||
@ -32,10 +34,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class VaultIntegration extends PluginIntegration<BukkitDiscordSRV>
|
||||
implements PermissionDataProvider.Permissions,
|
||||
PermissionDataProvider.Groups,
|
||||
PermissionDataProvider.PrefixAndSuffix {
|
||||
public class VaultIntegration extends PluginIntegration<BukkitDiscordSRV> implements PermissionDataProvider.Basic {
|
||||
|
||||
private Permission permission;
|
||||
private Chat chat;
|
||||
@ -100,13 +99,11 @@ public class VaultIntegration extends PluginIntegration<BukkitDiscordSRV>
|
||||
}
|
||||
|
||||
private <T> CompletableFuture<T> unsupported(@Nullable Object vault) {
|
||||
CompletableFuture<T> future = new CompletableFuture<>();
|
||||
future.completeExceptionally(new RuntimeException(
|
||||
return CompletableFutureUtil.failed(new MessageException(
|
||||
vault != null
|
||||
? "Vault backend " + vault.getClass().getName() + " unable to complete request"
|
||||
: "No vault backend available"
|
||||
? "Vault backend " + vault.getClass().getName() + " unable to complete request"
|
||||
: "No vault backend available"
|
||||
));
|
||||
return future;
|
||||
}
|
||||
|
||||
private <T> CompletableFuture<T> supply(CheckedSupplier<T> supplier, boolean async) {
|
||||
@ -131,7 +128,7 @@ public class VaultIntegration extends PluginIntegration<BukkitDiscordSRV>
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> hasGroup(UUID player, String groupName) {
|
||||
public CompletableFuture<Boolean> hasGroup(UUID player, String groupName, boolean includeInherited) {
|
||||
if (permission == null || !permission.isEnabled() || !permission.hasGroupSupport()) {
|
||||
return unsupported(permission);
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import com.discordsrv.api.discord.connection.DiscordConnectionDetails;
|
||||
import com.discordsrv.api.event.bus.EventBus;
|
||||
import com.discordsrv.api.event.events.lifecycle.DiscordSRVReloadEvent;
|
||||
import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent;
|
||||
import com.discordsrv.api.linking.LinkingBackend;
|
||||
import com.discordsrv.common.api.util.ApiInstanceUtil;
|
||||
import com.discordsrv.common.channel.ChannelConfigHelper;
|
||||
import com.discordsrv.common.channel.ChannelUpdaterModule;
|
||||
@ -39,6 +40,7 @@ import com.discordsrv.common.discord.details.DiscordConnectionDetailsImpl;
|
||||
import com.discordsrv.common.event.bus.EventBusImpl;
|
||||
import com.discordsrv.common.function.CheckedFunction;
|
||||
import com.discordsrv.common.function.CheckedRunnable;
|
||||
import com.discordsrv.common.groupsync.GroupSyncModule;
|
||||
import com.discordsrv.common.integration.LuckPermsIntegration;
|
||||
import com.discordsrv.common.logging.Logger;
|
||||
import com.discordsrv.common.logging.adapter.DependencyLoggerAdapter;
|
||||
@ -50,7 +52,7 @@ import com.discordsrv.common.messageforwarding.game.JoinMessageModule;
|
||||
import com.discordsrv.common.messageforwarding.game.LeaveMessageModule;
|
||||
import com.discordsrv.common.module.ModuleManager;
|
||||
import com.discordsrv.common.module.type.AbstractModule;
|
||||
import com.discordsrv.common.module.type.Module;
|
||||
import com.discordsrv.api.module.type.Module;
|
||||
import com.discordsrv.common.placeholder.ComponentResultStringifier;
|
||||
import com.discordsrv.common.placeholder.PlaceholderServiceImpl;
|
||||
import com.discordsrv.common.placeholder.context.GlobalTextHandlingContext;
|
||||
@ -84,8 +86,8 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
||||
|
||||
// DiscordSRV
|
||||
private final DiscordSRVLogger logger;
|
||||
private final ModuleManager moduleManager;
|
||||
private ChannelConfigHelper channelConfig;
|
||||
private ModuleManager moduleManager;
|
||||
private DiscordConnectionManager discordConnectionManager;
|
||||
|
||||
// Internal
|
||||
@ -94,6 +96,7 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
||||
public AbstractDiscordSRV() {
|
||||
ApiInstanceUtil.setInstance(this);
|
||||
this.logger = new DiscordSRVLogger(this);
|
||||
this.moduleManager = new ModuleManager(this);
|
||||
}
|
||||
|
||||
protected final void load() {
|
||||
@ -116,6 +119,11 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
||||
return eventBus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinkingBackend linkingBackend() {
|
||||
return getModule(LinkingBackend.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PlaceholderServiceImpl placeholderService() {
|
||||
return placeholderService;
|
||||
@ -306,12 +314,10 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
||||
// Register PlayerProvider listeners
|
||||
playerProvider().subscribe();
|
||||
|
||||
// Register modules
|
||||
moduleManager = new ModuleManager(this);
|
||||
|
||||
registerModule(ChannelUpdaterModule::new);
|
||||
registerModule(GlobalChannelLookupModule::new);
|
||||
registerModule(DiscordAPIEventModule::new);
|
||||
registerModule(GroupSyncModule::new);
|
||||
registerModule(LuckPermsIntegration::new);
|
||||
registerModule(DiscordChatMessageModule::new);
|
||||
registerModule(DiscordMessageMirroringModule::new);
|
||||
|
@ -29,7 +29,7 @@ import com.discordsrv.common.console.Console;
|
||||
import com.discordsrv.common.discord.api.DiscordAPIImpl;
|
||||
import com.discordsrv.common.discord.connection.DiscordConnectionManager;
|
||||
import com.discordsrv.common.module.type.AbstractModule;
|
||||
import com.discordsrv.common.module.type.Module;
|
||||
import com.discordsrv.api.module.type.Module;
|
||||
import com.discordsrv.common.placeholder.PlaceholderServiceImpl;
|
||||
import com.discordsrv.common.player.provider.AbstractPlayerProvider;
|
||||
import com.discordsrv.common.scheduler.Scheduler;
|
||||
@ -37,6 +37,7 @@ import com.discordsrv.common.logging.Logger;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
@ -84,6 +85,7 @@ public interface DiscordSRV extends DiscordSRVApi {
|
||||
DiscordConnectionManager discordConnectionManager();
|
||||
|
||||
// Modules
|
||||
@Nullable
|
||||
<T extends Module> T getModule(Class<T> moduleType);
|
||||
void registerModule(AbstractModule<?> module);
|
||||
void unregisterModule(AbstractModule<?> module);
|
||||
|
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.config.main;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.groupsync.enums.GroupSyncDirection;
|
||||
import com.discordsrv.common.groupsync.enums.GroupSyncSide;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@ConfigSerializable
|
||||
public class GroupSyncConfig {
|
||||
|
||||
@Comment("Group-Role pairs for group synchronization")
|
||||
public List<PairConfig> pairs = new ArrayList<>(Collections.singletonList(new PairConfig()));
|
||||
|
||||
@ConfigSerializable
|
||||
public static class PairConfig {
|
||||
|
||||
@Comment("The case-sensitive group name from your permissions plugin")
|
||||
public String groupName = "";
|
||||
|
||||
@Comment("The Discord role id")
|
||||
public Long roleId = 0L;
|
||||
|
||||
@Comment("The direction this group-role pair will synchronize in.\n"
|
||||
+ "Valid options: BIDIRECTIONAL, MINECRAFT_TO_DISCORD, DISCORD_TO_MINECRAFT")
|
||||
public String direction = GroupSyncDirection.BIDIRECTIONAL.name();
|
||||
|
||||
public GroupSyncDirection direction() {
|
||||
try {
|
||||
return GroupSyncDirection.valueOf(direction);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Comment("Timed resynchronization. This is required if you're not using LuckPerms")
|
||||
public TimerConfig timer = new TimerConfig();
|
||||
|
||||
@ConfigSerializable
|
||||
public static class TimerConfig {
|
||||
|
||||
@Comment("If timed synchronization of this group-role pair is enabled")
|
||||
public boolean enabled = true;
|
||||
|
||||
@Comment("The amount of minutes between cycles")
|
||||
public int cycleTime = 5;
|
||||
}
|
||||
|
||||
@Comment("Decides which side takes priority when using timed synchronization or the resync command\n"
|
||||
+ "Valid options: MINECRAFT, DISCORD")
|
||||
public String tieBreaker = GroupSyncSide.MINECRAFT.name();
|
||||
|
||||
public GroupSyncSide tieBreaker() {
|
||||
try {
|
||||
return GroupSyncSide.valueOf(tieBreaker);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Comment("The LuckPerms \"server\" context value, used when adding, removing and checking the groups of players.\n"
|
||||
+ "Make this blank (\"\") to use the current server's value, or \"global\" to not use the context")
|
||||
public String serverContext = "global";
|
||||
|
||||
public boolean isTheSameAs(PairConfig config) {
|
||||
return groupName.equals(config.groupName) && Objects.equals(roleId, config.roleId);
|
||||
}
|
||||
|
||||
public boolean validate(DiscordSRV discordSRV) {
|
||||
String label = "Group synchronization (" + groupName + ":" + Long.toUnsignedString(roleId) + ")";
|
||||
boolean invalidTieBreaker, invalidDirection = false;
|
||||
if ((invalidTieBreaker = (tieBreaker() == null)) || (invalidDirection = (direction == null))) {
|
||||
if (invalidTieBreaker) {
|
||||
discordSRV.logger().error(label + " has invalid tie-breaker: " + tieBreaker
|
||||
+ ", should be one of " + Arrays.toString(GroupSyncSide.values()));
|
||||
}
|
||||
if (invalidDirection) {
|
||||
discordSRV.logger().error(label + " has invalid direction: " + direction
|
||||
+ ", should be one of " + Arrays.toString(GroupSyncDirection.values()));
|
||||
}
|
||||
return false;
|
||||
} else if (direction() != GroupSyncDirection.BIDIRECTIONAL) {
|
||||
boolean minecraft;
|
||||
if ((direction() == GroupSyncDirection.MINECRAFT_TO_DISCORD) != (minecraft = (tieBreaker() == GroupSyncSide.MINECRAFT))) {
|
||||
String opposite = (minecraft ? GroupSyncSide.DISCORD : GroupSyncSide.MINECRAFT).name();
|
||||
discordSRV.logger().warning(label + " with direction "
|
||||
+ direction + " with tie-breaker "
|
||||
+ tieBreaker + " (should be " + opposite + ")");
|
||||
tieBreaker = opposite; // Fix the config
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String arrow;
|
||||
switch (direction()) {
|
||||
default:
|
||||
case BIDIRECTIONAL:
|
||||
arrow = "<->";
|
||||
break;
|
||||
case DISCORD_TO_MINECRAFT:
|
||||
arrow = "<-";
|
||||
break;
|
||||
case MINECRAFT_TO_DISCORD:
|
||||
arrow = "->";
|
||||
break;
|
||||
}
|
||||
return "PairConfig{" + groupName + arrow + roleId + '}';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -23,6 +23,7 @@ import com.discordsrv.common.config.annotation.DefaultOnly;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.ChannelConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@ -43,4 +44,7 @@ public class MainConfig implements Config {
|
||||
}};
|
||||
|
||||
public List<ChannelUpdaterConfig> channelUpdaters = new ArrayList<>(Collections.singletonList(new ChannelUpdaterConfig()));
|
||||
|
||||
@Comment("Configuration options for group-role synchronization")
|
||||
public GroupSyncConfig groupSync = new GroupSyncConfig();
|
||||
}
|
||||
|
@ -18,18 +18,26 @@
|
||||
|
||||
package com.discordsrv.common.discord.api;
|
||||
|
||||
import com.discordsrv.api.discord.events.DiscordMessageDeleteEvent;
|
||||
import com.discordsrv.api.discord.events.DiscordMessageReceiveEvent;
|
||||
import com.discordsrv.api.discord.events.DiscordMessageUpdateEvent;
|
||||
import com.discordsrv.api.discord.events.member.role.DiscordMemberRoleAddEvent;
|
||||
import com.discordsrv.api.discord.events.member.role.DiscordMemberRoleRemoveEvent;
|
||||
import com.discordsrv.api.discord.events.message.DiscordMessageDeleteEvent;
|
||||
import com.discordsrv.api.discord.events.message.DiscordMessageReceiveEvent;
|
||||
import com.discordsrv.api.discord.events.message.DiscordMessageUpdateEvent;
|
||||
import com.discordsrv.api.event.bus.Subscribe;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.discord.api.entity.channel.DiscordMessageChannelImpl;
|
||||
import com.discordsrv.common.discord.api.entity.guild.DiscordGuildMemberImpl;
|
||||
import com.discordsrv.common.discord.api.entity.guild.DiscordRoleImpl;
|
||||
import com.discordsrv.common.discord.api.entity.message.ReceivedDiscordMessageImpl;
|
||||
import com.discordsrv.common.module.type.AbstractModule;
|
||||
import net.dv8tion.jda.api.events.guild.member.GuildMemberRoleAddEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.GuildMemberRoleRemoveEvent;
|
||||
import net.dv8tion.jda.api.events.message.MessageDeleteEvent;
|
||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
||||
import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DiscordAPIEventModule extends AbstractModule<DiscordSRV> {
|
||||
|
||||
public DiscordAPIEventModule(DiscordSRV discordSRV) {
|
||||
@ -59,4 +67,20 @@ public class DiscordAPIEventModule extends AbstractModule<DiscordSRV> {
|
||||
event.getMessageIdLong()
|
||||
));
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGuildMemberRoleAdd(GuildMemberRoleAddEvent event) {
|
||||
discordSRV.eventBus().publish(new DiscordMemberRoleAddEvent(
|
||||
new DiscordGuildMemberImpl(discordSRV, event.getMember()),
|
||||
event.getRoles().stream().map(role -> new DiscordRoleImpl(discordSRV, role)).collect(Collectors.toList())
|
||||
));
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGuildMemberRoleRemove(GuildMemberRoleRemoveEvent event) {
|
||||
discordSRV.eventBus().publish(new DiscordMemberRoleRemoveEvent(
|
||||
new DiscordGuildMemberImpl(discordSRV, event.getMember()),
|
||||
event.getRoles().stream().map(role -> new DiscordRoleImpl(discordSRV, role)).collect(Collectors.toList())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -386,7 +386,7 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
public @NotNull Optional<DiscordRole> getRoleById(long id) {
|
||||
return discordSRV.jda()
|
||||
.map(jda -> jda.getRoleById(id))
|
||||
.map(DiscordRoleImpl::new);
|
||||
.map(role -> new DiscordRoleImpl(discordSRV, role));
|
||||
}
|
||||
|
||||
private class WebhookCacheLoader implements AsyncCacheLoader<Long, WebhookClient> {
|
||||
|
@ -31,9 +31,9 @@ import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class DiscordUserImpl implements DiscordUser {
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
private final User user;
|
||||
private final boolean self;
|
||||
protected final DiscordSRV discordSRV;
|
||||
protected final User user;
|
||||
protected final boolean self;
|
||||
|
||||
public DiscordUserImpl(DiscordSRV discordSRV, User user) {
|
||||
this.discordSRV = discordSRV;
|
||||
|
@ -80,14 +80,14 @@ public class DiscordGuildImpl implements DiscordGuild {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(new DiscordRoleImpl(role));
|
||||
return Optional.of(new DiscordRoleImpl(discordSRV, role));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<DiscordRole> getRoles() {
|
||||
List<DiscordRole> roles = new ArrayList<>();
|
||||
for (Role role : guild.getRoles()) {
|
||||
roles.add(new DiscordRoleImpl(role));
|
||||
roles.add(new DiscordRoleImpl(discordSRV, role));
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class DiscordGuildMemberImpl extends DiscordUserImpl implements DiscordGuildMember {
|
||||
|
||||
@ -52,7 +53,7 @@ public class DiscordGuildMemberImpl extends DiscordUserImpl implements DiscordGu
|
||||
|
||||
List<DiscordRole> roles = new ArrayList<>();
|
||||
for (Role role : member.getRoles()) {
|
||||
roles.add(new DiscordRoleImpl(role));
|
||||
roles.add(new DiscordRoleImpl(discordSRV, role));
|
||||
}
|
||||
this.roles = roles;
|
||||
this.color = new Color(member.getColorRaw());
|
||||
@ -73,6 +74,25 @@ public class DiscordGuildMemberImpl extends DiscordUserImpl implements DiscordGu
|
||||
return roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(@NotNull DiscordRole role) {
|
||||
return roles.stream().anyMatch(role::equals);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> addRole(@NotNull DiscordRole role) {
|
||||
return discordSRV.discordAPI().mapExceptions(() ->
|
||||
guild.getAsJDAGuild().addRoleToMember(member, role.getAsJDARole()).submit()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> removeRole(@NotNull DiscordRole role) {
|
||||
return discordSRV.discordAPI().mapExceptions(() ->
|
||||
guild.getAsJDAGuild().removeRoleFromMember(member, role.getAsJDARole()).submit()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getEffectiveServerAvatarUrl() {
|
||||
return member.getEffectiveAvatarUrl();
|
||||
|
@ -19,17 +19,23 @@
|
||||
package com.discordsrv.common.discord.api.entity.guild;
|
||||
|
||||
import com.discordsrv.api.color.Color;
|
||||
import com.discordsrv.api.discord.api.entity.guild.DiscordGuild;
|
||||
import com.discordsrv.api.discord.api.entity.guild.DiscordRole;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class DiscordRoleImpl implements DiscordRole {
|
||||
|
||||
private final Role role;
|
||||
private final DiscordGuild guild;
|
||||
private final Color color;
|
||||
|
||||
public DiscordRoleImpl(Role role) {
|
||||
public DiscordRoleImpl(DiscordSRV discordSRV, Role role) {
|
||||
this.role = role;
|
||||
this.guild = new DiscordGuildImpl(discordSRV, role.getGuild());
|
||||
this.color = new Color(role.getColorRaw());
|
||||
}
|
||||
|
||||
@ -38,6 +44,11 @@ public class DiscordRoleImpl implements DiscordRole {
|
||||
return role.getIdLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull DiscordGuild getGuild() {
|
||||
return guild;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getName() {
|
||||
return role.getName();
|
||||
@ -67,4 +78,17 @@ public class DiscordRoleImpl implements DiscordRole {
|
||||
public String toString() {
|
||||
return "ServerRole:" + getName() + "(" + Long.toUnsignedString(getId()) + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
DiscordRoleImpl that = (DiscordRoleImpl) o;
|
||||
return Objects.equals(role.getId(), that.role.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(role.getId());
|
||||
}
|
||||
}
|
||||
|
@ -184,7 +184,7 @@ public class JDAConnectionManager implements DiscordConnectionManager {
|
||||
} else if (o instanceof Member) {
|
||||
converted = new DiscordGuildMemberImpl(discordSRV, (Member) o);
|
||||
} else if (o instanceof Role) {
|
||||
converted = new DiscordRoleImpl((Role) o);
|
||||
converted = new DiscordRoleImpl(discordSRV, (Role) o);
|
||||
} else if (o instanceof ReceivedMessage) {
|
||||
converted = ReceivedDiscordMessageImpl.fromJDA(discordSRV, (Message) o);
|
||||
} else if (o instanceof User) {
|
||||
|
@ -16,28 +16,15 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.module.type;
|
||||
package com.discordsrv.common.exception;
|
||||
|
||||
public interface Module {
|
||||
import com.discordsrv.common.exception.util.ExceptionUtil;
|
||||
|
||||
default boolean isEnabled() {
|
||||
return true;
|
||||
public class MessageException extends RuntimeException {
|
||||
|
||||
@SuppressWarnings("ThrowableNotThrown")
|
||||
public MessageException(String message) {
|
||||
super(message);
|
||||
ExceptionUtil.minifyException(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the priority of this Module given the lookup type.
|
||||
* @param type the type being looked up this could be an interface
|
||||
* @return the priority of this module, higher is more important. Default is 0
|
||||
*/
|
||||
@SuppressWarnings("unused") // API
|
||||
default int priority(Class<?> type) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
default void enable() {
|
||||
reload();
|
||||
}
|
||||
|
||||
default void disable() {}
|
||||
default void reload() {}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.future.util;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public final class CompletableFutureUtil {
|
||||
|
||||
private CompletableFutureUtil() {}
|
||||
|
||||
/**
|
||||
* Same as {@link CompletableFuture#completedFuture(Object)} but for failing.
|
||||
*/
|
||||
public static <T> CompletableFuture<T> failed(Throwable throwable) {
|
||||
CompletableFuture<T> future = new CompletableFuture<>();
|
||||
future.completeExceptionally(throwable);
|
||||
return future;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> CompletableFuture<Set<T>> combine(Collection<CompletableFuture<T>> futures) {
|
||||
return combine(futures.toArray(new CompletableFuture[0]));
|
||||
}
|
||||
|
||||
public static <T> CompletableFuture<Set<T>> combine(CompletableFuture<T>[] futures) {
|
||||
CompletableFuture<Set<T>> future = new CompletableFuture<>();
|
||||
CompletableFuture.allOf(futures).whenComplete((v, t) -> {
|
||||
if (t != null) {
|
||||
future.completeExceptionally(t);
|
||||
return;
|
||||
}
|
||||
|
||||
Set<T> results = new HashSet<>();
|
||||
for (CompletableFuture<T> aFuture : futures) {
|
||||
results.add(aFuture.join());
|
||||
}
|
||||
future.complete(results);
|
||||
});
|
||||
return future;
|
||||
}
|
||||
}
|
@ -0,0 +1,563 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.groupsync;
|
||||
|
||||
import com.discordsrv.api.discord.api.entity.guild.DiscordGuildMember;
|
||||
import com.discordsrv.api.discord.api.entity.guild.DiscordRole;
|
||||
import com.discordsrv.api.discord.events.member.role.DiscordMemberRoleAddEvent;
|
||||
import com.discordsrv.api.discord.events.member.role.DiscordMemberRoleRemoveEvent;
|
||||
import com.discordsrv.api.event.bus.Subscribe;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.main.GroupSyncConfig;
|
||||
import com.discordsrv.common.future.util.CompletableFutureUtil;
|
||||
import com.discordsrv.common.groupsync.enums.GroupSyncCause;
|
||||
import com.discordsrv.common.groupsync.enums.GroupSyncDirection;
|
||||
import com.discordsrv.common.groupsync.enums.GroupSyncResult;
|
||||
import com.discordsrv.common.groupsync.enums.GroupSyncSide;
|
||||
import com.discordsrv.common.module.type.AbstractModule;
|
||||
import com.discordsrv.common.module.type.PermissionDataProvider;
|
||||
import com.discordsrv.common.player.IPlayer;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class GroupSyncModule extends AbstractModule<DiscordSRV> {
|
||||
|
||||
private final Map<GroupSyncConfig.PairConfig, Future<?>> pairs = new LinkedHashMap<>();
|
||||
private final Map<String, List<GroupSyncConfig.PairConfig>> groupsToPairs = new ConcurrentHashMap<>();
|
||||
private final Map<Long, List<GroupSyncConfig.PairConfig>> rolesToPairs = new ConcurrentHashMap<>();
|
||||
|
||||
private final Cache<Long, Map<Long, Boolean>> expectedDiscordChanges;
|
||||
private final Cache<UUID, Map<String, Boolean>> expectedMinecraftChanges;
|
||||
|
||||
public GroupSyncModule(DiscordSRV discordSRV) {
|
||||
super(discordSRV);
|
||||
this.expectedDiscordChanges = discordSRV.caffeineBuilder()
|
||||
.expireAfterWrite(30, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
this.expectedMinecraftChanges = discordSRV.caffeineBuilder()
|
||||
.expireAfterWrite(30, TimeUnit.SECONDS)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload() {
|
||||
synchronized (pairs) {
|
||||
pairs.values().forEach(future -> future.cancel(false));
|
||||
pairs.clear();
|
||||
groupsToPairs.clear();
|
||||
rolesToPairs.clear();
|
||||
|
||||
GroupSyncConfig config = discordSRV.config().groupSync;
|
||||
for (GroupSyncConfig.PairConfig pair : config.pairs) {
|
||||
String groupName = pair.groupName;
|
||||
long roleId = pair.roleId;
|
||||
if (StringUtils.isEmpty(groupName) || roleId == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!pair.validate(discordSRV)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean failed = false;
|
||||
for (GroupSyncConfig.PairConfig pairConfig : config.pairs) {
|
||||
if (pairConfig != pair && pair.isTheSameAs(pairConfig)) {
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (failed) {
|
||||
discordSRV.logger().error("Duplicate group synchronization pair: " + groupName + " to " + roleId);
|
||||
continue;
|
||||
}
|
||||
|
||||
Future<?> future = null;
|
||||
GroupSyncConfig.PairConfig.TimerConfig timer = pair.timer;
|
||||
if (timer != null && timer.enabled) {
|
||||
int cycleTime = timer.cycleTime;
|
||||
future = discordSRV.scheduler().runAtFixedRate(
|
||||
() -> resyncPair(pair, GroupSyncCause.TIMER),
|
||||
cycleTime,
|
||||
cycleTime,
|
||||
TimeUnit.MINUTES
|
||||
);
|
||||
}
|
||||
|
||||
pairs.put(pair, future);
|
||||
groupsToPairs.computeIfAbsent(groupName, key -> new ArrayList<>()).add(pair);
|
||||
rolesToPairs.computeIfAbsent(roleId, key -> new ArrayList<>()).add(pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void logSummary(
|
||||
UUID player,
|
||||
GroupSyncCause cause,
|
||||
Map<GroupSyncConfig.PairConfig, CompletableFuture<GroupSyncResult>> pairs
|
||||
) {
|
||||
CompletableFutureUtil.combine(pairs.values()).whenComplete((v, t) -> {
|
||||
SynchronizationSummary summary = new SynchronizationSummary(player, cause);
|
||||
for (Map.Entry<GroupSyncConfig.PairConfig, CompletableFuture<GroupSyncResult>> entry : pairs.entrySet()) {
|
||||
summary.add(entry.getKey(), entry.getValue().join());
|
||||
}
|
||||
discordSRV.logger().debug(summary.toString());
|
||||
});
|
||||
}
|
||||
|
||||
// Linked account helper methods
|
||||
|
||||
private Long getLinkedAccount(UUID player) {
|
||||
return discordSRV.linkingBackend().getLinkedAccount(player);
|
||||
}
|
||||
|
||||
private UUID getLinkedAccount(long userId) {
|
||||
return discordSRV.linkingBackend().getLinkedAccount(userId);
|
||||
}
|
||||
|
||||
// Permission data helper methods
|
||||
|
||||
private PermissionDataProvider.Groups getPermissionProvider() {
|
||||
PermissionDataProvider.GroupsContext groupsContext = discordSRV.getModule(PermissionDataProvider.GroupsContext.class);
|
||||
return groupsContext == null ? discordSRV.getModule(PermissionDataProvider.Groups.class) : groupsContext;
|
||||
}
|
||||
|
||||
private CompletableFuture<Boolean> hasGroup(
|
||||
UUID player,
|
||||
String groupName,
|
||||
@Nullable String serverContext
|
||||
) {
|
||||
PermissionDataProvider.Groups permissionProvider = getPermissionProvider();
|
||||
if (permissionProvider instanceof PermissionDataProvider.GroupsContext) {
|
||||
return ((PermissionDataProvider.GroupsContext) permissionProvider)
|
||||
.hasGroup(player, groupName, false, serverContext);
|
||||
} else {
|
||||
return permissionProvider.hasGroup(player, groupName, false);
|
||||
}
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> addGroup(
|
||||
UUID player,
|
||||
String groupName,
|
||||
@Nullable String serverContext
|
||||
) {
|
||||
PermissionDataProvider.Groups permissionProvider = getPermissionProvider();
|
||||
if (permissionProvider instanceof PermissionDataProvider.GroupsContext) {
|
||||
return ((PermissionDataProvider.GroupsContext) permissionProvider)
|
||||
.addGroup(player, groupName, serverContext);
|
||||
} else {
|
||||
return permissionProvider.addGroup(player, groupName);
|
||||
}
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> removeGroup(
|
||||
UUID player,
|
||||
String groupName,
|
||||
@Nullable String serverContext
|
||||
) {
|
||||
PermissionDataProvider.Groups permissionProvider = getPermissionProvider();
|
||||
if (permissionProvider instanceof PermissionDataProvider.GroupsContext) {
|
||||
return ((PermissionDataProvider.GroupsContext) permissionProvider)
|
||||
.removeGroup(player, groupName, serverContext);
|
||||
} else {
|
||||
return permissionProvider.removeGroup(player, groupName);
|
||||
}
|
||||
}
|
||||
|
||||
// Resync user
|
||||
|
||||
public void resync(UUID player, GroupSyncCause cause) {
|
||||
Long userId = getLinkedAccount(player);
|
||||
if (userId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
resync(player, userId, cause);
|
||||
}
|
||||
|
||||
public void resync(long userId, GroupSyncCause cause) {
|
||||
UUID player = getLinkedAccount(userId);
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
resync(player, userId, cause);
|
||||
}
|
||||
|
||||
public void resync(UUID player, long userId, GroupSyncCause cause) {
|
||||
Map<GroupSyncConfig.PairConfig, CompletableFuture<GroupSyncResult>> futures = new LinkedHashMap<>();
|
||||
for (GroupSyncConfig.PairConfig pair : pairs.keySet()) {
|
||||
futures.put(pair, resyncPair(pair, player, userId));
|
||||
}
|
||||
|
||||
logSummary(player, cause, futures);
|
||||
}
|
||||
|
||||
private void resyncPair(GroupSyncConfig.PairConfig pair, GroupSyncCause cause) {
|
||||
for (IPlayer player : discordSRV.playerProvider().allPlayers()) {
|
||||
UUID uuid = player.uniqueId();
|
||||
Long userId = getLinkedAccount(uuid);
|
||||
if (userId == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
resyncPair(pair, uuid, userId)
|
||||
.whenComplete((result, t) -> discordSRV.logger().debug(
|
||||
new SynchronizationSummary(uuid, cause, pair, result).toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private CompletableFuture<GroupSyncResult> resyncPair(GroupSyncConfig.PairConfig pair, UUID player, long userId) {
|
||||
DiscordRole role = discordSRV.discordAPI().getRoleById(pair.roleId).orElse(null);
|
||||
if (role == null) {
|
||||
return CompletableFuture.completedFuture(GroupSyncResult.ROLE_DOESNT_EXIST);
|
||||
}
|
||||
|
||||
DiscordGuildMember member = role.getGuild().getMemberById(userId).orElse(null);
|
||||
if (member == null) {
|
||||
return CompletableFuture.completedFuture(GroupSyncResult.NOT_A_GUILD_MEMBER);
|
||||
}
|
||||
|
||||
boolean hasRole = member.hasRole(role);
|
||||
String groupName = pair.groupName;
|
||||
CompletableFuture<GroupSyncResult> resultFuture = new CompletableFuture<>();
|
||||
|
||||
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) {
|
||||
resultFuture.complete(hasRole ? GroupSyncResult.BOTH_TRUE : GroupSyncResult.BOTH_FALSE);
|
||||
// We're all good
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@Subscribe
|
||||
public void onDiscordMemberRoleAdd(DiscordMemberRoleAddEvent event) {
|
||||
event.getRoles().forEach(role -> roleChanged(event.getMember().getId(), role.getId(), false));
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDiscordMemberRoleRemove(DiscordMemberRoleRemoveEvent event) {
|
||||
event.getRoles().forEach(role -> roleChanged(event.getMember().getId(), role.getId(), true));
|
||||
}
|
||||
|
||||
public void groupAdded(UUID player, String groupName, @Nullable Set<String> serverContext, GroupSyncCause cause) {
|
||||
groupChanged(player, groupName, serverContext, cause, false);
|
||||
}
|
||||
|
||||
public void groupRemoved(UUID player, String groupName, @Nullable Set<String> serverContext, GroupSyncCause cause) {
|
||||
groupChanged(player, groupName, serverContext, cause, true);
|
||||
}
|
||||
|
||||
// Internal handling of changes
|
||||
|
||||
private <T, R> boolean checkExpectation(Cache<T, Map<R, Boolean>> expectations, T key, R mapKey, boolean remove) {
|
||||
// Check if we were expecting the change (when we add/remove something due to synchronization),
|
||||
// if we did expect the change, we won't trigger a synchronization since we just synchronized what was needed
|
||||
Map<R, Boolean> expected = expectations.getIfPresent(key);
|
||||
if (expected != null && Objects.equals(expected.get(mapKey), remove)) {
|
||||
expected.remove(mapKey);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void roleChanged(long userId, long roleId, boolean remove) {
|
||||
if (checkExpectation(expectedDiscordChanges, userId, roleId, remove)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<GroupSyncConfig.PairConfig> pairs = rolesToPairs.get(roleId);
|
||||
if (pairs == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PermissionDataProvider.Groups permissionProvider = getPermissionProvider();
|
||||
if (permissionProvider == null) {
|
||||
discordSRV.logger().warning("No supported permission plugin available to perform group sync");
|
||||
return;
|
||||
}
|
||||
|
||||
UUID player = getLinkedAccount(userId);
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<GroupSyncConfig.PairConfig, CompletableFuture<GroupSyncResult>> futures = new LinkedHashMap<>();
|
||||
for (GroupSyncConfig.PairConfig pair : pairs) {
|
||||
GroupSyncDirection direction = pair.direction();
|
||||
if (direction == GroupSyncDirection.MINECRAFT_TO_DISCORD) {
|
||||
// Not going Discord -> Minecraft
|
||||
futures.put(pair, CompletableFuture.completedFuture(GroupSyncResult.WRONG_DIRECTION));
|
||||
continue;
|
||||
}
|
||||
|
||||
futures.put(pair, modifyGroupState(player, pair, remove));
|
||||
|
||||
// If the sync is bidirectional, also add/remove any other roles that are linked to this group
|
||||
if (direction == GroupSyncDirection.DISCORD_TO_MINECRAFT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<GroupSyncConfig.PairConfig> groupPairs = groupsToPairs.get(pair.groupName);
|
||||
if (groupPairs == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (GroupSyncConfig.PairConfig groupPair : groupPairs) {
|
||||
if (groupPair.roleId == roleId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
futures.put(groupPair, modifyRoleState(userId, groupPair, remove));
|
||||
}
|
||||
}
|
||||
logSummary(player, GroupSyncCause.DISCORD_ROLE_CHANGE, futures);
|
||||
}
|
||||
|
||||
private void groupChanged(UUID player, String groupName, @Nullable Set<String> serverContext, GroupSyncCause cause, boolean remove) {
|
||||
if (cause.isDiscordSRVCanCause() && checkExpectation(expectedMinecraftChanges, player, groupName, remove)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<GroupSyncConfig.PairConfig> pairs = groupsToPairs.get(groupName);
|
||||
if (pairs == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Long userId = getLinkedAccount(player);
|
||||
if (userId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PermissionDataProvider.Groups permissionProvider = getPermissionProvider();
|
||||
Map<GroupSyncConfig.PairConfig, CompletableFuture<GroupSyncResult>> futures = new LinkedHashMap<>();
|
||||
for (GroupSyncConfig.PairConfig pair : pairs) {
|
||||
GroupSyncDirection direction = pair.direction();
|
||||
if (direction == GroupSyncDirection.DISCORD_TO_MINECRAFT) {
|
||||
// Not going Minecraft -> Discord
|
||||
futures.put(pair, CompletableFuture.completedFuture(GroupSyncResult.WRONG_DIRECTION));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if we're in the right context
|
||||
String context = pair.serverContext;
|
||||
if (permissionProvider instanceof PermissionDataProvider.GroupsContext) {
|
||||
if (StringUtils.isEmpty(context)) {
|
||||
// Use the default server context of the server
|
||||
Set<String> defaultValues = ((PermissionDataProvider.GroupsContext) permissionProvider)
|
||||
.getDefaultServerContext();
|
||||
if (!Objects.equals(serverContext, defaultValues)) {
|
||||
continue;
|
||||
}
|
||||
} else if (context.equals("global")) {
|
||||
// No server context
|
||||
if (serverContext != null && !serverContext.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Server context has to match the specified
|
||||
if (serverContext == null
|
||||
|| serverContext.size() != 1
|
||||
|| !serverContext.iterator().next().equals(context)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
futures.put(pair, modifyRoleState(userId, pair, remove));
|
||||
|
||||
// If the sync is bidirectional, also add/remove any other groups that are linked to this role
|
||||
if (direction == GroupSyncDirection.MINECRAFT_TO_DISCORD) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long roleId = pair.roleId;
|
||||
List<GroupSyncConfig.PairConfig> rolePairs = rolesToPairs.get(roleId);
|
||||
if (rolePairs == null || rolePairs.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (GroupSyncConfig.PairConfig rolePair : rolePairs) {
|
||||
if (rolePair.groupName.equals(groupName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
futures.put(rolePair, modifyGroupState(player, rolePair, remove));
|
||||
}
|
||||
}
|
||||
logSummary(player, cause, futures);
|
||||
}
|
||||
|
||||
private CompletableFuture<GroupSyncResult> modifyGroupState(UUID player, GroupSyncConfig.PairConfig config, boolean remove) {
|
||||
String groupName = config.groupName;
|
||||
|
||||
Map<String, Boolean> expected = expectedMinecraftChanges.get(player, key -> new ConcurrentHashMap<>());
|
||||
if (expected != null) {
|
||||
expected.put(groupName, remove);
|
||||
}
|
||||
|
||||
CompletableFuture<GroupSyncResult> future = new CompletableFuture<>();
|
||||
String serverContext = config.serverContext;
|
||||
hasGroup(player, groupName, serverContext).thenCompose(hasGroup -> {
|
||||
if (remove && hasGroup) {
|
||||
return removeGroup(player, groupName, serverContext).thenApply(v -> GroupSyncResult.REMOVE_GROUP);
|
||||
} else if (!remove && !hasGroup) {
|
||||
return addGroup(player, groupName, serverContext).thenApply(v -> GroupSyncResult.ADD_GROUP);
|
||||
} else {
|
||||
// Nothing to do
|
||||
return CompletableFuture.completedFuture(GroupSyncResult.ALREADY_IN_SYNC);
|
||||
}
|
||||
}).whenComplete((result, t) -> {
|
||||
if (t != null) {
|
||||
if (expected != null) {
|
||||
// Failed, remove expectation
|
||||
expected.remove(groupName);
|
||||
}
|
||||
|
||||
future.complete(GroupSyncResult.UPDATE_FAILED);
|
||||
discordSRV.logger().error("Failed to add group " + groupName + " to " + player, t);
|
||||
return;
|
||||
}
|
||||
|
||||
future.complete(result);
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
private CompletableFuture<GroupSyncResult> modifyRoleState(long userId, GroupSyncConfig.PairConfig config, boolean remove) {
|
||||
long roleId = config.roleId;
|
||||
DiscordRole role = discordSRV.discordAPI().getRoleById(roleId).orElse(null);
|
||||
if (role == null) {
|
||||
return CompletableFuture.completedFuture(GroupSyncResult.ROLE_DOESNT_EXIST);
|
||||
}
|
||||
|
||||
DiscordGuildMember member = role.getGuild().getMemberById(userId).orElse(null);
|
||||
if (member == null) {
|
||||
return CompletableFuture.completedFuture(GroupSyncResult.NOT_A_GUILD_MEMBER);
|
||||
}
|
||||
|
||||
Map<Long, Boolean> expected = expectedDiscordChanges.get(userId, key -> new ConcurrentHashMap<>());
|
||||
if (expected != 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) {
|
||||
// Nothing needed to be changed, remove expectation
|
||||
expected.remove(roleId);
|
||||
}
|
||||
return CompletableFuture.completedFuture(GroupSyncResult.ALREADY_IN_SYNC);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.groupsync;
|
||||
|
||||
import com.discordsrv.common.config.main.GroupSyncConfig;
|
||||
import com.discordsrv.common.groupsync.enums.GroupSyncCause;
|
||||
import com.discordsrv.common.groupsync.enums.GroupSyncResult;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class SynchronizationSummary {
|
||||
|
||||
private final EnumMap<GroupSyncResult, Set<GroupSyncConfig.PairConfig>> pairs = new EnumMap<>(GroupSyncResult.class);
|
||||
private final UUID player;
|
||||
private final GroupSyncCause cause;
|
||||
|
||||
public SynchronizationSummary(UUID player, GroupSyncCause cause, GroupSyncConfig.PairConfig config, GroupSyncResult result) {
|
||||
this(player, cause);
|
||||
add(config, result);
|
||||
}
|
||||
|
||||
public SynchronizationSummary(UUID player, GroupSyncCause cause) {
|
||||
this.player = player;
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
public void add(GroupSyncConfig.PairConfig config, GroupSyncResult result) {
|
||||
pairs.computeIfAbsent(result, key -> new LinkedHashSet<>()).add(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
int count = pairs.size();
|
||||
StringBuilder message = new StringBuilder(
|
||||
"Group synchronization (of " + count + " pairs) for " + player + " (" + cause + ")");
|
||||
|
||||
for (Map.Entry<GroupSyncResult, Set<GroupSyncConfig.PairConfig>> entry : pairs.entrySet()) {
|
||||
message.append(count == 1 ? ": " : "\n")
|
||||
.append(entry.getKey().toString())
|
||||
.append(": ")
|
||||
.append(entry.getValue().toString());
|
||||
}
|
||||
return message.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.groupsync.enums;
|
||||
|
||||
public enum GroupSyncCause {
|
||||
|
||||
API("API"),
|
||||
COMMAND("Command"),
|
||||
GAME_JOIN("Joined game"),
|
||||
LINK("Linked account"),
|
||||
TIMER("Timed synchronization"),
|
||||
DISCORD_ROLE_CHANGE("Discord role changed", true),
|
||||
LUCKPERMS_NODE_CHANGE("LuckPerms node changed", true),
|
||||
LUCKPERMS_TRACK("LuckPerms track promotion/demotion"),
|
||||
;
|
||||
|
||||
private final String prettyCause;
|
||||
private final boolean discordSRVCanCause;
|
||||
|
||||
GroupSyncCause(String prettyCause) {
|
||||
this(prettyCause, false);
|
||||
}
|
||||
|
||||
GroupSyncCause(String prettyCause, boolean discordSRVCanCause) {
|
||||
this.prettyCause = prettyCause;
|
||||
this.discordSRVCanCause = discordSRVCanCause;
|
||||
}
|
||||
|
||||
public boolean isDiscordSRVCanCause() {
|
||||
return discordSRVCanCause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return prettyCause;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.groupsync.enums;
|
||||
|
||||
public enum GroupSyncDirection {
|
||||
|
||||
MINECRAFT_TO_DISCORD,
|
||||
DISCORD_TO_MINECRAFT,
|
||||
BIDIRECTIONAL
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.groupsync.enums;
|
||||
|
||||
public enum GroupSyncResult {
|
||||
|
||||
// Something happened
|
||||
ADD_GROUP("Success (group add)"),
|
||||
REMOVE_GROUP("Success (group remove)"),
|
||||
ADD_ROLE("Success (role add)"),
|
||||
REMOVE_ROLE("Success (role remove)"),
|
||||
|
||||
// Nothing done
|
||||
ALREADY_IN_SYNC("Already in sync"),
|
||||
WRONG_DIRECTION("Wrong direction"),
|
||||
BOTH_TRUE("Both sides true"),
|
||||
BOTH_FALSE("Both sides false"),
|
||||
|
||||
// Errors
|
||||
ROLE_DOESNT_EXIST("Role doesn't exist"),
|
||||
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"),
|
||||
UPDATE_FAILED("Failed to modify role/group, error printed"),
|
||||
|
||||
;
|
||||
|
||||
final String prettyResult;
|
||||
|
||||
GroupSyncResult(String prettyResult) {
|
||||
this.prettyResult = prettyResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return prettyResult;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.groupsync.enums;
|
||||
|
||||
public enum GroupSyncSide {
|
||||
|
||||
MINECRAFT,
|
||||
DISCORD
|
||||
|
||||
}
|
@ -19,25 +19,44 @@
|
||||
package com.discordsrv.common.integration;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.exception.MessageException;
|
||||
import com.discordsrv.common.future.util.CompletableFutureUtil;
|
||||
import com.discordsrv.common.groupsync.GroupSyncModule;
|
||||
import com.discordsrv.common.groupsync.enums.GroupSyncCause;
|
||||
import com.discordsrv.common.module.type.PermissionDataProvider;
|
||||
import com.discordsrv.common.module.type.PluginIntegration;
|
||||
import net.luckperms.api.LuckPerms;
|
||||
import net.luckperms.api.LuckPermsProvider;
|
||||
import net.luckperms.api.context.ContextSet;
|
||||
import net.luckperms.api.context.DefaultContextKeys;
|
||||
import net.luckperms.api.context.ImmutableContextSet;
|
||||
import net.luckperms.api.context.MutableContextSet;
|
||||
import net.luckperms.api.event.EventSubscription;
|
||||
import net.luckperms.api.event.LuckPermsEvent;
|
||||
import net.luckperms.api.event.node.NodeAddEvent;
|
||||
import net.luckperms.api.event.node.NodeClearEvent;
|
||||
import net.luckperms.api.event.node.NodeRemoveEvent;
|
||||
import net.luckperms.api.event.user.track.UserTrackEvent;
|
||||
import net.luckperms.api.model.PermissionHolder;
|
||||
import net.luckperms.api.model.data.DataMutateResult;
|
||||
import net.luckperms.api.model.data.NodeMap;
|
||||
import net.luckperms.api.model.group.Group;
|
||||
import net.luckperms.api.model.user.User;
|
||||
import net.luckperms.api.node.Node;
|
||||
import net.luckperms.api.node.NodeType;
|
||||
import net.luckperms.api.node.types.InheritanceNode;
|
||||
import net.luckperms.api.query.QueryMode;
|
||||
import net.luckperms.api.query.QueryOptions;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class LuckPermsIntegration extends PluginIntegration<DiscordSRV> implements PermissionDataProvider.All {
|
||||
|
||||
private LuckPerms luckPerms;
|
||||
private final List<EventSubscription<?>> subscriptions = new ArrayList<>();
|
||||
|
||||
public LuckPermsIntegration(DiscordSRV discordSRV) {
|
||||
super(discordSRV);
|
||||
@ -57,10 +76,20 @@ public class LuckPermsIntegration extends PluginIntegration<DiscordSRV> implemen
|
||||
@Override
|
||||
public void enable() {
|
||||
luckPerms = LuckPermsProvider.get();
|
||||
subscribe(NodeAddEvent.class, this::onNodeAdd);
|
||||
subscribe(NodeRemoveEvent.class, this::onNodeRemove);
|
||||
subscribe(NodeClearEvent.class, this::onNodeClear);
|
||||
subscribe(UserTrackEvent.class, this::onUserTrack);
|
||||
}
|
||||
|
||||
private <E extends LuckPermsEvent> void subscribe(Class<E> clazz, Consumer<E> method) {
|
||||
subscriptions.add(luckPerms.getEventBus().subscribe(clazz, method));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
subscriptions.forEach(EventSubscription::close);
|
||||
subscriptions.clear();
|
||||
luckPerms = null;
|
||||
}
|
||||
|
||||
@ -74,41 +103,72 @@ public class LuckPermsIntegration extends PluginIntegration<DiscordSRV> implemen
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> hasGroup(UUID player, String groupName) {
|
||||
public Set<String> getDefaultServerContext() {
|
||||
return luckPerms.getContextManager().getStaticContext().getValues(DefaultContextKeys.SERVER_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> hasGroup(UUID player, String groupName, boolean includeInherited, String serverContext) {
|
||||
return user(player).thenApply(user -> {
|
||||
for (Group inheritedGroup : user.getInheritedGroups(QueryOptions.defaultContextualOptions())) {
|
||||
if (inheritedGroup.getName().equalsIgnoreCase(groupName)) {
|
||||
return true;
|
||||
MutableContextSet context = luckPerms.getContextManager().getStaticContext().mutableCopy();
|
||||
if (serverContext != null) {
|
||||
context.removeAll(DefaultContextKeys.SERVER_KEY);
|
||||
if (!serverContext.equals("global")) {
|
||||
context.add(DefaultContextKeys.SERVER_KEY, serverContext);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
return (
|
||||
includeInherited
|
||||
? user.getInheritedGroups(QueryOptions.builder(QueryMode.CONTEXTUAL).context(context).build())
|
||||
.stream()
|
||||
.map(Group::getName)
|
||||
: user.getNodes(NodeType.INHERITANCE)
|
||||
.stream()
|
||||
.filter(node -> node.getContexts().isSatisfiedBy(context))
|
||||
.map(InheritanceNode::getGroupName)
|
||||
).anyMatch(name -> name.equalsIgnoreCase(groupName));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> addGroup(UUID player, String groupName) {
|
||||
return groupMutate(player, groupName, NodeMap::add);
|
||||
public CompletableFuture<Void> addGroup(UUID player, String groupName, String serverContext) {
|
||||
return groupMutate(player, groupName, serverContext, NodeMap::add);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> removeGroup(UUID player, String groupName) {
|
||||
return groupMutate(player, groupName, NodeMap::remove);
|
||||
public CompletableFuture<Void> removeGroup(UUID player, String groupName, String serverContext) {
|
||||
return groupMutate(player, groupName, serverContext, NodeMap::remove);
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> groupMutate(UUID player, String groupName, BiFunction<NodeMap, Node, DataMutateResult> function) {
|
||||
private CompletableFuture<Void> groupMutate(UUID player, String groupName, String serverContext, BiFunction<NodeMap, Node, DataMutateResult> function) {
|
||||
Group group = luckPerms.getGroupManager().getGroup(groupName);
|
||||
if (group == null) {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
future.completeExceptionally(new RuntimeException("Group does not exist"));
|
||||
return future;
|
||||
return CompletableFutureUtil.failed(new MessageException("Group does not exist"));
|
||||
}
|
||||
|
||||
return user(player).thenApply(user -> {
|
||||
DataMutateResult result = function.apply(user.data(), InheritanceNode.builder(group).build());
|
||||
if (result != DataMutateResult.SUCCESS) {
|
||||
throw new RuntimeException(result.name());
|
||||
return user(player).thenCompose(user -> {
|
||||
ContextSet contexts;
|
||||
if (serverContext != null) {
|
||||
if (!serverContext.equals("global")) {
|
||||
contexts = ImmutableContextSet.of(DefaultContextKeys.SERVER_KEY, serverContext);
|
||||
} else {
|
||||
contexts = ImmutableContextSet.empty();
|
||||
}
|
||||
} else {
|
||||
MutableContextSet contextSet = MutableContextSet.create();
|
||||
for (String value : getDefaultServerContext()) {
|
||||
contextSet.add(DefaultContextKeys.SERVER_KEY, value);
|
||||
}
|
||||
contexts = contextSet;
|
||||
}
|
||||
return null;
|
||||
|
||||
InheritanceNode node = InheritanceNode.builder(group).context(contexts).build();
|
||||
DataMutateResult result = function.apply(user.data(), node);
|
||||
if (result != DataMutateResult.SUCCESS) {
|
||||
return CompletableFutureUtil.failed(new MessageException(result.name()));
|
||||
}
|
||||
|
||||
return luckPerms.getUserManager().saveUser(user);
|
||||
});
|
||||
}
|
||||
|
||||
@ -132,4 +192,53 @@ public class LuckPermsIntegration extends PluginIntegration<DiscordSRV> implemen
|
||||
public CompletableFuture<String> getMeta(UUID player, String key) throws UnsupportedOperationException {
|
||||
return user(player).thenApply(user -> user.getCachedData().getMetaData().getMetaValue(key));
|
||||
}
|
||||
|
||||
private void onNodeAdd(NodeAddEvent event) {
|
||||
nodeUpdate(event.getTarget(), event.getNode(), false);
|
||||
}
|
||||
|
||||
private void onNodeRemove(NodeRemoveEvent event) {
|
||||
nodeUpdate(event.getTarget(), event.getNode(), true);
|
||||
}
|
||||
|
||||
private void onNodeClear(NodeClearEvent event) {
|
||||
PermissionHolder target = event.getTarget();
|
||||
for (Node node : event.getNodes()) {
|
||||
nodeUpdate(target, node, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void onUserTrack(UserTrackEvent event) {
|
||||
User user = event.getUser();
|
||||
event.getGroupFrom().ifPresent(group -> groupUpdate(user, group, Collections.emptySet(), true, true));
|
||||
event.getGroupTo().ifPresent(group -> groupUpdate(user, group, Collections.emptySet(), false, true));
|
||||
}
|
||||
|
||||
private void nodeUpdate(PermissionHolder holder, Node node, boolean remove) {
|
||||
if (!(holder instanceof User) || node.getType() != NodeType.INHERITANCE) {
|
||||
return;
|
||||
}
|
||||
|
||||
InheritanceNode inheritanceNode = NodeType.INHERITANCE.cast(node);
|
||||
String groupName = inheritanceNode.getGroupName();
|
||||
Set<String> serverContext = inheritanceNode.getContexts().getValues(DefaultContextKeys.SERVER_KEY);
|
||||
|
||||
groupUpdate((User) holder, groupName, serverContext, remove, false);
|
||||
}
|
||||
|
||||
private void groupUpdate(User user, String groupName, Set<String> serverContext, boolean remove, boolean track) {
|
||||
GroupSyncModule module = discordSRV.getModule(GroupSyncModule.class);
|
||||
if (module == null || !module.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
GroupSyncCause cause = track ? GroupSyncCause.LUCKPERMS_TRACK : GroupSyncCause.LUCKPERMS_NODE_CHANGE;
|
||||
UUID uuid = user.getUniqueId();
|
||||
if (remove) {
|
||||
module.groupRemoved(uuid, groupName, serverContext, cause);
|
||||
} else {
|
||||
module.groupAdded(uuid, groupName, serverContext, cause);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ import com.discordsrv.api.discord.api.entity.DiscordUser;
|
||||
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel;
|
||||
import com.discordsrv.api.discord.api.entity.guild.DiscordGuildMember;
|
||||
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessage;
|
||||
import com.discordsrv.api.discord.events.DiscordMessageReceiveEvent;
|
||||
import com.discordsrv.api.discord.events.message.DiscordMessageReceiveEvent;
|
||||
import com.discordsrv.api.event.bus.Subscribe;
|
||||
import com.discordsrv.api.event.events.message.receive.discord.DiscordChatMessageProcessingEvent;
|
||||
import com.discordsrv.api.placeholder.util.Placeholders;
|
||||
|
@ -27,8 +27,8 @@ import com.discordsrv.api.discord.api.entity.guild.DiscordGuildMember;
|
||||
import com.discordsrv.api.discord.api.entity.message.DiscordMessageEmbed;
|
||||
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessage;
|
||||
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
|
||||
import com.discordsrv.api.discord.events.DiscordMessageDeleteEvent;
|
||||
import com.discordsrv.api.discord.events.DiscordMessageUpdateEvent;
|
||||
import com.discordsrv.api.discord.events.message.DiscordMessageDeleteEvent;
|
||||
import com.discordsrv.api.discord.events.message.DiscordMessageUpdateEvent;
|
||||
import com.discordsrv.api.event.bus.Subscribe;
|
||||
import com.discordsrv.api.event.events.message.receive.discord.DiscordChatMessageProcessingEvent;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
|
@ -24,7 +24,7 @@ import com.discordsrv.api.event.events.lifecycle.DiscordSRVReloadEvent;
|
||||
import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.module.type.AbstractModule;
|
||||
import com.discordsrv.common.module.type.Module;
|
||||
import com.discordsrv.api.module.type.Module;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -20,6 +20,7 @@ package com.discordsrv.common.module.type;
|
||||
|
||||
import com.discordsrv.api.event.events.Cancellable;
|
||||
import com.discordsrv.api.event.events.Processable;
|
||||
import com.discordsrv.api.module.type.Module;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.event.util.EventUtil;
|
||||
|
||||
|
@ -18,6 +18,10 @@
|
||||
|
||||
package com.discordsrv.common.module.type;
|
||||
|
||||
import com.discordsrv.api.module.type.Module;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@ -25,10 +29,11 @@ public interface PermissionDataProvider extends Module {
|
||||
|
||||
boolean supportsOffline();
|
||||
|
||||
interface All extends Groups, Permissions, PrefixAndSuffix, Meta {}
|
||||
interface Basic extends Groups, Permissions, PrefixAndSuffix {}
|
||||
interface All extends Basic, Meta, GroupsContext {}
|
||||
|
||||
interface Groups extends PermissionDataProvider {
|
||||
CompletableFuture<Boolean> hasGroup(UUID player, String groupName);
|
||||
CompletableFuture<Boolean> hasGroup(UUID player, String groupName, boolean includeInherited);
|
||||
CompletableFuture<Void> addGroup(UUID player, String groupName);
|
||||
CompletableFuture<Void> removeGroup(UUID player, String groupName);
|
||||
}
|
||||
@ -46,4 +51,27 @@ public interface PermissionDataProvider extends Module {
|
||||
CompletableFuture<String> getMeta(UUID player, String key);
|
||||
}
|
||||
|
||||
interface GroupsContext extends Groups {
|
||||
|
||||
Set<String> getDefaultServerContext();
|
||||
CompletableFuture<Boolean> hasGroup(UUID player, String groupName, boolean includeInherited, @Nullable String serverContext);
|
||||
CompletableFuture<Void> addGroup(UUID player, String groupName, @Nullable String serverContext);
|
||||
CompletableFuture<Void> removeGroup(UUID player, String groupName, @Nullable String serverContext);
|
||||
|
||||
@Override
|
||||
default CompletableFuture<Boolean> hasGroup(UUID player, String groupName, boolean includeInherited) {
|
||||
return hasGroup(player, groupName, includeInherited, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
default CompletableFuture<Void> addGroup(UUID player, String groupName) {
|
||||
return addGroup(player, groupName, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
default CompletableFuture<Void> removeGroup(UUID player, String groupName) {
|
||||
return removeGroup(player, groupName, null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user