mirror of
https://github.com/DiscordSRV/Ascension.git
synced 2024-12-27 17:28:23 +01:00
Linking improvements, required linking initial
This commit is contained in:
parent
6ff5ddb6a7
commit
42ec392bcb
@ -34,6 +34,8 @@ public class Color {
|
||||
* Discord's blurple color (<a href="https://discord.com/branding">Discord branding</a>).
|
||||
*/
|
||||
public static final Color BLURPLE = new Color(0x5865F2);
|
||||
public static final Color WHITE = new Color(0xFFFFFF);
|
||||
public static final Color BLACK = new Color(0);
|
||||
|
||||
private final int rgb;
|
||||
|
||||
|
@ -32,6 +32,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* A Discord server.
|
||||
@ -53,6 +54,14 @@ public interface DiscordGuild extends JDAEntity<Guild>, Snowflake {
|
||||
@Placeholder("server_member_count")
|
||||
int getMemberCount();
|
||||
|
||||
/**
|
||||
* Retrieves a Discord guild member from Discord by id.
|
||||
* @param id the id for the Discord guild member
|
||||
* @return a future for the Discord guild member
|
||||
*/
|
||||
@NotNull
|
||||
CompletableFuture<DiscordGuildMember> retrieveMemberById(long id);
|
||||
|
||||
/**
|
||||
* Gets a Discord guild member by id from the cache, the provided entity can be cached and will not update if it changes on Discord.
|
||||
* @param id the id for the Discord guild member
|
||||
|
@ -32,6 +32,7 @@ import net.dv8tion.jda.api.entities.Member;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@ -115,4 +116,27 @@ public interface DiscordGuildMember extends JDAEntity<Member>, Mentionable {
|
||||
@Placeholder("user_color")
|
||||
Color getColor();
|
||||
|
||||
/**
|
||||
* Gets the time the member joined the server.
|
||||
* @return the time the member joined the server
|
||||
*/
|
||||
@NotNull
|
||||
OffsetDateTime getTimeJoined();
|
||||
|
||||
/**
|
||||
* Time the member started boosting.
|
||||
* @return the time the member started boosting or {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
OffsetDateTime getTimeBoosted();
|
||||
|
||||
/**
|
||||
* If the Discord server member is boosted.
|
||||
* @return {@code true} if this Discord server member is boosting
|
||||
*/
|
||||
@Placeholder("user_isboosting")
|
||||
default boolean isBoosting() {
|
||||
return getTimeBoosted() != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import com.discordsrv.bukkit.listener.BukkitDeathListener;
|
||||
import com.discordsrv.bukkit.listener.BukkitStatusMessageListener;
|
||||
import com.discordsrv.bukkit.player.BukkitPlayerProvider;
|
||||
import com.discordsrv.bukkit.plugin.BukkitPluginManager;
|
||||
import com.discordsrv.bukkit.requiredlinking.BukkitRequiredLinkingModule;
|
||||
import com.discordsrv.bukkit.scheduler.BukkitScheduler;
|
||||
import com.discordsrv.common.ServerDiscordSRV;
|
||||
import com.discordsrv.common.command.game.handler.ICommandHandler;
|
||||
@ -222,6 +223,7 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<DiscordSRVBukkitBootstrap
|
||||
|
||||
// Modules
|
||||
registerModule(MinecraftToDiscordChatModule::new);
|
||||
registerModule(BukkitRequiredLinkingModule::new);
|
||||
|
||||
// Integrations
|
||||
registerIntegration("com.discordsrv.bukkit.integration.VaultIntegration");
|
||||
|
@ -35,6 +35,8 @@ public class BukkitConfig extends MainConfig {
|
||||
channels.put(ChannelConfig.DEFAULT_KEY, new ServerBaseChannelConfig());
|
||||
}
|
||||
|
||||
public BukkitRequiredLinkingConfig requiredLinking = new BukkitRequiredLinkingConfig();
|
||||
|
||||
public PluginIntegrationConfig integrations = new PluginIntegrationConfig();
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.bukkit.config.main;
|
||||
|
||||
import com.discordsrv.common.config.main.linking.ServerRequiredLinkingConfig;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
public class BukkitRequiredLinkingConfig extends ServerRequiredLinkingConfig {
|
||||
|
||||
public KickOptions kick = new KickOptions();
|
||||
|
||||
@ConfigSerializable
|
||||
public static class KickOptions {
|
||||
|
||||
@Comment("The event to use for kick.\n"
|
||||
+ "Available events: AsyncPlayerPreLoginEvent (preferred), PlayerLoginEvent")
|
||||
public String event = "AsyncPlayerPreLoginEvent";
|
||||
|
||||
@Comment("The event priority to use for the kick")
|
||||
public String priority = EventPriority.NORMAL.name();
|
||||
|
||||
}
|
||||
}
|
@ -1,3 +1,21 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.bukkit.integration.chat;
|
||||
|
||||
import com.discordsrv.api.channel.GameChannel;
|
||||
|
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.bukkit.requiredlinking;
|
||||
|
||||
import com.discordsrv.bukkit.BukkitDiscordSRV;
|
||||
import com.discordsrv.bukkit.config.main.BukkitRequiredLinkingConfig;
|
||||
import com.discordsrv.common.config.main.linking.RequirementsConfig;
|
||||
import com.discordsrv.common.linking.requirelinking.ServerRequireLinkingModule;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitComponentSerializer;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
|
||||
import org.bukkit.event.player.PlayerLoginEvent;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
// TODO: implement freeze
|
||||
public class BukkitRequiredLinkingModule extends ServerRequireLinkingModule<BukkitDiscordSRV> implements Listener {
|
||||
|
||||
public BukkitRequiredLinkingModule(BukkitDiscordSRV discordSRV) {
|
||||
super(discordSRV);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequirementsConfig config() {
|
||||
return discordSRV.config().requiredLinking.requirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
super.enable();
|
||||
|
||||
register(PlayerLoginEvent.class, this::handle);
|
||||
register(AsyncPlayerPreLoginEvent.class, this::handle);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends Event> void register(Class<T> eventType, BiConsumer<T, EventPriority> eventConsumer) {
|
||||
for (EventPriority priority : EventPriority.values()) {
|
||||
if (priority == EventPriority.MONITOR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
discordSRV.server().getPluginManager().registerEvent(
|
||||
eventType,
|
||||
this,
|
||||
priority,
|
||||
(listener, event) -> eventConsumer.accept((T) event, priority),
|
||||
discordSRV.plugin(),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Component is relocated so using it here is inconvenient
|
||||
private void handle(AsyncPlayerPreLoginEvent event, EventPriority priority) {
|
||||
handle(
|
||||
"AsyncPlayerPreLoginEvent",
|
||||
priority,
|
||||
event.getUniqueId(),
|
||||
() -> event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED ? event.getLoginResult().name() : null,
|
||||
text -> event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, text)
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Component is relocated so using it here is inconvenient
|
||||
private void handle(PlayerLoginEvent event, EventPriority priority) {
|
||||
handle(
|
||||
"PlayerLoginEvent",
|
||||
priority,
|
||||
event.getPlayer().getUniqueId(),
|
||||
() -> event.getResult() != PlayerLoginEvent.Result.ALLOWED ? event.getResult().name() : null,
|
||||
text -> event.disallow(PlayerLoginEvent.Result.KICK_OTHER, text)
|
||||
);
|
||||
}
|
||||
|
||||
private void handle(
|
||||
String eventType,
|
||||
EventPriority priority,
|
||||
UUID playerUUID,
|
||||
Supplier<String> alreadyBlocked,
|
||||
Consumer<String> disallow
|
||||
) {
|
||||
BukkitRequiredLinkingConfig config = discordSRV.config().requiredLinking;
|
||||
if (!config.enabled || !config.action.equalsIgnoreCase("KICK")
|
||||
|| !eventType.equals(config.kick.event) || !priority.name().equals(config.kick.priority)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String blockType = alreadyBlocked.get();
|
||||
if (blockType != null) {
|
||||
discordSRV.logger().debug(playerUUID + " is already blocked for " + eventType + "/" + priority + " (" + blockType + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
Component kickReason = getKickReason(playerUUID).join();
|
||||
if (kickReason != null) {
|
||||
disallow.accept(BukkitComponentSerializer.legacy().serialize(kickReason));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
super.disable();
|
||||
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
}
|
@ -33,7 +33,7 @@ import com.discordsrv.common.command.game.GameCommandModule;
|
||||
import com.discordsrv.common.component.ComponentFactory;
|
||||
import com.discordsrv.common.config.connection.ConnectionConfig;
|
||||
import com.discordsrv.common.config.connection.UpdateConfig;
|
||||
import com.discordsrv.common.config.main.LinkedAccountConfig;
|
||||
import com.discordsrv.common.config.main.linking.LinkedAccountConfig;
|
||||
import com.discordsrv.common.config.main.MainConfig;
|
||||
import com.discordsrv.common.config.manager.ConnectionConfigManager;
|
||||
import com.discordsrv.common.config.manager.MainConfigManager;
|
||||
|
@ -1,3 +1,21 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.channel;
|
||||
|
||||
import com.discordsrv.api.channel.GameChannel;
|
||||
|
@ -35,7 +35,6 @@ import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
@ -75,7 +74,7 @@ public class ResyncCommand implements GameCommandExecutor {
|
||||
.whenComplete((results, t) -> {
|
||||
EnumMap<GroupSyncResult, AtomicInteger> resultCounts = new EnumMap<>(GroupSyncResult.class);
|
||||
int total = 0;
|
||||
for (Set<GroupSyncResult> result : results) {
|
||||
for (List<GroupSyncResult> result : results) {
|
||||
for (GroupSyncResult singleResult : result) {
|
||||
total++;
|
||||
resultCounts.computeIfAbsent(singleResult, key -> new AtomicInteger(0)).getAndIncrement();
|
||||
@ -103,8 +102,8 @@ public class ResyncCommand implements GameCommandExecutor {
|
||||
});
|
||||
}
|
||||
|
||||
private List<CompletableFuture<Set<GroupSyncResult>>> resyncOnlinePlayers(GroupSyncModule module) {
|
||||
List<CompletableFuture<Set<GroupSyncResult>>> futures = new ArrayList<>();
|
||||
private List<CompletableFuture<List<GroupSyncResult>>> resyncOnlinePlayers(GroupSyncModule module) {
|
||||
List<CompletableFuture<List<GroupSyncResult>>> futures = new ArrayList<>();
|
||||
for (IPlayer player : discordSRV.playerProvider().allPlayers()) {
|
||||
futures.add(module.resync(player.uniqueId(), GroupSyncCause.COMMAND));
|
||||
}
|
||||
|
@ -31,5 +31,9 @@ import java.lang.annotation.Target;
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Order {
|
||||
|
||||
/**
|
||||
* Lowest to highest.
|
||||
* @return the order value of the option
|
||||
*/
|
||||
int value();
|
||||
}
|
||||
|
@ -26,7 +26,11 @@ public class MinecraftAuthConfig {
|
||||
|
||||
@Comment("If minecraftauth.me connections are allowed for Discord linking (requires linked-accounts.provider to be \"auto\" or \"minecraftauth\").\n"
|
||||
+ "Requires a connection to: minecraftauth.me\n"
|
||||
+ "Privacy Policy: https://minecraftauth.me/privacy.txt")
|
||||
+ "Privacy Policy: https://minecraftauth.me/privacy")
|
||||
public boolean allow = true;
|
||||
|
||||
@Comment("minecraftauth.me token for checking subscription, following and membership statuses for required linking\n"
|
||||
+ "You can get the token from https://minecraftauth.me/api/token whilst logged in (please keep in mind that the token resets every time you visit that page)")
|
||||
public String token = "";
|
||||
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ public class UpdateConfig {
|
||||
|
||||
@Setting(value = "force")
|
||||
@Comment("If the security check needs to be completed for DiscordSRV to enable,\n"
|
||||
+ "if the security check fails, DiscordSRV will be disabled if this option is set to true")
|
||||
+ "if the security check cannot be performed, DiscordSRV will be disabled if this option is set to true")
|
||||
public boolean force = false;
|
||||
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import com.discordsrv.common.config.Config;
|
||||
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 com.discordsrv.common.config.main.linking.LinkedAccountConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
|
@ -1,3 +1,21 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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 org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.config.main;
|
||||
package com.discordsrv.common.config.main.linking;
|
||||
|
||||
import com.discordsrv.common.config.connection.ConnectionConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.linking;
|
||||
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ConfigSerializable
|
||||
public class ProxyRequiredLinkingConfig extends RequiredLinkingConfig {
|
||||
|
||||
public TargetRequirementConfig proxyRequirements = new TargetRequirementConfig();
|
||||
|
||||
public Map<String, TargetRequirementConfig> serverRequirements = new HashMap<String, TargetRequirementConfig>() {{
|
||||
put("example", new TargetRequirementConfig());
|
||||
}};
|
||||
|
||||
@ConfigSerializable
|
||||
public static class TargetRequirementConfig extends RequirementsConfig {}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.linking;
|
||||
|
||||
import com.discordsrv.common.config.annotation.Order;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
@ConfigSerializable
|
||||
public abstract class RequiredLinkingConfig {
|
||||
|
||||
@Comment("If required linking is enabled")
|
||||
@Order(-10)
|
||||
public boolean enabled = false;
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.linking;
|
||||
|
||||
import com.discordsrv.common.config.annotation.DefaultOnly;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Setting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@ConfigSerializable
|
||||
public class RequirementsConfig {
|
||||
|
||||
@Setting("bypass-uuids")
|
||||
@Comment("A list of uuids that are allowed to bypass these requirements")
|
||||
@DefaultOnly
|
||||
public List<String> bypassUUIDs = new ArrayList<>(Collections.singletonList("6c983d46-0631-48b8-9baf-5e33eb5ffec4"));
|
||||
|
||||
@Comment("Requirements players must meet to be pass requirements\n"
|
||||
+ "Only one option has to pass, for example [\"TwitchSubscriber()\", \"DiscordRole(...)\"] allows twitch subscribers and users with the specified role to play\n"
|
||||
+ "while [\"TwitchSubscriber() && DiscordRole(...)\"] only allows twitch subscribers with the specified role to play\n"
|
||||
+ "\n"
|
||||
+ "Valid values are:\n"
|
||||
+ "DiscordServer(Server ID)\n"
|
||||
+ "DiscordBoosting(Server ID)\n"
|
||||
+ "DiscordRole(Role ID)\n"
|
||||
+ "The following are available if you're using MinecraftAuth.me for linked accounts:\n"
|
||||
+ "PatreonSubscriber() or PatreonSubscriber(Tier Title)\n"
|
||||
+ "GlimpseSubscriber() or GlimpseSubscriber(Level Name)\n"
|
||||
+ "TwitchFollower()\n"
|
||||
+ "TwitchSubscriber() or TwitchSubscriber(Tier)\n"
|
||||
+ "YouTubeSubscriber()\n"
|
||||
+ "YouTubeMember() or YouTubeMember(Tier)\n"
|
||||
+ "\n"
|
||||
+ "The following operators are available:\n"
|
||||
+ "&& = and, for example: \"DiscordServer(...) && TwitchFollower()\"\n"
|
||||
+ "|| = or, for example \"DiscordBoosting(...) || YouTubeMember()\n"
|
||||
+ "You can also use brackets () to clear ambiguity, for example: \"DiscordServer(...) && (TwitchSubscriber() || PatreonSubscriber())\"")
|
||||
public List<String> requirements = new ArrayList<>();
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.linking;
|
||||
|
||||
import com.discordsrv.common.config.annotation.Order;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Setting;
|
||||
|
||||
@ConfigSerializable
|
||||
public class ServerRequiredLinkingConfig extends RequiredLinkingConfig {
|
||||
|
||||
@Comment("How the player should be blocked from joining the server.\nAvailable options: KICK, FREEZE")
|
||||
public String action = "KICK";
|
||||
|
||||
@Setting(nodeFromParent = true)
|
||||
@Order(10)
|
||||
public RequirementsConfig requirements = new RequirementsConfig();
|
||||
|
||||
}
|
@ -29,6 +29,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class DiscordGuildImpl implements DiscordGuild {
|
||||
|
||||
@ -55,6 +56,14 @@ public class DiscordGuildImpl implements DiscordGuild {
|
||||
return guild.getMemberCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompletableFuture<DiscordGuildMember> retrieveMemberById(long id) {
|
||||
return discordSRV.discordAPI().mapExceptions(() -> guild.retrieveMemberById(id)
|
||||
.submit()
|
||||
.thenApply(member -> new DiscordGuildMemberImpl(discordSRV, member))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscordGuildMember getMemberById(long id) {
|
||||
Member member = guild.getMemberById(id);
|
||||
|
@ -34,6 +34,7 @@ import net.kyori.adventure.text.format.TextColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@ -110,6 +111,16 @@ public class DiscordGuildMemberImpl implements DiscordGuildMember {
|
||||
return color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OffsetDateTime getTimeJoined() {
|
||||
return member.getTimeJoined();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable OffsetDateTime getTimeBoosted() {
|
||||
return member.getTimeBoosted();
|
||||
}
|
||||
|
||||
//
|
||||
// Placeholders
|
||||
//
|
||||
|
@ -24,17 +24,15 @@ import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
import com.discordsrv.common.future.util.CompletableFutureUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class ReceivedDiscordMessageClusterImpl implements ReceivedDiscordMessageCluster {
|
||||
|
||||
private final Set<ReceivedDiscordMessage> messages;
|
||||
|
||||
public ReceivedDiscordMessageClusterImpl(Set<ReceivedDiscordMessage> messages) {
|
||||
this.messages = messages;
|
||||
public ReceivedDiscordMessageClusterImpl(Collection<ReceivedDiscordMessage> messages) {
|
||||
this.messages = new HashSet<>(messages);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -18,9 +18,9 @@
|
||||
|
||||
package com.discordsrv.common.future.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public final class CompletableFutureUtil {
|
||||
@ -37,19 +37,20 @@ public final class CompletableFutureUtil {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> CompletableFuture<Set<T>> combine(Collection<CompletableFuture<T>> futures) {
|
||||
public static <T> CompletableFuture<List<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<>();
|
||||
@SafeVarargs
|
||||
public static <T> CompletableFuture<List<T>> combine(CompletableFuture<T>... futures) {
|
||||
CompletableFuture<List<T>> future = new CompletableFuture<>();
|
||||
CompletableFuture.allOf(futures).whenComplete((v, t) -> {
|
||||
if (t != null) {
|
||||
future.completeExceptionally(t);
|
||||
return;
|
||||
}
|
||||
|
||||
Set<T> results = new HashSet<>();
|
||||
List<T> results = new ArrayList<>();
|
||||
for (CompletableFuture<T> aFuture : futures) {
|
||||
results.add(aFuture.join());
|
||||
}
|
||||
|
@ -255,20 +255,20 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
|
||||
|
||||
// Resync user
|
||||
|
||||
public CompletableFuture<Set<GroupSyncResult>> resync(UUID player, GroupSyncCause cause) {
|
||||
public CompletableFuture<List<GroupSyncResult>> resync(UUID player, GroupSyncCause cause) {
|
||||
return lookupLinkedAccount(player).thenCompose(userId -> {
|
||||
if (userId == null) {
|
||||
return CompletableFuture.completedFuture(Collections.emptySet());
|
||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||
}
|
||||
|
||||
return CompletableFutureUtil.combine(resync(player, userId, cause));
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<Set<GroupSyncResult>> resync(long userId, GroupSyncCause cause) {
|
||||
public CompletableFuture<List<GroupSyncResult>> resync(long userId, GroupSyncCause cause) {
|
||||
return lookupLinkedAccount(userId).thenCompose(player -> {
|
||||
if (player == null) {
|
||||
return CompletableFuture.completedFuture(Collections.emptySet());
|
||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||
}
|
||||
|
||||
return CompletableFutureUtil.combine(resync(player, userId, cause));
|
||||
|
@ -26,6 +26,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
public interface LinkStore extends LinkProvider {
|
||||
|
||||
CompletableFuture<Void> createLink(@NotNull UUID playerUUID, long userId);
|
||||
CompletableFuture<Void> removeLink(@NotNull UUID playerUUID, long userId);
|
||||
|
||||
CompletableFuture<Integer> getLinkedAccountCount();
|
||||
}
|
||||
|
@ -48,6 +48,12 @@ public class MemoryLinker implements LinkProvider, LinkStore {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> removeLink(@NotNull UUID playerUUID, long userId) {
|
||||
map.remove(playerUUID);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Integer> getLinkedAccountCount() {
|
||||
return CompletableFuture.completedFuture(map.size());
|
||||
|
@ -19,19 +19,22 @@
|
||||
package com.discordsrv.common.linking.impl;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.function.CheckedSupplier;
|
||||
import com.discordsrv.common.future.util.CompletableFutureUtil;
|
||||
import com.discordsrv.common.linking.LinkProvider;
|
||||
import com.discordsrv.common.logging.Logger;
|
||||
import com.discordsrv.common.logging.NamedLogger;
|
||||
import me.minecraftauth.lib.AuthService;
|
||||
import me.minecraftauth.lib.account.AccountType;
|
||||
import me.minecraftauth.lib.account.Identity;
|
||||
import me.minecraftauth.lib.account.MinecraftAccount;
|
||||
import me.minecraftauth.lib.exception.LookupException;
|
||||
import me.minecraftauth.lib.account.platform.discord.DiscordAccount;
|
||||
import me.minecraftauth.lib.account.platform.minecraft.MinecraftAccount;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class MinecraftAuthenticationLinker extends CachedLinkProvider implements LinkProvider {
|
||||
|
||||
@ -44,35 +47,85 @@ public class MinecraftAuthenticationLinker extends CachedLinkProvider implements
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<Long>> queryUserId(@NotNull UUID playerUUID) {
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
try {
|
||||
return AuthService.lookup(AccountType.MINECRAFT, playerUUID.toString())
|
||||
.map(Identity::getDiscordAccount)
|
||||
.map(discord -> Long.parseUnsignedLong(discord.getUserId()));
|
||||
} catch (LookupException e) {
|
||||
logger.error("Lookup for uuid " + playerUUID + " failed", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
},
|
||||
discordSRV.scheduler().executor()
|
||||
);
|
||||
return query(
|
||||
() -> AuthService.lookup(AccountType.MINECRAFT, playerUUID.toString(), AccountType.DISCORD)
|
||||
.map(account -> (DiscordAccount) account)
|
||||
.map(discord -> Long.parseUnsignedLong(discord.getUserId())),
|
||||
() -> discordSRV.storage().getUserId(playerUUID),
|
||||
userId -> linked(playerUUID, userId),
|
||||
userId -> unlinked(playerUUID, userId)
|
||||
).exceptionally(t -> {
|
||||
logger.error("Lookup for uuid " + playerUUID + " failed", t);
|
||||
return Optional.empty();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<UUID>> queryPlayerUUID(long userId) {
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
try {
|
||||
return AuthService.lookup(AccountType.DISCORD, Long.toUnsignedString(userId))
|
||||
.map(Identity::getMinecraftAccount)
|
||||
.map(MinecraftAccount::getUUID);
|
||||
} catch (LookupException e) {
|
||||
logger.error("Lookup for user id " + Long.toUnsignedString(userId) + " failed", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
},
|
||||
return query(
|
||||
() -> AuthService.lookup(AccountType.DISCORD, Long.toUnsignedString(userId), AccountType.MINECRAFT)
|
||||
.map(account -> (MinecraftAccount) account)
|
||||
.map(MinecraftAccount::getUUID),
|
||||
() -> discordSRV.storage().getPlayerUUID(userId),
|
||||
playerUUID -> linked(playerUUID, userId),
|
||||
playerUUID -> unlinked(playerUUID, userId)
|
||||
).exceptionally(t -> {
|
||||
logger.error("Lookup for user id " + Long.toUnsignedString(userId) + " failed", t);
|
||||
return Optional.empty();
|
||||
});
|
||||
}
|
||||
|
||||
private void linked(UUID playerUUID, long userId) {
|
||||
logger.debug("New link: " + playerUUID + " & " + Long.toUnsignedString(userId));
|
||||
discordSRV.storage().createLink(playerUUID, userId);
|
||||
|
||||
}
|
||||
|
||||
private void unlinked(UUID playerUUID, long userId) {
|
||||
logger.debug("Unlink: " + playerUUID + " & " + Long.toUnsignedString(userId));
|
||||
discordSRV.storage().removeLink(playerUUID, userId);
|
||||
|
||||
}
|
||||
|
||||
private <T> CompletableFuture<Optional<T>> query(
|
||||
CheckedSupplier<Optional<T>> authSupplier,
|
||||
Supplier<T> storageSupplier,
|
||||
Consumer<T> linked,
|
||||
Consumer<T> unlinked
|
||||
) {
|
||||
CompletableFuture<Optional<T>> authService = new CompletableFuture<>();
|
||||
|
||||
discordSRV.scheduler().run(() -> {
|
||||
try {
|
||||
authService.complete(authSupplier.get());
|
||||
} catch (Throwable t) {
|
||||
authService.completeExceptionally(t);
|
||||
}
|
||||
});
|
||||
CompletableFuture<Optional<T>> storageResult = CompletableFuture.supplyAsync(
|
||||
() -> Optional.ofNullable(storageSupplier.get()),
|
||||
discordSRV.scheduler().executor()
|
||||
);
|
||||
|
||||
return CompletableFutureUtil.combine(authService, storageResult).thenApply(results -> {
|
||||
Optional<T> auth = authService.join();
|
||||
Optional<T> storage = storageResult.join();
|
||||
|
||||
if (auth.isPresent() && !storage.isPresent()) {
|
||||
// new link
|
||||
linked.accept(auth.get());
|
||||
}
|
||||
if (!auth.isPresent() && storage.isPresent()) {
|
||||
// unlink
|
||||
unlinked.accept(storage.get());
|
||||
}
|
||||
if (auth.isPresent() && storage.isPresent() && !auth.get().equals(storage.get())) {
|
||||
// linked account changed
|
||||
unlinked.accept(storage.get());
|
||||
linked.accept(auth.get());
|
||||
}
|
||||
|
||||
return auth;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +57,14 @@ public class StorageLinker extends CachedLinkProvider implements LinkProvider, L
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> removeLink(@NotNull UUID playerUUID, long userId) {
|
||||
return CompletableFuture.runAsync(
|
||||
() -> discordSRV.storage().removeLink(playerUUID, userId),
|
||||
discordSRV.scheduler().executor()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Integer> getLinkedAccountCount() {
|
||||
return CompletableFuture.supplyAsync(
|
||||
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.linking.requirelinking;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.linking.impl.MinecraftAuthenticationLinker;
|
||||
import com.discordsrv.common.linking.requirelinking.requirement.*;
|
||||
import com.discordsrv.common.linking.requirelinking.requirement.parser.RequirementParser;
|
||||
import com.discordsrv.common.module.type.AbstractModule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public abstract class RequiredLinkingModule<T extends DiscordSRV> extends AbstractModule<T> {
|
||||
|
||||
private final List<Requirement<?>> availableRequirements = new ArrayList<>();
|
||||
|
||||
public RequiredLinkingModule(T discordSRV) {
|
||||
super(discordSRV);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reloadNoResult() {
|
||||
List<Requirement<?>> requirements = new ArrayList<>();
|
||||
|
||||
requirements.add(new DiscordRoleRequirement(discordSRV));
|
||||
requirements.add(new DiscordServerRequirement(discordSRV));
|
||||
requirements.add(new DiscordBoostingRequirement(discordSRV));
|
||||
|
||||
if (discordSRV.linkProvider() instanceof MinecraftAuthenticationLinker) {
|
||||
requirements.addAll(MinecraftAuthRequirement.createRequirements(discordSRV));
|
||||
}
|
||||
|
||||
synchronized (availableRequirements) {
|
||||
availableRequirements.clear();
|
||||
availableRequirements.addAll(requirements);
|
||||
}
|
||||
}
|
||||
|
||||
protected List<CompiledRequirement> compile(List<String> requirements) {
|
||||
List<CompiledRequirement> checks = new ArrayList<>();
|
||||
for (String requirement : requirements) {
|
||||
BiFunction<UUID, Long, CompletableFuture<Boolean>> function = RequirementParser.getInstance().parse(requirement, availableRequirements);
|
||||
checks.add(new CompiledRequirement(requirement, function));
|
||||
}
|
||||
return checks;
|
||||
}
|
||||
|
||||
public static class CompiledRequirement {
|
||||
|
||||
private final String input;
|
||||
private final BiFunction<UUID, Long, CompletableFuture<Boolean>> function;
|
||||
|
||||
protected CompiledRequirement(String input, BiFunction<UUID, Long, CompletableFuture<Boolean>> function) {
|
||||
this.input = input;
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
public String input() {
|
||||
return input;
|
||||
}
|
||||
|
||||
public BiFunction<UUID, Long, CompletableFuture<Boolean>> function() {
|
||||
return function;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.linking.requirelinking;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.main.linking.RequirementsConfig;
|
||||
import com.discordsrv.common.future.util.CompletableFutureUtil;
|
||||
import com.discordsrv.common.linking.LinkProvider;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public abstract class ServerRequireLinkingModule<T extends DiscordSRV> extends RequiredLinkingModule<T> {
|
||||
|
||||
private final List<CompiledRequirement> compiledRequirements = new CopyOnWriteArrayList<>();
|
||||
|
||||
public ServerRequireLinkingModule(T discordSRV) {
|
||||
super(discordSRV);
|
||||
}
|
||||
|
||||
public abstract RequirementsConfig config();
|
||||
|
||||
@Override
|
||||
public void reloadNoResult() {
|
||||
super.reloadNoResult();
|
||||
|
||||
synchronized (compiledRequirements) {
|
||||
compiledRequirements.clear();
|
||||
compiledRequirements.addAll(compile(config().requirements));
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<Component> getKickReason(UUID playerUUID) {
|
||||
RequirementsConfig config = config();
|
||||
if (config.bypassUUIDs.contains(playerUUID.toString())) {
|
||||
// Bypasses: let them through
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
LinkProvider linkProvider = discordSRV.linkProvider();
|
||||
if (linkProvider == null) {
|
||||
// Link provider unavailable but required linking enabled: error message
|
||||
return CompletableFuture.completedFuture(Component.text("Unable to check linking status at this time"));
|
||||
}
|
||||
|
||||
return linkProvider.getUserId(playerUUID)
|
||||
.thenCompose(opt -> {
|
||||
if (!opt.isPresent()) {
|
||||
// User is not linked
|
||||
return CompletableFuture.completedFuture(Component.text("Not linked"));
|
||||
}
|
||||
|
||||
List<CompiledRequirement> requirements;
|
||||
synchronized (compiledRequirements) {
|
||||
requirements = compiledRequirements;
|
||||
}
|
||||
|
||||
if (requirements.isEmpty()) {
|
||||
// No additional requirements: let them through
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
CompletableFuture<Void> pass = new CompletableFuture<>();
|
||||
List<CompletableFuture<Boolean>> all = new ArrayList<>();
|
||||
long userId = opt.get();
|
||||
|
||||
for (CompiledRequirement requirement : requirements) {
|
||||
CompletableFuture<Boolean> future = requirement.function().apply(playerUUID, userId);
|
||||
all.add(future);
|
||||
|
||||
future.whenComplete((val, t) -> {
|
||||
if (val != null && val) {
|
||||
pass.complete(null);
|
||||
}
|
||||
}).exceptionally(t -> {
|
||||
logger().debug("Check \"" + requirement.input() + "\" failed for " + playerUUID + " / " + Long.toUnsignedString(userId), t);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
// Complete when at least one passes or all of them completed
|
||||
return CompletableFuture.anyOf(pass, CompletableFutureUtil.combine(all))
|
||||
.thenApply(v -> {
|
||||
if (pass.isDone()) {
|
||||
// One of the futures passed: let them through
|
||||
return null;
|
||||
}
|
||||
|
||||
// None of the futures passed: requirements not met
|
||||
return Component.text("You did not pass requirements");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.linking.requirelinking.requirement;
|
||||
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuild;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class DiscordBoostingRequirement extends LongRequirement {
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
|
||||
public DiscordBoostingRequirement(DiscordSRV discordSRV) {
|
||||
this.discordSRV = discordSRV;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "DiscordBoosting";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> isMet(Long value, UUID player, long userId) {
|
||||
DiscordGuild guild = discordSRV.discordAPI().getGuildById(value);
|
||||
if (guild == null) {
|
||||
return CompletableFuture.completedFuture(false);
|
||||
}
|
||||
|
||||
return guild.retrieveMemberById(userId)
|
||||
.thenApply(member -> member != null && member.isBoosting());
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.linking.requirelinking.requirement;
|
||||
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordRole;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class DiscordRoleRequirement extends LongRequirement {
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
|
||||
public DiscordRoleRequirement(DiscordSRV discordSRV) {
|
||||
this.discordSRV = discordSRV;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "DiscordRole";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> isMet(Long value, UUID player, long userId) {
|
||||
DiscordRole role = discordSRV.discordAPI().getRoleById(value);
|
||||
if (role == null) {
|
||||
return CompletableFuture.completedFuture(false);
|
||||
}
|
||||
|
||||
return role.getGuild()
|
||||
.retrieveMemberById(userId)
|
||||
.thenApply(member -> member.getRoles().contains(role));
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.linking.requirelinking.requirement;
|
||||
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuild;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class DiscordServerRequirement extends LongRequirement {
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
|
||||
public DiscordServerRequirement(DiscordSRV discordSRV) {
|
||||
this.discordSRV = discordSRV;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "DiscordServer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> isMet(Long value, UUID player, long userId) {
|
||||
DiscordGuild guild = discordSRV.discordAPI().getGuildById(value);
|
||||
if (guild == null) {
|
||||
return CompletableFuture.completedFuture(false);
|
||||
}
|
||||
|
||||
return guild.retrieveMemberById(userId)
|
||||
.thenApply(Objects::nonNull);
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.linking.requirelinking.requirement;
|
||||
|
||||
public abstract class LongRequirement implements Requirement<Long> {
|
||||
|
||||
@Override
|
||||
public Long parse(String input) {
|
||||
try {
|
||||
return Long.parseUnsignedLong(input);
|
||||
} catch (NumberFormatException ignored) {}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.linking.requirelinking.requirement;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.function.CheckedSupplier;
|
||||
import me.minecraftauth.lib.AuthService;
|
||||
import me.minecraftauth.lib.account.platform.twitch.SubTier;
|
||||
import me.minecraftauth.lib.exception.LookupException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class MinecraftAuthRequirement<T> implements Requirement<MinecraftAuthRequirement.Reference<T>> {
|
||||
|
||||
private static final Reference<?> NULL_VALUE = new Reference<>(null);
|
||||
|
||||
public static List<Requirement<?>> createRequirements(DiscordSRV discordSRV) {
|
||||
List<Requirement<?>> requirements = new ArrayList<>();
|
||||
|
||||
// Patreon
|
||||
requirements.add(new MinecraftAuthRequirement<>(
|
||||
discordSRV,
|
||||
"PatreonSubscriber",
|
||||
AuthService::isSubscribedPatreon,
|
||||
AuthService::isSubscribedPatreon
|
||||
));
|
||||
|
||||
// Glimpse
|
||||
requirements.add(new MinecraftAuthRequirement<>(
|
||||
discordSRV,
|
||||
"GlimpseSubscriber",
|
||||
AuthService::isSubscribedGlimpse,
|
||||
AuthService::isSubscribedGlimpse
|
||||
));
|
||||
|
||||
// Twitch
|
||||
requirements.add(new MinecraftAuthRequirement<>(
|
||||
discordSRV,
|
||||
"TwitchFollower",
|
||||
AuthService::isFollowingTwitch
|
||||
));
|
||||
requirements.add(new MinecraftAuthRequirement<>(
|
||||
discordSRV,
|
||||
"TwitchSubscriber",
|
||||
AuthService::isSubscribedTwitch,
|
||||
AuthService::isSubscribedTwitch,
|
||||
string -> {
|
||||
try {
|
||||
int value = Integer.parseInt(string);
|
||||
return SubTier.level(value);
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
// YouTube
|
||||
requirements.add(new MinecraftAuthRequirement<>(
|
||||
discordSRV,
|
||||
"YouTubeSubscriber",
|
||||
AuthService::isSubscribedYouTube
|
||||
));
|
||||
requirements.add(new MinecraftAuthRequirement<>(
|
||||
discordSRV,
|
||||
"YouTubeMember",
|
||||
AuthService::isMemberYouTube,
|
||||
AuthService::isMemberYouTube
|
||||
));
|
||||
|
||||
return requirements;
|
||||
}
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
private final String name;
|
||||
private final Test test;
|
||||
private final TestSpecific<T> testSpecific;
|
||||
private final Function<String, T> parse;
|
||||
|
||||
public MinecraftAuthRequirement(
|
||||
DiscordSRV discordSRV,
|
||||
String name,
|
||||
Test test
|
||||
) {
|
||||
this(discordSRV, name, test, null, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public MinecraftAuthRequirement(
|
||||
DiscordSRV discordSRV,
|
||||
String name,
|
||||
Test test,
|
||||
TestSpecific<String> testSpecific
|
||||
) {
|
||||
this(discordSRV, name, test, (TestSpecific<T>) testSpecific, t -> (T) t);
|
||||
}
|
||||
|
||||
public MinecraftAuthRequirement(
|
||||
DiscordSRV discordSRV,
|
||||
String name,
|
||||
Test test,
|
||||
TestSpecific<T> testSpecific,
|
||||
Function<String, T> parse
|
||||
) {
|
||||
this.discordSRV = discordSRV;
|
||||
this.name = name;
|
||||
this.test = test;
|
||||
this.testSpecific = testSpecific;
|
||||
this.parse = parse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Reference<T> parse(String input) {
|
||||
if (StringUtils.isEmpty(input)) {
|
||||
return (Reference<T>) NULL_VALUE;
|
||||
} else if (parse != null) {
|
||||
return new Reference<>(parse.apply(input));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> isMet(Reference<T> atomicReference, UUID player, long userId) {
|
||||
String token = discordSRV.connectionConfig().minecraftAuth.token;
|
||||
T value = atomicReference.getValue();
|
||||
if (value == null) {
|
||||
return supply(() -> test.test(token, player));
|
||||
} else {
|
||||
return supply(() -> testSpecific.test(token, player, value));
|
||||
}
|
||||
}
|
||||
|
||||
private CompletableFuture<Boolean> supply(CheckedSupplier<Boolean> provider) {
|
||||
CompletableFuture<Boolean> completableFuture = new CompletableFuture<>();
|
||||
discordSRV.scheduler().run(() -> {
|
||||
try {
|
||||
completableFuture.complete(provider.get());
|
||||
} catch (Throwable t) {
|
||||
completableFuture.completeExceptionally(t);
|
||||
}
|
||||
});
|
||||
return completableFuture;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Test {
|
||||
boolean test(String serverToken, UUID player) throws LookupException;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface TestSpecific<T> {
|
||||
boolean test(String serverToken, UUID uuid, T specific) throws LookupException;
|
||||
}
|
||||
|
||||
public static class Reference<T> {
|
||||
|
||||
private final T value;
|
||||
|
||||
public Reference(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.linking.requirelinking.requirement;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface Requirement<T> {
|
||||
|
||||
String name();
|
||||
|
||||
T parse(String input);
|
||||
|
||||
CompletableFuture<Boolean> isMet(T value, UUID player, long userId);
|
||||
|
||||
}
|
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.linking.requirelinking.requirement.parser;
|
||||
|
||||
import com.discordsrv.common.future.util.CompletableFutureUtil;
|
||||
import com.discordsrv.common.linking.requirelinking.requirement.Requirement;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class RequirementParser {
|
||||
|
||||
private static RequirementParser INSTANCE;
|
||||
|
||||
public static RequirementParser getInstance() {
|
||||
return INSTANCE != null ? INSTANCE : (INSTANCE = new RequirementParser());
|
||||
}
|
||||
|
||||
private RequirementParser() {}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> BiFunction<UUID, Long, CompletableFuture<Boolean>> parse(String input, List<Requirement<?>> requirements) {
|
||||
List<Requirement<T>> reqs = new ArrayList<>(requirements.size());
|
||||
requirements.forEach(r -> reqs.add((Requirement<T>) r));
|
||||
|
||||
Func func = parse(input, new AtomicInteger(0), reqs);
|
||||
return func::test;
|
||||
}
|
||||
|
||||
private <T> Func parse(String input, AtomicInteger iterator, List<Requirement<T>> requirements) {
|
||||
StringBuilder functionNameBuffer = new StringBuilder();
|
||||
StringBuilder functionValueBuffer = new StringBuilder();
|
||||
boolean isFunctionValue = false;
|
||||
|
||||
Func func = null;
|
||||
Operator operator = null;
|
||||
boolean operatorSecond = false;
|
||||
|
||||
Function<String, RuntimeException> error = text -> {
|
||||
int i = iterator.get();
|
||||
return new IllegalArgumentException(text + "\n" + input + "\n" + StringUtils.leftPad("^", i));
|
||||
};
|
||||
|
||||
char[] chars = input.toCharArray();
|
||||
int i;
|
||||
for (; (i = iterator.get()) < chars.length; iterator.incrementAndGet()) {
|
||||
char c = chars[i];
|
||||
if (c == '(' && functionNameBuffer.length() == 0) {
|
||||
iterator.incrementAndGet();
|
||||
Func function = parse(input, iterator, requirements);
|
||||
if (function == null) {
|
||||
throw error.apply("Empty brackets");
|
||||
}
|
||||
|
||||
if (func != null) {
|
||||
if (operator == null) {
|
||||
throw error.apply("No operator");
|
||||
}
|
||||
|
||||
func = operator.function.apply(func, function);
|
||||
operator = null;
|
||||
} else {
|
||||
func = function;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == ')' && functionNameBuffer.length() == 0) {
|
||||
return func;
|
||||
}
|
||||
if (c == '(' && functionNameBuffer.length() > 0) {
|
||||
if (isFunctionValue) {
|
||||
throw error.apply("Opening bracket inside function value");
|
||||
}
|
||||
|
||||
isFunctionValue = true;
|
||||
continue;
|
||||
}
|
||||
if (c == ')' && functionNameBuffer.length() > 0) {
|
||||
String functionName = functionNameBuffer.toString();
|
||||
String value = functionValueBuffer.toString();
|
||||
|
||||
for (Requirement<T> requirement : requirements) {
|
||||
if (requirement.name().equalsIgnoreCase(functionName)) {
|
||||
T requirementValue = requirement.parse(value);
|
||||
if (requirementValue == null) {
|
||||
throw error.apply("Unacceptable function value for " + functionName);
|
||||
}
|
||||
|
||||
Func function = (player, user) -> requirement.isMet(requirementValue, player, user);
|
||||
if (func != null) {
|
||||
if (operator == null) {
|
||||
throw error.apply("No operator");
|
||||
}
|
||||
|
||||
func = operator.function.apply(func, function);
|
||||
operator = null;
|
||||
} else {
|
||||
func = function;
|
||||
}
|
||||
|
||||
functionNameBuffer.setLength(0);
|
||||
functionValueBuffer.setLength(0);
|
||||
isFunctionValue = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (functionNameBuffer.length() != 0) {
|
||||
throw error.apply("Unknown function: " + functionName);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (operator != null && !operatorSecond && c == operator.character) {
|
||||
operatorSecond = true;
|
||||
continue;
|
||||
} else if (operator == null && functionNameBuffer.length() == 0) {
|
||||
boolean found = false;
|
||||
for (Operator value : Operator.values()) {
|
||||
if (value.character == c) {
|
||||
if (func == null) {
|
||||
throw error.apply("No condition");
|
||||
}
|
||||
operator = value;
|
||||
operatorSecond = false;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (operator != null && !operatorSecond) {
|
||||
throw error.apply("Operators must be exactly two of the same character");
|
||||
}
|
||||
|
||||
if (!Character.isSpaceChar(c)) {
|
||||
if (isFunctionValue) {
|
||||
functionValueBuffer.append(c);
|
||||
} else {
|
||||
functionNameBuffer.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (operator != null) {
|
||||
throw error.apply("Dangling operator");
|
||||
}
|
||||
return func;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface Func {
|
||||
CompletableFuture<Boolean> test(UUID player, long user);
|
||||
}
|
||||
|
||||
private enum Operator {
|
||||
|
||||
AND('&', (one, two) -> apply(one, two, (o, t) -> o && t)),
|
||||
OR('|', (one, two) -> apply(one, two, (o, t) -> o || t));
|
||||
|
||||
private final char character;
|
||||
private final BiFunction<Func, Func, Func> function;
|
||||
|
||||
Operator(char character, BiFunction<Func, Func, Func> function) {
|
||||
this.character = character;
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
private static Func apply(Func one, Func two, BiFunction<Boolean, Boolean, Boolean> function) {
|
||||
return (player, user) -> CompletableFutureUtil.combine(one.test(player, user), two.test(player, user))
|
||||
.thenApply(bools -> function.apply(bools.get(0), bools.get(1)));
|
||||
}
|
||||
}
|
||||
}
|
@ -37,6 +37,7 @@ public interface Storage {
|
||||
UUID getPlayerUUID(long userId);
|
||||
|
||||
void createLink(@NotNull UUID player, long userId);
|
||||
void removeLink(@NotNull UUID player, long userId);
|
||||
|
||||
int getLinkedAccountCount();
|
||||
|
||||
|
@ -22,7 +22,6 @@ import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.exception.StorageException;
|
||||
import com.discordsrv.common.function.CheckedConsumer;
|
||||
import com.discordsrv.common.function.CheckedFunction;
|
||||
import com.discordsrv.common.linking.impl.StorageLinker;
|
||||
import com.discordsrv.common.storage.Storage;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@ -40,7 +39,7 @@ public abstract class SQLStorage implements Storage {
|
||||
|
||||
public abstract Connection getConnection();
|
||||
public abstract boolean isAutoCloseConnections();
|
||||
public abstract void createTables(Connection connection, String tablePrefix, boolean linkedAccounts) throws SQLException;
|
||||
public abstract void createTables(Connection connection, String tablePrefix) throws SQLException;
|
||||
|
||||
private void useConnection(CheckedConsumer<Connection> connectionConsumer) throws StorageException {
|
||||
useConnection(connection -> {
|
||||
@ -77,8 +76,7 @@ public abstract class SQLStorage implements Storage {
|
||||
public void initialize() {
|
||||
useConnection((CheckedConsumer<Connection>) connection -> createTables(
|
||||
connection,
|
||||
discordSRV.connectionConfig().storage.sqlTablePrefix,
|
||||
discordSRV.linkProvider() instanceof StorageLinker
|
||||
discordSRV.connectionConfig().storage.sqlTablePrefix
|
||||
));
|
||||
}
|
||||
|
||||
@ -128,6 +126,16 @@ public abstract class SQLStorage implements Storage {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeLink(@NotNull UUID player, long userId) {
|
||||
useConnection(connection -> {
|
||||
try (PreparedStatement statement = connection.prepareStatement("delete " + tablePrefix() + "LINKED_ACCOUNTS where PLAYER_UUID = ?;")) {
|
||||
statement.setString(1, player.toString());
|
||||
exceptEffectedRows(statement.executeUpdate(), 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLinkedAccountCount() {
|
||||
return useConnection(connection -> {
|
||||
|
@ -99,17 +99,15 @@ public class H2Storage extends SQLStorage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createTables(Connection connection, String tablePrefix, boolean linkedAccounts) throws SQLException {
|
||||
if (linkedAccounts) {
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
statement.execute(
|
||||
"create table if not exists " + tablePrefix + "linked_accounts "
|
||||
+ "(ID int not null auto_increment, "
|
||||
+ "PLAYER_UUID varchar(36), "
|
||||
+ "USER_ID bigint, "
|
||||
+ "constraint LINKED_ACCOUNTS_PK primary key (ID)"
|
||||
+ ")");
|
||||
}
|
||||
public void createTables(Connection connection, String tablePrefix) throws SQLException {
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
statement.execute(
|
||||
"create table if not exists " + tablePrefix + "linked_accounts "
|
||||
+ "(ID int not null auto_increment, "
|
||||
+ "PLAYER_UUID varchar(36), "
|
||||
+ "USER_ID bigint, "
|
||||
+ "constraint LINKED_ACCOUNTS_PK primary key (ID)"
|
||||
+ ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,17 +51,15 @@ public class MySQLStorage extends HikariStorage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createTables(Connection connection, String tablePrefix, boolean linkedAccounts) throws SQLException {
|
||||
if (linkedAccounts) {
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
statement.execute(
|
||||
"create table if not exists " + tablePrefix + "linked_accounts "
|
||||
+ "(ID int not null auto_increment, "
|
||||
+ "PLAYER_UUID varchar(36), "
|
||||
+ "USER_ID bigint, "
|
||||
+ "constraint LINKED_ACCOUNTS_PK primary key (ID)"
|
||||
+ ")");
|
||||
}
|
||||
public void createTables(Connection connection, String tablePrefix) throws SQLException {
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
statement.execute(
|
||||
"create table if not exists " + tablePrefix + "linked_accounts "
|
||||
+ "(ID int not null auto_increment, "
|
||||
+ "PLAYER_UUID varchar(36), "
|
||||
+ "USER_ID bigint, "
|
||||
+ "constraint LINKED_ACCOUNTS_PK primary key (ID)"
|
||||
+ ")");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,145 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.linking.requirement.parser;
|
||||
|
||||
import com.discordsrv.common.linking.requirelinking.requirement.Requirement;
|
||||
import com.discordsrv.common.linking.requirelinking.requirement.parser.RequirementParser;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.function.Executable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class RequirementParserTest {
|
||||
|
||||
private final RequirementParser requirementParser = RequirementParser.getInstance();
|
||||
private final List<Requirement<?>> requirements = Arrays.asList(
|
||||
new Requirement<Boolean>() {
|
||||
@Override
|
||||
public String name() {
|
||||
return "F";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean parse(String input) {
|
||||
return Boolean.parseBoolean(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> isMet(Boolean value, UUID player, long userId) {
|
||||
return CompletableFuture.completedFuture(value);
|
||||
}
|
||||
},
|
||||
new Requirement<Object>() {
|
||||
@Override
|
||||
public String name() {
|
||||
return "AlwaysError";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parse(String input) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> isMet(Object value, UUID player, long userId) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
private boolean parse(String input) {
|
||||
return requirementParser.parse(input, requirements).apply(null, 0L).join();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void differentCase() {
|
||||
assertFalse(parse("f(false) || F(false)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void orFail() {
|
||||
assertFalse(parse("F(false) || F(false)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void orPass() {
|
||||
assertTrue(parse("F(true) || F(false)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void andFail() {
|
||||
assertFalse(parse("F(true) && F(false)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void andPass() {
|
||||
assertTrue(parse("F(true) && F(true)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void complexFail() {
|
||||
assertFalse(parse("F(true) && (F(false) && F(true))"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void complexPass() {
|
||||
assertTrue(parse("F(true) && (F(false) || F(true))"));
|
||||
}
|
||||
|
||||
private void assertExceptionMessageStartsWith(String exceptionMessage, Executable executable) {
|
||||
try {
|
||||
executable.execute();
|
||||
} catch (Throwable e) {
|
||||
if (!e.getMessage().startsWith(exceptionMessage)) {
|
||||
fail("Exception message did not start with: " + exceptionMessage + " Actually: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noConditionError() {
|
||||
assertExceptionMessageStartsWith("No condition", () -> parse("&&"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void operatorLengthError() {
|
||||
assertExceptionMessageStartsWith("Operators must be exactly two of the same character", () -> parse("F(true) & F(true)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void danglingOperatorError() {
|
||||
assertExceptionMessageStartsWith("Dangling operator", () -> parse("F(true) &&"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyBracketsError() {
|
||||
assertExceptionMessageStartsWith("Empty brackets", () -> parse("()"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unacceptableValueError() {
|
||||
assertExceptionMessageStartsWith("Unacceptable function value for", () -> parse("AlwaysError()"));
|
||||
}
|
||||
|
||||
}
|
@ -91,7 +91,7 @@ dependencyResolutionManagement {
|
||||
library('mysql', 'mysql', 'mysql-connector-java').version('8.0.28')
|
||||
|
||||
// MinecraftAuth lib
|
||||
library('minecraftauth-lib', 'me.minecraftauth', 'lib').version('1.0.1')
|
||||
library('minecraftauth-lib', 'me.minecraftauth', 'lib').version('1.1.0')
|
||||
|
||||
// Brigadier & Commodore
|
||||
library('brigadier', 'com.mojang', 'brigadier').version('1.0.18')
|
||||
|
Loading…
Reference in New Issue
Block a user