mirror of
https://github.com/GeyserMC/Floodgate.git
synced 2026-01-06 15:42:03 +00:00
Use Micronaut HTTP client, config loading in boostrap, work on database
This commit is contained in:
@@ -33,20 +33,15 @@ import org.geysermc.floodgate.api.InstanceHolder;
|
||||
import org.geysermc.floodgate.api.event.FloodgateEventBus;
|
||||
import org.geysermc.floodgate.api.handshake.HandshakeHandlers;
|
||||
import org.geysermc.floodgate.api.inject.PlatformInjector;
|
||||
import org.geysermc.floodgate.api.link.PlayerLink;
|
||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.api.packet.PacketHandlers;
|
||||
import org.geysermc.floodgate.core.config.Properties;
|
||||
import org.geysermc.floodgate.core.database.PlayerLinkRepository;
|
||||
import org.geysermc.floodgate.core.database.entity.LinkedPlayer;
|
||||
import org.geysermc.floodgate.core.event.EventBus;
|
||||
import org.geysermc.floodgate.core.event.lifecycle.PostEnableEvent;
|
||||
import org.geysermc.floodgate.core.event.lifecycle.ShutdownEvent;
|
||||
import org.geysermc.floodgate.core.util.EagerSingleton;
|
||||
import org.geysermc.floodgate.isolation.library.Library;
|
||||
import org.geysermc.floodgate.core.util.GlobalBeanCache;
|
||||
import org.geysermc.floodgate.isolation.library.LibraryManager;
|
||||
import org.geysermc.floodgate.isolation.library.Repository;
|
||||
import org.geysermc.floodgate.isolation.library.info.DependencyInfoLoader;
|
||||
|
||||
public abstract class FloodgatePlatform {
|
||||
private static final UUID KEY = UUID.randomUUID();
|
||||
@@ -65,32 +60,11 @@ public abstract class FloodgatePlatform {
|
||||
public void load() {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
var infoLoader = DependencyInfoLoader.load(
|
||||
getClass().getClassLoader().getResource("dependencyInfo.txt")
|
||||
);
|
||||
|
||||
manager
|
||||
// .addLibrary(
|
||||
// Library.builder(infoLoader)
|
||||
// .id("guava")
|
||||
// .repository(Repository.MAVEN_CENTRAL)
|
||||
// .groupId("com.google.guava")
|
||||
// .artifactId("guava")
|
||||
// .build()
|
||||
// )
|
||||
.addLibrary(
|
||||
Library.builder()
|
||||
.id("local-linking")
|
||||
.repository(Repository.OPEN_COLLAB)
|
||||
.groupId("org.geysermc.floodgate")
|
||||
.artifactId("database")
|
||||
.version("a-version")
|
||||
.build()
|
||||
)
|
||||
.apply();
|
||||
GlobalBeanCache.cacheIfAbsent("libraryManager", () -> manager);
|
||||
|
||||
//noinspection unchecked
|
||||
context = ApplicationContext.builder(manager.classLoader())
|
||||
.singletons(manager)
|
||||
.properties(Map.of(
|
||||
"platform.proxy", isProxy()
|
||||
))
|
||||
@@ -102,34 +76,11 @@ public abstract class FloodgatePlatform {
|
||||
onContextCreated(context);
|
||||
context.start();
|
||||
|
||||
LinkedPlayer link = new LinkedPlayer()
|
||||
.bedrockId(UUID.fromString("00000000-0000-0000-0009-01f64f65c7c3"))
|
||||
.javaUniqueId(UUID.fromString("d34eb447-6e90-4c78-9281-600df88aef1d"))
|
||||
.javaUsername("Tim203");
|
||||
System.out.println(context.getBean(PlayerLinkRepository.class).save(link));
|
||||
|
||||
System.out.println(context.getBean(PlayerLinkRepository.class)
|
||||
.findByBedrockId(UUID.fromString("00000000-0000-0000-0009-01f64f65c7c3")));
|
||||
System.out.println(context.getBean(PlayerLinkRepository.class)
|
||||
.findByJavaUniqueId(UUID.fromString("d34eb447-6e90-4c78-9281-600df88aef1d")));
|
||||
System.out.println(context.getBean(PlayerLinkRepository.class)
|
||||
.existsByBedrockId(UUID.fromString("00000000-0000-0000-0009-01f64f65c7c3")));
|
||||
System.out.println(context.getBean(PlayerLinkRepository.class)
|
||||
.existsByJavaUniqueId(UUID.fromString("d34eb447-6e90-4c78-9281-600df88aef1d")));
|
||||
|
||||
// var scopeBuilder = BeanScope.builder()
|
||||
// .bean("isProxy", boolean.class, isProxy())
|
||||
// .modules(new CoreModule())
|
||||
// // temporary fix for https://github.com/avaje/avaje-inject/issues/295
|
||||
// .modules(makeModule(isProxy() ? PROXY_MODULE : SERVER_MODULE));
|
||||
// onBuildBeanScope(scopeBuilder);
|
||||
// scope = scopeBuilder.build();
|
||||
|
||||
injector = context.getBean(PlatformInjector.class);
|
||||
|
||||
InstanceHolder.set(
|
||||
context.getBean(FloodgateApi.class),
|
||||
context.getBean(PlayerLink.class),
|
||||
null, // todo context.getBean(PlayerLink.class),
|
||||
context.getBean(FloodgateEventBus.class),
|
||||
injector,
|
||||
context.getBean(PacketHandlers.class),
|
||||
|
||||
@@ -28,8 +28,6 @@ package org.geysermc.floodgate.core.api;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.micronaut.context.BeanProvider;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Collection;
|
||||
@@ -45,12 +43,13 @@ import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
import org.geysermc.floodgate.api.unsafe.Unsafe;
|
||||
import org.geysermc.floodgate.core.config.FloodgateConfig;
|
||||
import org.geysermc.floodgate.core.http.xbox.GetGamertagResult;
|
||||
import org.geysermc.floodgate.core.http.xbox.GetXuidResult;
|
||||
import org.geysermc.floodgate.core.http.xbox.XboxClient;
|
||||
import org.geysermc.floodgate.core.pluginmessage.PluginMessageManager;
|
||||
import org.geysermc.floodgate.core.pluginmessage.channel.FormChannel;
|
||||
import org.geysermc.floodgate.core.pluginmessage.channel.TransferChannel;
|
||||
import org.geysermc.floodgate.core.scope.ServerOnly;
|
||||
import org.geysermc.floodgate.core.util.Constants;
|
||||
import org.geysermc.floodgate.core.util.HttpClient;
|
||||
import org.geysermc.floodgate.core.util.Utils;
|
||||
|
||||
@ServerOnly
|
||||
@@ -63,8 +62,8 @@ public class SimpleFloodgateApi implements FloodgateApi {
|
||||
|
||||
@Inject BeanProvider<PluginMessageManager> pluginMessageManager;
|
||||
@Inject FloodgateConfig config;
|
||||
@Inject HttpClient httpClient;
|
||||
@Inject FloodgateLogger logger;
|
||||
@Inject XboxClient xboxClient;
|
||||
|
||||
@Override
|
||||
public String getPlayerPrefix() {
|
||||
@@ -148,36 +147,12 @@ public class SimpleFloodgateApi implements FloodgateApi {
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Long> getXuidFor(String gamertag) {
|
||||
if (gamertag == null || gamertag.isEmpty() || gamertag.length() > 16) {
|
||||
return Utils.failedFuture(new IllegalStateException("Received an invalid gamertag"));
|
||||
}
|
||||
|
||||
return httpClient.asyncGet(Constants.GET_XUID_URL + gamertag)
|
||||
.thenApply(result -> {
|
||||
JsonObject response = result.getResponse();
|
||||
|
||||
if (!result.isCodeOk()) {
|
||||
throw new IllegalStateException(response.get("message").getAsString());
|
||||
}
|
||||
|
||||
JsonElement xuid = response.get("xuid");
|
||||
return xuid != null ? xuid.getAsLong() : null;
|
||||
});
|
||||
return xboxClient.xuidByGamertag(gamertag).thenApply(GetXuidResult::xuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<String> getGamertagFor(long xuid) {
|
||||
return httpClient.asyncGet(Constants.GET_GAMERTAG_URL + xuid)
|
||||
.thenApply(result -> {
|
||||
JsonObject response = result.getResponse();
|
||||
|
||||
if (!result.isCodeOk()) {
|
||||
throw new IllegalStateException(response.get("message").getAsString());
|
||||
}
|
||||
|
||||
JsonElement gamertag = response.get("gamertag");
|
||||
return gamertag != null ? gamertag.getAsString() : null;
|
||||
});
|
||||
return xboxClient.gamertagByXuid(xuid).thenApply(GetGamertagResult::gamertag);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -33,14 +33,15 @@ import cloud.commandframework.context.CommandContext;
|
||||
import io.micronaut.context.annotation.Secondary;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.floodgate.api.FloodgateApi;
|
||||
import org.geysermc.floodgate.api.link.LinkRequestResult;
|
||||
import org.geysermc.floodgate.api.link.PlayerLink;
|
||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.core.command.util.Permission;
|
||||
import org.geysermc.floodgate.core.config.FloodgateConfig;
|
||||
import org.geysermc.floodgate.core.link.CommonPlayerLink;
|
||||
import org.geysermc.floodgate.core.link.GlobalPlayerLinking;
|
||||
import org.geysermc.floodgate.core.link.LinkVerificationException;
|
||||
import org.geysermc.floodgate.core.platform.command.FloodgateCommand;
|
||||
import org.geysermc.floodgate.core.platform.command.TranslatableMessage;
|
||||
import org.geysermc.floodgate.core.player.UserAudience;
|
||||
@@ -48,11 +49,13 @@ import org.geysermc.floodgate.core.player.UserAudience.PlayerAudience;
|
||||
import org.geysermc.floodgate.core.player.audience.ProfileAudience;
|
||||
import org.geysermc.floodgate.core.player.audience.ProfileAudienceArgument;
|
||||
import org.geysermc.floodgate.core.util.Constants;
|
||||
import org.geysermc.floodgate.core.util.Utils;
|
||||
|
||||
@Singleton
|
||||
@Secondary
|
||||
public final class LinkAccountCommand implements FloodgateCommand {
|
||||
@Inject FloodgateApi api;
|
||||
@Inject CommonPlayerLink link;
|
||||
@Inject FloodgateLogger logger;
|
||||
|
||||
@Override
|
||||
@@ -71,11 +74,9 @@ public final class LinkAccountCommand implements FloodgateCommand {
|
||||
public void execute(CommandContext<UserAudience> context) {
|
||||
UserAudience sender = context.getSender();
|
||||
|
||||
PlayerLink link = api.getPlayerLink();
|
||||
|
||||
//todo make this less hacky
|
||||
if (link instanceof GlobalPlayerLinking) {
|
||||
if (((GlobalPlayerLinking) link).getDatabaseImpl() != null) {
|
||||
if (((GlobalPlayerLinking) link).getDatabase() != null) {
|
||||
sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE,
|
||||
Constants.LINK_INFO_URL);
|
||||
} else {
|
||||
@@ -85,7 +86,7 @@ public final class LinkAccountCommand implements FloodgateCommand {
|
||||
}
|
||||
}
|
||||
|
||||
if (!link.isEnabledAndAllowed()) {
|
||||
if (!link.isActive()) {
|
||||
sender.sendMessage(CommonCommandMessage.LINKING_DISABLED);
|
||||
return;
|
||||
}
|
||||
@@ -103,33 +104,40 @@ public final class LinkAccountCommand implements FloodgateCommand {
|
||||
|
||||
String code = context.get("code");
|
||||
|
||||
link.verifyLinkRequest(sender.uuid(), targetName, sender.username(), code)
|
||||
.whenComplete((result, throwable) -> {
|
||||
if (throwable != null || result == LinkRequestResult.UNKNOWN_ERROR) {
|
||||
link.linkRequest(targetName)
|
||||
.thenApply(request -> {
|
||||
if (request == null || link.isRequestedPlayer(request, sender.uuid())) {
|
||||
throw LinkVerificationException.NO_LINK_REQUESTED;
|
||||
}
|
||||
|
||||
if (!request.linkCode().equals(code)) {
|
||||
throw LinkVerificationException.INVALID_CODE;
|
||||
}
|
||||
|
||||
if (request.isExpired(link.getVerifyLinkTimeout())) {
|
||||
throw LinkVerificationException.LINK_REQUEST_EXPIRED;
|
||||
}
|
||||
|
||||
return request;
|
||||
})
|
||||
.thenCompose(request ->
|
||||
CompletableFuture.allOf(
|
||||
link.invalidateLinkRequest(request),
|
||||
link.addLink(
|
||||
request.javaUniqueId(), request.javaUsername(), sender.uuid()
|
||||
)
|
||||
)
|
||||
)
|
||||
.whenComplete(($, throwable) -> {
|
||||
if (throwable instanceof LinkVerificationException exception) {
|
||||
sender.sendMessage(exception.message());
|
||||
return;
|
||||
}
|
||||
if (throwable != null) {
|
||||
sender.sendMessage(Message.LINK_REQUEST_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (result) {
|
||||
case ALREADY_LINKED:
|
||||
sender.sendMessage(Message.ALREADY_LINKED);
|
||||
break;
|
||||
case NO_LINK_REQUESTED:
|
||||
sender.sendMessage(Message.NO_LINK_REQUESTED);
|
||||
break;
|
||||
case INVALID_CODE:
|
||||
sender.sendMessage(Message.INVALID_CODE);
|
||||
break;
|
||||
case REQUEST_EXPIRED:
|
||||
sender.sendMessage(Message.LINK_REQUEST_EXPIRED);
|
||||
break;
|
||||
case LINK_COMPLETED:
|
||||
sender.disconnect(Message.LINK_REQUEST_COMPLETED, targetName);
|
||||
break;
|
||||
default:
|
||||
sender.disconnect("Invalid account linking result");
|
||||
break;
|
||||
}
|
||||
sender.disconnect(Message.LINK_REQUEST_COMPLETED, targetName);
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -139,21 +147,16 @@ public final class LinkAccountCommand implements FloodgateCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
link.createLinkRequest(sender.uuid(), sender.username(), targetName)
|
||||
.whenComplete((result, throwable) -> {
|
||||
if (throwable != null || result == LinkRequestResult.UNKNOWN_ERROR) {
|
||||
String username = sender.username();
|
||||
String code = Utils.generateCode(6);
|
||||
|
||||
link.createLinkRequest(sender.uuid(), username, targetName, code)
|
||||
.whenComplete(($, throwable) -> {
|
||||
if (throwable != null) {
|
||||
sender.sendMessage(Message.LINK_REQUEST_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(result instanceof String)) {
|
||||
logger.error("Expected string code, got {}", result);
|
||||
sender.sendMessage(Message.LINK_REQUEST_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
sender.sendMessage(Message.LINK_REQUEST_CREATED,
|
||||
targetName, sender.username(), result);
|
||||
sender.sendMessage(Message.LINK_REQUEST_CREATED, targetName, username, code);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -32,10 +32,9 @@ import cloud.commandframework.context.CommandContext;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.floodgate.api.FloodgateApi;
|
||||
import org.geysermc.floodgate.api.link.PlayerLink;
|
||||
import org.geysermc.floodgate.core.command.util.Permission;
|
||||
import org.geysermc.floodgate.core.config.FloodgateConfig;
|
||||
import org.geysermc.floodgate.core.link.CommonPlayerLink;
|
||||
import org.geysermc.floodgate.core.link.GlobalPlayerLinking;
|
||||
import org.geysermc.floodgate.core.platform.command.FloodgateCommand;
|
||||
import org.geysermc.floodgate.core.platform.command.TranslatableMessage;
|
||||
@@ -45,7 +44,7 @@ import org.geysermc.floodgate.core.util.Constants;
|
||||
|
||||
@Singleton
|
||||
public final class UnlinkAccountCommand implements FloodgateCommand {
|
||||
@Inject FloodgateApi api;
|
||||
@Inject CommonPlayerLink link;
|
||||
|
||||
@Override
|
||||
public Command<UserAudience> buildCommand(CommandManager<UserAudience> commandManager) {
|
||||
@@ -61,11 +60,9 @@ public final class UnlinkAccountCommand implements FloodgateCommand {
|
||||
public void execute(CommandContext<UserAudience> context) {
|
||||
UserAudience sender = context.getSender();
|
||||
|
||||
PlayerLink link = api.getPlayerLink();
|
||||
|
||||
//todo make this less hacky
|
||||
if (link instanceof GlobalPlayerLinking) {
|
||||
if (((GlobalPlayerLinking) link).getDatabaseImpl() != null) {
|
||||
if (((GlobalPlayerLinking) link).getDatabase() != null) {
|
||||
sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE,
|
||||
Constants.LINK_INFO_URL);
|
||||
} else {
|
||||
@@ -75,12 +72,12 @@ public final class UnlinkAccountCommand implements FloodgateCommand {
|
||||
}
|
||||
}
|
||||
|
||||
if (!link.isEnabledAndAllowed()) {
|
||||
if (!link.isActive()) {
|
||||
sender.sendMessage(CommonCommandMessage.LINKING_DISABLED);
|
||||
return;
|
||||
}
|
||||
|
||||
link.isLinkedPlayer(sender.uuid())
|
||||
link.isLinked(sender.uuid())
|
||||
.whenComplete((linked, error) -> {
|
||||
if (error != null) {
|
||||
sender.sendMessage(CommonCommandMessage.IS_LINKED_ERROR);
|
||||
@@ -92,7 +89,7 @@ public final class UnlinkAccountCommand implements FloodgateCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
link.unlinkPlayer(sender.uuid())
|
||||
link.unlink(sender.uuid())
|
||||
.whenComplete((unused, error1) -> {
|
||||
if (error1 != null) {
|
||||
sender.sendMessage(Message.UNLINK_ERROR);
|
||||
|
||||
@@ -29,8 +29,7 @@ import cloud.commandframework.ArgumentDescription;
|
||||
import cloud.commandframework.Command;
|
||||
import cloud.commandframework.CommandManager;
|
||||
import cloud.commandframework.context.CommandContext;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.micronaut.http.client.exceptions.HttpClientResponseException;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.util.UUID;
|
||||
@@ -40,6 +39,8 @@ import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.core.command.util.Permission;
|
||||
import org.geysermc.floodgate.core.config.FloodgateConfig;
|
||||
import org.geysermc.floodgate.core.config.ProxyFloodgateConfig;
|
||||
import org.geysermc.floodgate.core.http.UnsuccessfulResponse;
|
||||
import org.geysermc.floodgate.core.http.xbox.XboxClient;
|
||||
import org.geysermc.floodgate.core.platform.command.CommandUtil;
|
||||
import org.geysermc.floodgate.core.platform.command.FloodgateCommand;
|
||||
import org.geysermc.floodgate.core.platform.command.TranslatableMessage;
|
||||
@@ -47,13 +48,12 @@ import org.geysermc.floodgate.core.platform.util.PlayerType;
|
||||
import org.geysermc.floodgate.core.player.UserAudience;
|
||||
import org.geysermc.floodgate.core.player.audience.ProfileAudience;
|
||||
import org.geysermc.floodgate.core.player.audience.ProfileAudienceArgument;
|
||||
import org.geysermc.floodgate.core.util.Constants;
|
||||
import org.geysermc.floodgate.core.util.HttpClient;
|
||||
|
||||
@Singleton
|
||||
public class WhitelistCommand implements FloodgateCommand {
|
||||
@Inject FloodgateApi api;
|
||||
@Inject FloodgateConfig config;
|
||||
@Inject HttpClient httpClient;
|
||||
@Inject XboxClient xboxClient;
|
||||
@Inject FloodgateLogger logger;
|
||||
|
||||
@Override
|
||||
@@ -86,7 +86,7 @@ public class WhitelistCommand implements FloodgateCommand {
|
||||
}
|
||||
|
||||
if (uuid != null) {
|
||||
if (!FloodgateApi.getInstance().isFloodgateId(uuid)) {
|
||||
if (!api.isFloodgateId(uuid)) {
|
||||
sender.sendMessage(Message.INVALID_USERNAME);
|
||||
return;
|
||||
}
|
||||
@@ -129,32 +129,34 @@ public class WhitelistCommand implements FloodgateCommand {
|
||||
final String strippedName = name;
|
||||
|
||||
// We need to get the UUID of the player if it's not manually specified
|
||||
httpClient.asyncGet(Constants.GET_XUID_URL + name)
|
||||
xboxClient.xuidByGamertag(name)
|
||||
.whenComplete((result, error) -> {
|
||||
if (error != null) {
|
||||
sender.sendMessage(Message.API_UNAVAILABLE);
|
||||
error.printStackTrace();
|
||||
if (!(error instanceof HttpClientResponseException exception)) {
|
||||
sender.sendMessage(Message.API_UNAVAILABLE);
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
sender.sendMessage(Message.UNEXPECTED_ERROR);
|
||||
|
||||
var response = exception.getResponse().getBody(UnsuccessfulResponse.class);
|
||||
var message =
|
||||
response.isPresent() ?
|
||||
response.get().message() :
|
||||
exception.getMessage();
|
||||
logger.error(
|
||||
"Got an error from requesting the xuid of a Bedrock player: {}",
|
||||
message
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject response = result.getResponse();
|
||||
|
||||
if (!result.isCodeOk()) {
|
||||
sender.sendMessage(Message.UNEXPECTED_ERROR);
|
||||
logger.error(
|
||||
"Got an error from requesting the xuid of a Bedrock player: {}",
|
||||
response.get("message").getAsString()
|
||||
);
|
||||
}
|
||||
|
||||
JsonElement xuidElement = response.get("xuid");
|
||||
|
||||
if (xuidElement == null) {
|
||||
Long xuid = result.xuid();
|
||||
if (xuid == null) {
|
||||
sender.sendMessage(Message.USER_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
String xuid = xuidElement.getAsString();
|
||||
CommandUtil commandUtil = context.get("CommandUtil");
|
||||
|
||||
try {
|
||||
|
||||
@@ -69,7 +69,7 @@ public class VersionSubcommand extends FloodgateSubCommand {
|
||||
COLOR_CHAR + "7You're currently on " + COLOR_CHAR + "b%s" +
|
||||
COLOR_CHAR + "7 (branch: " + COLOR_CHAR + "b%s" + COLOR_CHAR + "7)\n" +
|
||||
COLOR_CHAR + "eFetching latest build info...",
|
||||
Constants.VERSION, Constants.GIT_BRANCH
|
||||
Constants.FULL_VERSION, Constants.GIT_BRANCH
|
||||
));
|
||||
|
||||
String baseUrl = String.format(
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Floodgate
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.core.config;
|
||||
|
||||
import io.micronaut.context.annotation.BootstrapContextCompatible;
|
||||
import io.micronaut.context.env.BootstrapPropertySourceLocator;
|
||||
import io.micronaut.context.env.Environment;
|
||||
import io.micronaut.context.env.PropertySource;
|
||||
import io.micronaut.context.exceptions.ConfigurationException;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.geysermc.configutils.node.codec.strategy.object.ProxyEmbodimentStrategy;
|
||||
import org.geysermc.floodgate.core.database.loader.DatabaseType;
|
||||
import org.geysermc.floodgate.core.util.GlobalBeanCache;
|
||||
import org.geysermc.floodgate.isolation.library.LibraryManager;
|
||||
|
||||
@Singleton
|
||||
@BootstrapContextCompatible
|
||||
public class ConfigAsProperties implements BootstrapPropertySourceLocator {
|
||||
@Inject
|
||||
@Named("dataDirectory")
|
||||
Path dataDirectory;
|
||||
|
||||
@Inject FloodgateConfig config;
|
||||
|
||||
public ConfigAsProperties() {
|
||||
System.out.println("test1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<PropertySource> findPropertySources(Environment environment)
|
||||
throws ConfigurationException {
|
||||
loadDatabase();
|
||||
|
||||
var flat = flattern(Map.of("config", config));
|
||||
flat.forEach((key, value) -> System.out.println(key + ": " + value));
|
||||
|
||||
return Collections.singleton(PropertySource.of(flat));
|
||||
}
|
||||
|
||||
private void loadDatabase() {
|
||||
LibraryManager manager = GlobalBeanCache.get("libraryManager");
|
||||
|
||||
var databaseConfig = config.database();
|
||||
if (!databaseConfig.enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var type = DatabaseType.byId(databaseConfig.type());
|
||||
if (type == null) {
|
||||
throw new IllegalStateException(
|
||||
"Unable to find database type that matches: " + databaseConfig.type()
|
||||
);
|
||||
}
|
||||
|
||||
type.libraries().forEach(manager::addLibrary);
|
||||
manager.apply();
|
||||
}
|
||||
|
||||
private Map<String, Object> flattern(Map<String, Object> map) {
|
||||
return flattern0(Map.of("", map).get(""));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> flattern0(Map<String, Object> map) {
|
||||
var result = new HashMap<String, Object>();
|
||||
map.forEach((key, value) -> {
|
||||
if (Proxy.isProxyClass(value.getClass())) {
|
||||
value = new ProxyEmbodimentStrategy().disembody(value);
|
||||
}
|
||||
if (value instanceof Map<?,?>) {
|
||||
flattern0((Map<String, Object>) value).forEach(
|
||||
(key1, value1) -> result.put(key + "." + key1, value1)
|
||||
);
|
||||
return;
|
||||
}
|
||||
result.put(key, value);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,8 @@
|
||||
|
||||
package org.geysermc.floodgate.core.config;
|
||||
|
||||
import io.micronaut.context.ApplicationContext;
|
||||
import io.micronaut.context.annotation.Bean;
|
||||
import io.micronaut.context.annotation.BootstrapContextCompatible;
|
||||
import io.micronaut.context.annotation.Factory;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
@@ -39,49 +39,43 @@ import lombok.Getter;
|
||||
import org.geysermc.configutils.ConfigUtilities;
|
||||
import org.geysermc.configutils.file.codec.PathFileCodec;
|
||||
import org.geysermc.configutils.updater.change.Changes;
|
||||
import org.geysermc.floodgate.core.event.lifecycle.ConfigLoadedEvent;
|
||||
import org.geysermc.floodgate.core.scope.ProxyOnly;
|
||||
import org.geysermc.floodgate.core.scope.ServerOnly;
|
||||
import org.geysermc.floodgate.core.util.LanguageManager;
|
||||
import org.geysermc.floodgate.core.util.GlobalBeanCache;
|
||||
import org.geysermc.floodgate.crypto.FloodgateCipher;
|
||||
import org.geysermc.floodgate.crypto.KeyProducer;
|
||||
|
||||
@Factory
|
||||
@Getter
|
||||
@BootstrapContextCompatible
|
||||
public final class ConfigLoader {
|
||||
private final Path dataDirectory;
|
||||
private final KeyProducer keyProducer;
|
||||
private final FloodgateCipher cipher;
|
||||
private final LanguageManager languageManager;
|
||||
private final ApplicationContext context;
|
||||
|
||||
@Inject
|
||||
ConfigLoader(
|
||||
@Named("dataDirectory") Path dataDirectory,
|
||||
KeyProducer keyProducer,
|
||||
FloodgateCipher cipher,
|
||||
LanguageManager languageManager,
|
||||
ApplicationContext context
|
||||
FloodgateCipher cipher
|
||||
) {
|
||||
this.dataDirectory = dataDirectory;
|
||||
this.keyProducer = keyProducer;
|
||||
this.cipher = cipher;
|
||||
this.languageManager = languageManager;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ServerOnly
|
||||
@Singleton
|
||||
FloodgateConfig config() {
|
||||
return load(FloodgateConfig.class);
|
||||
return GlobalBeanCache.cacheIfAbsent("config", () -> load(FloodgateConfig.class));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ProxyOnly
|
||||
@Singleton
|
||||
ProxyFloodgateConfig proxyConfig() {
|
||||
return load(ProxyFloodgateConfig.class);
|
||||
return GlobalBeanCache.cacheIfAbsent("config", () -> load(ProxyFloodgateConfig.class));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -99,25 +93,20 @@ public final class ConfigLoader {
|
||||
.keyRenamed("playerLink.allowLinking", "playerLink.allowed"))
|
||||
.version(2, Changes.versionBuilder()
|
||||
.keyRenamed("playerLink.useGlobalLinking", "playerLink.enableGlobalLinking"))
|
||||
.version(3, Changes.versionBuilder()
|
||||
.keyRenamed("playerLink.type", "database.type"))
|
||||
.build())
|
||||
.definePlaceholder("metrics.uuid", UUID::randomUUID)
|
||||
.postInitializeCallbackArgument(this)
|
||||
.commentTranslator((key) -> languageManager.getLogString("floodgate.config." + key))
|
||||
.build();
|
||||
|
||||
T config;
|
||||
try {
|
||||
config = (T) utilities.executeOn(configClass);
|
||||
return (T) utilities.executeOn(configClass);
|
||||
} catch (Throwable throwable) {
|
||||
throw new RuntimeException(
|
||||
"Failed to load the config! Try to delete the config file if this error persists",
|
||||
throwable
|
||||
);
|
||||
}
|
||||
|
||||
context.getEventPublisher(ConfigLoadedEvent.class)
|
||||
.publishEvent(new ConfigLoadedEvent(config));
|
||||
return config;
|
||||
}
|
||||
|
||||
public void generateKey(Path keyPath) {
|
||||
|
||||
@@ -75,10 +75,6 @@ public interface FloodgateConfig extends GenericPostInitializeCallback<ConfigLoa
|
||||
usernamePrefix(".");
|
||||
}
|
||||
|
||||
// this happens before the config is serialized,
|
||||
// so the messages in the config will be translated
|
||||
loader.getLanguageManager().loadConfiguredDefaultLocale(this);
|
||||
|
||||
return CallbackResult.ok();
|
||||
}
|
||||
|
||||
@@ -102,6 +98,8 @@ public interface FloodgateConfig extends GenericPostInitializeCallback<ConfigLoa
|
||||
|
||||
DisconnectMessages disconnect();
|
||||
|
||||
DatabaseConfig database();
|
||||
|
||||
@Comment
|
||||
PlayerLinkConfig playerLink();
|
||||
|
||||
@@ -130,6 +128,17 @@ public interface FloodgateConfig extends GenericPostInitializeCallback<ConfigLoa
|
||||
String invalidArgumentsLength();
|
||||
}
|
||||
|
||||
@ConfigSection
|
||||
interface DatabaseConfig {
|
||||
@Comment
|
||||
@DefaultBoolean(true)
|
||||
boolean enabled();
|
||||
|
||||
@Comment
|
||||
@DefaultString("h2")
|
||||
String type();
|
||||
}
|
||||
|
||||
@ConfigSection
|
||||
interface PlayerLinkConfig {
|
||||
@Comment
|
||||
@@ -152,10 +161,6 @@ public interface FloodgateConfig extends GenericPostInitializeCallback<ConfigLoa
|
||||
@DefaultNumeric(300)
|
||||
long linkCodeTimeout();
|
||||
|
||||
@Comment
|
||||
@DefaultString("sqlite")
|
||||
String type();
|
||||
|
||||
@Comment
|
||||
@DefaultBoolean(true)
|
||||
boolean enableGlobalLinking();
|
||||
|
||||
@@ -31,14 +31,12 @@ public interface Properties {
|
||||
public static PropertySource defaults() {
|
||||
return PropertySource.of(
|
||||
"floodgate-properties",
|
||||
"micronaut.data.mongodb.create-collections", true,
|
||||
"mongodb.uri", "mongodb://root:root@localhost:27017/test?authSource=admin",
|
||||
"mongodb.uuid-representation", "standard",
|
||||
"datasources.default.url", "jdbc:h2:./test",
|
||||
"datasources.default.username", "sa",
|
||||
"datasources.default.password", "",
|
||||
"datasources.default.schema-generate", "create",
|
||||
"datasources.default.dialect", "H2"
|
||||
"datasources.default.driverClassName", "org.h2.Driver",
|
||||
"jpa.default.properties.hibernate.hbm2ddl.auto", "update",
|
||||
"jpa.default.properties.hibernate.show_sql", "true"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Floodgate
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.core.database;
|
||||
|
||||
import io.micronaut.context.annotation.Requires;
|
||||
import io.micronaut.data.annotation.Repository;
|
||||
import io.micronaut.data.repository.async.AsyncCrudRepository;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.geysermc.floodgate.core.database.entity.LinkRequest;
|
||||
|
||||
@Repository
|
||||
@Requires(property = "config.database.enabled", value = "true")
|
||||
public interface PendingLinkRepository extends AsyncCrudRepository<LinkRequest, UUID> {
|
||||
CompletableFuture<LinkRequest> findByJavaUsername(String javaUsername);
|
||||
}
|
||||
@@ -25,18 +25,31 @@
|
||||
|
||||
package org.geysermc.floodgate.core.database;
|
||||
|
||||
import io.micronaut.context.annotation.Requires;
|
||||
import io.micronaut.data.annotation.Repository;
|
||||
import io.micronaut.data.repository.CrudRepository;
|
||||
import io.micronaut.data.repository.async.AsyncCrudRepository;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import org.geysermc.floodgate.core.database.entity.LinkedPlayer;
|
||||
|
||||
@Repository
|
||||
public interface PlayerLinkRepository extends CrudRepository<LinkedPlayer, UUID> {
|
||||
@Requires(property = "config.database.enabled", value = "true")
|
||||
public interface PlayerLinkRepository extends AsyncCrudRepository<LinkedPlayer, UUID> {
|
||||
Optional<LinkedPlayer> findByBedrockId(@NotNull UUID bedrockId);
|
||||
|
||||
Optional<LinkedPlayer> findByJavaUniqueId(@NotNull UUID javaUniqueId);
|
||||
|
||||
Optional<LinkedPlayer> findByBedrockIdOrJavaUniqueId(
|
||||
UUID bedrockId,
|
||||
UUID javaUniqueId
|
||||
);
|
||||
|
||||
Boolean existsByBedrockId(@NotNull UUID bedrockId);
|
||||
|
||||
Boolean existsByJavaUniqueId(@NotNull UUID javaUniqueId);
|
||||
|
||||
Boolean existsByBedrockIdOrJavaUniqueId(UUID bedrockId, UUID javaUniqueId);
|
||||
|
||||
void deleteByBedrockIdOrJavaUniqueId(UUID bedrockId, UUID javaUniqueId);
|
||||
}
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Floodgate
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.core.database.config;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.micronaut.context.annotation.Requires;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
@Requires(env = "no")
|
||||
public class DatabaseConfigLoader {
|
||||
private Yaml yaml;
|
||||
|
||||
@Inject
|
||||
@Named("dataDirectory")
|
||||
Path dataDirectory;
|
||||
|
||||
@Inject
|
||||
@Named("database.name")
|
||||
String name;
|
||||
|
||||
@Inject
|
||||
@Named("database.classloader")
|
||||
ClassLoader classLoader;
|
||||
|
||||
@Inject
|
||||
@Named("database.init.data")
|
||||
JsonObject initData;
|
||||
|
||||
@Inject
|
||||
public void init() {
|
||||
// yaml = new Yaml(new CustomClassLoaderConstructor(classLoader));
|
||||
// yaml.setBeanAccess(BeanAccess.FIELD);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will load the config if it already exists or will create the config from the default
|
||||
* config file if it doesn't exist.
|
||||
*
|
||||
* @param configType the class to parse the config into
|
||||
* @param <T> type that extends the base DatabaseConfig class
|
||||
* @return the config if successful or null if not. It'll return null if the database didn't
|
||||
* provide a config or if there is no config present nor default config available or if an error
|
||||
* occurred while executing this method.
|
||||
*/
|
||||
public <T extends DatabaseConfig> T loadAs(Class<T> configType) {
|
||||
if (!initData.has("config")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String configFile = initData.get("config").getAsString();
|
||||
Path configPath = dataDirectory.resolve(name).resolve(configFile);
|
||||
|
||||
// return the existing config
|
||||
if (Files.exists(configPath)) {
|
||||
try (BufferedReader reader = Files.newBufferedReader(configPath)) {
|
||||
return yaml.loadAs(reader, configType);
|
||||
} catch (IOException exception) {
|
||||
exception.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// make directories
|
||||
try {
|
||||
Files.createDirectories(configPath.getParent());
|
||||
} catch (IOException exception) {
|
||||
exception.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
||||
// load default config resource
|
||||
try (InputStream configStream = classLoader.getResourceAsStream(configFile)) {
|
||||
if (configStream == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// copy resource and load config
|
||||
if (!configStream.markSupported()) {
|
||||
Files.copy(configStream, configPath);
|
||||
try (InputStream configStream1 = classLoader.getResourceAsStream(configFile)) {
|
||||
return yaml.loadAs(configStream1, configType);
|
||||
}
|
||||
}
|
||||
|
||||
configStream.mark(Integer.MAX_VALUE);
|
||||
Files.copy(configStream, configPath);
|
||||
configStream.reset();
|
||||
return yaml.loadAs(configStream, configType);
|
||||
} catch (IOException exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Floodgate
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.core.database.entity;
|
||||
|
||||
import io.micronaut.core.annotation.AccessorsStyle;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
|
||||
@Entity
|
||||
@AccessorsStyle(readPrefixes = "", writePrefixes = "")
|
||||
public class LinkRequest {
|
||||
@Id
|
||||
private UUID javaUniqueId;
|
||||
private String javaUsername;
|
||||
private String bedrockUsername;
|
||||
private String linkCode;
|
||||
private long requestTime = Instant.now().getEpochSecond();
|
||||
|
||||
public UUID javaUniqueId() {
|
||||
return javaUniqueId;
|
||||
}
|
||||
|
||||
public LinkRequest javaUniqueId(UUID javaUniqueId) {
|
||||
this.javaUniqueId = javaUniqueId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String javaUsername() {
|
||||
return javaUsername;
|
||||
}
|
||||
|
||||
public LinkRequest javaUsername(String javaUsername) {
|
||||
this.javaUsername = javaUsername;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String bedrockUsername() {
|
||||
return bedrockUsername;
|
||||
}
|
||||
|
||||
public LinkRequest bedrockUsername(String bedrockUsername) {
|
||||
this.bedrockUsername = bedrockUsername;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String linkCode() {
|
||||
return linkCode;
|
||||
}
|
||||
|
||||
public LinkRequest linkCode(String linkCode) {
|
||||
this.linkCode = linkCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long requestTime() {
|
||||
return requestTime;
|
||||
}
|
||||
|
||||
public LinkRequest requestTime(long requestTime) {
|
||||
this.requestTime = requestTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isExpired(long linkTimeout) {
|
||||
long timePassed = Instant.now().getEpochSecond() - requestTime;
|
||||
return timePassed > linkTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
LinkRequest that = (LinkRequest) o;
|
||||
return Objects.equals(javaUniqueId, that.javaUniqueId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(javaUniqueId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LinkRequest{" +
|
||||
"javaUniqueId=" + javaUniqueId +
|
||||
", javaUsername='" + javaUsername + '\'' +
|
||||
", bedrockUsername='" + bedrockUsername + '\'' +
|
||||
", linkCode='" + linkCode + '\'' +
|
||||
", requestTime=" + requestTime +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -25,19 +25,21 @@
|
||||
|
||||
package org.geysermc.floodgate.core.database.entity;
|
||||
|
||||
import io.micronaut.core.annotation.AccessorsStyle;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
|
||||
@Entity
|
||||
@AccessorsStyle(readPrefixes = "", writePrefixes = "")
|
||||
public class LinkedPlayer {
|
||||
@Id
|
||||
private UUID bedrockId;
|
||||
private UUID javaUniqueId;
|
||||
private String javaUsername;
|
||||
|
||||
public UUID getBedrockId() {
|
||||
public UUID bedrockId() {
|
||||
return bedrockId;
|
||||
}
|
||||
|
||||
@@ -46,7 +48,7 @@ public class LinkedPlayer {
|
||||
return this;
|
||||
}
|
||||
|
||||
public UUID getJavaUniqueId() {
|
||||
public UUID javaUniqueId() {
|
||||
return javaUniqueId;
|
||||
}
|
||||
|
||||
@@ -55,7 +57,7 @@ public class LinkedPlayer {
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getJavaUsername() {
|
||||
public String javaUsername() {
|
||||
return javaUsername;
|
||||
}
|
||||
|
||||
@@ -74,13 +76,12 @@ public class LinkedPlayer {
|
||||
}
|
||||
var that = (LinkedPlayer) obj;
|
||||
return Objects.equals(this.bedrockId, that.bedrockId) &&
|
||||
Objects.equals(this.javaUniqueId, that.javaUniqueId) &&
|
||||
Objects.equals(this.javaUsername, that.javaUsername);
|
||||
Objects.equals(this.javaUniqueId, that.javaUniqueId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(bedrockId, javaUniqueId, javaUsername);
|
||||
return Objects.hash(bedrockId, javaUniqueId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Floodgate
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.core.database.loader;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.floodgate.core.util.Constants;
|
||||
import org.geysermc.floodgate.isolation.library.Library;
|
||||
import org.geysermc.floodgate.isolation.library.Repository;
|
||||
|
||||
public enum DatabaseType {
|
||||
H2(
|
||||
DriverCategory.HIBERNATE,
|
||||
databaseModule(),
|
||||
library("h2", Repository.MAVEN_CENTRAL, "com.h2database", "h2", "1.4.200")
|
||||
);
|
||||
|
||||
private static final DatabaseType[] VALUES = values();
|
||||
private final DriverCategory category;
|
||||
private final Set<Library> libraries;
|
||||
|
||||
DatabaseType(@NonNull DriverCategory category, @NonNull Library... libraries) {
|
||||
this.category = Objects.requireNonNull(category);
|
||||
this.libraries = Set.of(Objects.requireNonNull(libraries));
|
||||
}
|
||||
|
||||
static Library databaseModule() {
|
||||
return library(
|
||||
"database",
|
||||
Repository.OPEN_COLLAB,
|
||||
"org.geysermc.floodgate",
|
||||
"database",
|
||||
Constants.VERSION
|
||||
);
|
||||
}
|
||||
|
||||
static Library library(
|
||||
String id,
|
||||
Repository repo,
|
||||
String groupId,
|
||||
String artifactId,
|
||||
String version
|
||||
) {
|
||||
return Library.builder()
|
||||
.id(id)
|
||||
.repository(repo)
|
||||
.groupId(groupId)
|
||||
.artifactId(artifactId)
|
||||
.version(version)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static DatabaseType byId(String id) {
|
||||
id = id.toUpperCase(Locale.ROOT);
|
||||
for (DatabaseType value : VALUES) {
|
||||
if (value.name().equals(id)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public DriverCategory category() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public Set<Library> libraries() {
|
||||
return libraries;
|
||||
}
|
||||
}
|
||||
@@ -23,10 +23,8 @@
|
||||
* @link https://github.com/GeyserMC/Floodgate
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.core.database.config;
|
||||
package org.geysermc.floodgate.core.database.loader;
|
||||
|
||||
/**
|
||||
* Base class for every database related configuration.
|
||||
*/
|
||||
public interface DatabaseConfig {
|
||||
public enum DriverCategory {
|
||||
HIBERNATE
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Floodgate
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.core.http.link;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.UUID;
|
||||
import org.geysermc.floodgate.core.util.Utils;
|
||||
|
||||
public record LinkedPlayer(
|
||||
@JsonProperty("bedrock_id")
|
||||
long xuid,
|
||||
String gamertag,
|
||||
@JsonProperty("java_id")
|
||||
UUID uuid,
|
||||
@JsonProperty("java_name")
|
||||
String username
|
||||
) {
|
||||
public boolean isLinked() {
|
||||
// everything will be null when the player is not linked, since we return an empty object.
|
||||
// but it's sufficient to check if one of them is null
|
||||
return uuid != null;
|
||||
}
|
||||
|
||||
public org.geysermc.floodgate.core.database.entity.LinkedPlayer toDatabase() {
|
||||
if (!isLinked()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new org.geysermc.floodgate.core.database.entity.LinkedPlayer()
|
||||
.bedrockId(Utils.getJavaUuid(xuid))
|
||||
.javaUniqueId(uuid)
|
||||
.javaUsername(username);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Floodgate
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.core.http.xbox;
|
||||
|
||||
public record GetGamertagResult(String gamertag) {
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Floodgate
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.core.http.xbox;
|
||||
|
||||
public record GetXuidResult(Long xuid) {
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Floodgate
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.core.http.xbox;
|
||||
|
||||
import static io.micronaut.http.HttpHeaders.ACCEPT;
|
||||
import static io.micronaut.http.HttpHeaders.USER_AGENT;
|
||||
|
||||
import io.micronaut.http.annotation.Get;
|
||||
import io.micronaut.http.annotation.Header;
|
||||
import io.micronaut.http.client.annotation.Client;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
@Client("${http.baseUrl}/v2/xbox")
|
||||
@Header(name = USER_AGENT, value = "${http.userAgent}")
|
||||
@Header(name = ACCEPT, value = "application/json")
|
||||
public interface XboxClient {
|
||||
@Get("/xuid/{gamertag}")
|
||||
CompletableFuture<GetXuidResult> xuidByGamertag(
|
||||
@NotNull @Size(min = 1, max = 16) String gamertag
|
||||
);
|
||||
|
||||
@Get("/gamertag/{xuid}")
|
||||
CompletableFuture<GetGamertagResult> gamertagByXuid(long xuid);
|
||||
}
|
||||
@@ -25,30 +25,20 @@
|
||||
|
||||
package org.geysermc.floodgate.core.link;
|
||||
|
||||
import io.micronaut.context.ApplicationContext;
|
||||
import io.micronaut.context.annotation.Requires;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.floodgate.api.FloodgateApi;
|
||||
import org.geysermc.floodgate.api.link.LinkRequest;
|
||||
import org.geysermc.floodgate.api.link.PlayerLink;
|
||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
import org.geysermc.floodgate.core.config.FloodgateConfig;
|
||||
import org.geysermc.floodgate.core.database.config.DatabaseConfig;
|
||||
import org.geysermc.floodgate.core.database.config.DatabaseConfigLoader;
|
||||
|
||||
//@Listener
|
||||
@Requires(env = "no")
|
||||
public abstract class CommonPlayerLink implements PlayerLink {
|
||||
@Getter(AccessLevel.PROTECTED)
|
||||
private final ExecutorService executorService = Executors.newCachedThreadPool();
|
||||
import org.geysermc.floodgate.core.database.entity.LinkRequest;
|
||||
import org.geysermc.floodgate.core.database.entity.LinkedPlayer;
|
||||
|
||||
public abstract class CommonPlayerLink {
|
||||
@Getter private boolean enabled;
|
||||
@Getter private boolean allowLinking;
|
||||
@Getter private long verifyLinkTimeout;
|
||||
@@ -61,9 +51,6 @@ public abstract class CommonPlayerLink implements PlayerLink {
|
||||
@Getter(AccessLevel.PROTECTED)
|
||||
FloodgateApi api;
|
||||
|
||||
@Inject
|
||||
ApplicationContext context;
|
||||
|
||||
@Inject
|
||||
void init(FloodgateConfig config) {
|
||||
FloodgateConfig.PlayerLinkConfig linkConfig = config.playerLink();
|
||||
@@ -72,37 +59,45 @@ public abstract class CommonPlayerLink implements PlayerLink {
|
||||
verifyLinkTimeout = linkConfig.linkCodeTimeout();
|
||||
}
|
||||
|
||||
public String createCode() {
|
||||
return String.format("%04d", new Random().nextInt(10000));
|
||||
}
|
||||
|
||||
public boolean isRequestedPlayer(LinkRequest request, UUID bedrockId) {
|
||||
return request.isRequestedPlayer(api.getPlayer(bedrockId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the config present in init.json and turn it into the given config class. This method will
|
||||
* automatically copy and save the default config if the config doesn't exist.
|
||||
* Checks if the given FloodgatePlayer is the player requested in this LinkRequest. This method
|
||||
* will check both the real bedrock username {@link FloodgatePlayer#getUsername()} and the
|
||||
* edited username {@link FloodgatePlayer#getJavaUsername()} and returns true if one of the two
|
||||
* matches.
|
||||
*
|
||||
* @param configClass the class to convert the config to
|
||||
* @param <T> the config type
|
||||
* @return the loaded config or null if something went wrong.
|
||||
* @see DatabaseConfigLoader#loadAs(Class)
|
||||
* @return true if the given player is the player requested
|
||||
*/
|
||||
public <T extends DatabaseConfig> T getConfig(Class<T> configClass) {
|
||||
// this method is not intended to be used more than once. It'll make a new instance of
|
||||
// DatabaseConfigLoader and DatabaseConfig every time you run this method.
|
||||
return context.getBean(DatabaseConfigLoader.class).loadAs(configClass);
|
||||
public boolean isRequestedPlayer(LinkRequest request, UUID bedrockId) {
|
||||
// Java starts the process, Bedrock finishes it. So player can't be null
|
||||
var player = api.getPlayer(bedrockId);
|
||||
return request.bedrockUsername().equals(player.getUsername()) ||
|
||||
request.bedrockUsername().equals(player.getJavaUsername());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return context.get("database.name", String.class).get();
|
||||
}
|
||||
public abstract CompletableFuture<LinkedPlayer> addLink(
|
||||
@NonNull UUID javaUniqueId,
|
||||
@NonNull String javaUsername,
|
||||
@NonNull UUID bedrockId
|
||||
);
|
||||
|
||||
@Override
|
||||
@PreDestroy
|
||||
public void stop() {
|
||||
executorService.shutdown();
|
||||
public abstract CompletableFuture<LinkedPlayer> fetchLink(@NonNull UUID uuid);
|
||||
|
||||
public abstract CompletableFuture<Boolean> isLinked(@NonNull UUID uuid);
|
||||
|
||||
public abstract CompletableFuture<Void> unlink(@NonNull UUID uuid);
|
||||
|
||||
public abstract CompletableFuture<LinkRequest> createLinkRequest(
|
||||
@NonNull UUID javaUniqueId,
|
||||
@NonNull String javaUsername,
|
||||
@NonNull String bedrockUsername,
|
||||
@NonNull String code
|
||||
);
|
||||
|
||||
public abstract CompletableFuture<LinkRequest> linkRequest(@NonNull String javaUsername);
|
||||
|
||||
public abstract CompletableFuture<Void> invalidateLinkRequest(@NonNull LinkRequest request);
|
||||
|
||||
public boolean isActive() {
|
||||
return enabled && allowLinking;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,76 +25,18 @@
|
||||
|
||||
package org.geysermc.floodgate.core.link;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.floodgate.api.FloodgateApi;
|
||||
import org.geysermc.floodgate.api.link.LinkRequestResult;
|
||||
import org.geysermc.floodgate.api.link.PlayerLink;
|
||||
import org.geysermc.floodgate.core.util.Utils;
|
||||
import org.geysermc.floodgate.util.LinkedPlayer;
|
||||
import org.geysermc.floodgate.core.database.entity.LinkRequest;
|
||||
import org.geysermc.floodgate.core.database.entity.LinkedPlayer;
|
||||
|
||||
/**
|
||||
* Simple class used when PlayerLinking is disabled. This class has been made because Floodgate
|
||||
* doesn't have a default PlayerLink implementation anymore and {@link FloodgateApi#getPlayerLink()}
|
||||
* returning null} is also not an option.
|
||||
* Simple class used when PlayerLinking is disabled
|
||||
*/
|
||||
final class DisabledPlayerLink implements PlayerLink {
|
||||
@Override
|
||||
public void load() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public CompletableFuture<LinkedPlayer> getLinkedPlayer(@NonNull UUID bedrockId) {
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public CompletableFuture<Boolean> isLinkedPlayer(@NonNull UUID bedrockId) {
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public CompletableFuture<Void> linkPlayer(
|
||||
@NonNull UUID bedrockId,
|
||||
@NonNull UUID javaId,
|
||||
@NonNull String username) {
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public CompletableFuture<Void> unlinkPlayer(@NonNull UUID javaId) {
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public CompletableFuture<?> createLinkRequest(
|
||||
@NonNull UUID javaId,
|
||||
@NonNull String javaUsername,
|
||||
@NonNull String bedrockUsername) {
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public CompletableFuture<LinkRequestResult> verifyLinkRequest(
|
||||
@NonNull UUID bedrockId,
|
||||
@NonNull String javaUsername,
|
||||
@NonNull String bedrockUsername,
|
||||
@NonNull String code) {
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Singleton
|
||||
final class DisabledPlayerLink extends CommonPlayerLink {
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return false;
|
||||
@@ -111,11 +53,52 @@ final class DisabledPlayerLink implements PlayerLink {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
public CompletableFuture<LinkedPlayer> addLink(
|
||||
@NonNull UUID javaUniqueId,
|
||||
@NonNull String javaUsername,
|
||||
@NonNull UUID bedrockId
|
||||
) {
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LinkedPlayer> fetchLink(@NonNull UUID uuid) {
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> isLinked(@NonNull UUID uuid) {
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> unlink(@NonNull UUID uuid) {
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LinkRequest> createLinkRequest(
|
||||
@NonNull UUID javaUniqueId,
|
||||
@NonNull String javaUsername,
|
||||
@NonNull String bedrockUsername,
|
||||
@NonNull String code
|
||||
) {
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LinkRequest> linkRequest(@NonNull String javaUsername) {
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> invalidateLinkRequest(@NonNull LinkRequest request) {
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
private <U> CompletableFuture<U> failedFuture() {
|
||||
return Utils.failedFuture(new IllegalStateException(
|
||||
"Cannot perform this action when PlayerLinking is disabled"));
|
||||
return CompletableFuture.failedFuture(new IllegalStateException(
|
||||
"Cannot perform this action when PlayerLinking is disabled"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,66 +25,43 @@
|
||||
|
||||
package org.geysermc.floodgate.core.link;
|
||||
|
||||
import static org.geysermc.floodgate.core.util.Constants.GET_BEDROCK_LINK;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.micronaut.context.BeanProvider;
|
||||
import io.micronaut.context.annotation.Primary;
|
||||
import io.micronaut.context.annotation.Requires;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import lombok.Getter;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.floodgate.api.link.LinkRequestResult;
|
||||
import org.geysermc.floodgate.api.link.PlayerLink;
|
||||
import org.geysermc.floodgate.core.util.HttpClient;
|
||||
import org.geysermc.floodgate.core.util.HttpClient.DefaultHttpResponse;
|
||||
import org.geysermc.floodgate.core.util.Utils;
|
||||
import org.geysermc.floodgate.util.LinkedPlayer;
|
||||
import org.geysermc.floodgate.core.database.entity.LinkRequest;
|
||||
import org.geysermc.floodgate.core.database.entity.LinkedPlayer;
|
||||
import org.geysermc.floodgate.core.http.link.GlobalLinkClient;
|
||||
|
||||
@Requires(property = "config.link.enableGlobalLinking", value = "true")
|
||||
@Primary
|
||||
@Singleton
|
||||
@Getter
|
||||
public class GlobalPlayerLinking extends CommonPlayerLink {
|
||||
@Inject HttpClient httpClient;
|
||||
@Inject GlobalLinkClient linkClient;
|
||||
|
||||
private PlayerLink databaseImpl;
|
||||
private CommonPlayerLink database;
|
||||
|
||||
public void setDatabaseImpl(PlayerLink databaseImpl) {
|
||||
if (this.databaseImpl == null) {
|
||||
this.databaseImpl = databaseImpl;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
if (databaseImpl != null) {
|
||||
databaseImpl.load();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
if (databaseImpl != null) {
|
||||
return databaseImpl.getName();
|
||||
}
|
||||
// Global Linking is integrated
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
if (databaseImpl != null) {
|
||||
databaseImpl.stop();
|
||||
}
|
||||
@Inject
|
||||
void init(@Named("localLinking") BeanProvider<CommonPlayerLink> databaseImpl) {
|
||||
this.database = databaseImpl.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public CompletableFuture<LinkedPlayer> getLinkedPlayer(@NonNull UUID bedrockId) {
|
||||
if (databaseImpl == null) {
|
||||
public CompletableFuture<LinkedPlayer> fetchLink(@NonNull UUID bedrockId) {
|
||||
if (database == null) {
|
||||
return getLinkedPlayer0(bedrockId);
|
||||
}
|
||||
|
||||
return databaseImpl.getLinkedPlayer(bedrockId).thenComposeAsync(result -> {
|
||||
return database.fetchLink(bedrockId).thenComposeAsync(result -> {
|
||||
if (result != null) {
|
||||
return CompletableFuture.completedFuture(result);
|
||||
}
|
||||
@@ -94,47 +71,18 @@ public class GlobalPlayerLinking extends CommonPlayerLink {
|
||||
|
||||
@NonNull
|
||||
private CompletableFuture<LinkedPlayer> getLinkedPlayer0(@NonNull UUID bedrockId) {
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
DefaultHttpResponse response =
|
||||
httpClient.get(GET_BEDROCK_LINK + bedrockId.getLeastSignificantBits());
|
||||
|
||||
// either the global api is down or it failed to return link
|
||||
if (!response.isCodeOk()) {
|
||||
if (response.getResponse() != null) {
|
||||
getLogger().error(
|
||||
"Failed to request link for {}: {}",
|
||||
bedrockId.getLeastSignificantBits(),
|
||||
response.getResponse().get("message").getAsString()
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
JsonObject data = response.getResponse();
|
||||
|
||||
JsonElement javaName = data.get("java_name");
|
||||
// javaName will be null when the player isn't linked
|
||||
if (javaName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return LinkedPlayer.of(
|
||||
javaName.getAsString(),
|
||||
UUID.fromString(data.get("java_id").getAsString()),
|
||||
Utils.getJavaUuid(data.get("bedrock_id").getAsLong()));
|
||||
},
|
||||
getExecutorService());
|
||||
return linkClient.bedrockLink(bedrockId.getLeastSignificantBits())
|
||||
.thenApply(org.geysermc.floodgate.core.http.link.LinkedPlayer::toDatabase);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public CompletableFuture<Boolean> isLinkedPlayer(@NonNull UUID bedrockId) {
|
||||
if (databaseImpl == null) {
|
||||
public CompletableFuture<Boolean> isLinked(@NonNull UUID bedrockId) {
|
||||
if (database == null) {
|
||||
return isLinkedPlayer0(bedrockId);
|
||||
}
|
||||
|
||||
return databaseImpl.isLinkedPlayer(bedrockId).thenComposeAsync(result -> {
|
||||
return database.isLinked(bedrockId).thenComposeAsync(result -> {
|
||||
if (result != null) {
|
||||
return CompletableFuture.completedFuture(result);
|
||||
}
|
||||
@@ -144,23 +92,7 @@ public class GlobalPlayerLinking extends CommonPlayerLink {
|
||||
|
||||
@NonNull
|
||||
private CompletableFuture<Boolean> isLinkedPlayer0(@NonNull UUID bedrockId) {
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
DefaultHttpResponse response =
|
||||
httpClient.get(GET_BEDROCK_LINK + bedrockId.getLeastSignificantBits());
|
||||
|
||||
if (!response.isCodeOk()) {
|
||||
getLogger().error(
|
||||
"Failed to request link for {}: {}",
|
||||
bedrockId.getLeastSignificantBits(),
|
||||
response.getResponse().get("message").getAsString());
|
||||
return false;
|
||||
}
|
||||
|
||||
// no link if data is empty, otherwise the player is linked
|
||||
return response.getResponse().entrySet().size() != 0;
|
||||
},
|
||||
getExecutorService());
|
||||
return getLinkedPlayer0(bedrockId).thenApply(Objects::nonNull);
|
||||
}
|
||||
|
||||
// player linking and unlinking now goes through the global player linking server.
|
||||
@@ -168,57 +100,64 @@ public class GlobalPlayerLinking extends CommonPlayerLink {
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public CompletableFuture<Void> linkPlayer(
|
||||
@NonNull UUID bedrockId,
|
||||
@NonNull UUID javaId,
|
||||
@NonNull String username) {
|
||||
if (databaseImpl != null) {
|
||||
return databaseImpl.linkPlayer(bedrockId, javaId, username);
|
||||
}
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public CompletableFuture<Void> unlinkPlayer(@NonNull UUID javaId) {
|
||||
if (databaseImpl != null) {
|
||||
return databaseImpl.unlinkPlayer(javaId);
|
||||
}
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public CompletableFuture<?> createLinkRequest(
|
||||
@NonNull UUID javaId,
|
||||
public CompletableFuture<LinkedPlayer> addLink(
|
||||
@NonNull UUID javaUniqueId,
|
||||
@NonNull String javaUsername,
|
||||
@NonNull String bedrockUsername) {
|
||||
if (databaseImpl != null) {
|
||||
return databaseImpl.createLinkRequest(javaId, javaUsername, bedrockUsername);
|
||||
@NonNull UUID bedrockId
|
||||
) {
|
||||
if (database != null) {
|
||||
return database.addLink(javaUniqueId, javaUsername, bedrockId);
|
||||
}
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public CompletableFuture<LinkRequestResult> verifyLinkRequest(
|
||||
@NonNull UUID bedrockId,
|
||||
public CompletableFuture<Void> unlink(@NonNull UUID javaUniqueId) {
|
||||
if (database != null) {
|
||||
return database.unlink(javaUniqueId);
|
||||
}
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public CompletableFuture<LinkRequest> createLinkRequest(
|
||||
@NonNull UUID javaUniqueId,
|
||||
@NonNull String javaUsername,
|
||||
@NonNull String bedrockUsername,
|
||||
@NonNull String code) {
|
||||
if (databaseImpl != null) {
|
||||
return databaseImpl.verifyLinkRequest(bedrockId, javaUsername, bedrockUsername, code);
|
||||
@NonNull String code
|
||||
) {
|
||||
if (database != null) {
|
||||
return database.createLinkRequest(javaUniqueId, javaUsername, bedrockUsername, code);
|
||||
}
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledAndAllowed() {
|
||||
return databaseImpl != null && databaseImpl.isEnabledAndAllowed();
|
||||
public CompletableFuture<LinkRequest> linkRequest(@NonNull String javaUsername) {
|
||||
if (database != null) {
|
||||
return database.linkRequest(javaUsername);
|
||||
}
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> invalidateLinkRequest(@NonNull LinkRequest request) {
|
||||
if (database != null) {
|
||||
return database.invalidateLinkRequest(request);
|
||||
}
|
||||
return failedFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return database != null && database.isActive();
|
||||
}
|
||||
|
||||
private <U> CompletableFuture<U> failedFuture() {
|
||||
return Utils.failedFuture(new IllegalStateException(
|
||||
"Cannot perform this action when Global Linking is enabled"));
|
||||
return CompletableFuture.failedFuture(new IllegalStateException(
|
||||
"Cannot perform this action when Global Linking is enabled"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Floodgate
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.core.link;
|
||||
|
||||
import org.geysermc.floodgate.core.command.LinkAccountCommand.Message;
|
||||
|
||||
public class LinkVerificationException extends RuntimeException {
|
||||
public static final LinkVerificationException NO_LINK_REQUESTED =
|
||||
new LinkVerificationException(Message.NO_LINK_REQUESTED);
|
||||
public static final LinkVerificationException INVALID_CODE =
|
||||
new LinkVerificationException(Message.INVALID_CODE);
|
||||
public static final LinkVerificationException LINK_REQUEST_EXPIRED =
|
||||
new LinkVerificationException(Message.LINK_REQUEST_EXPIRED);
|
||||
|
||||
private final Message message;
|
||||
|
||||
private LinkVerificationException(Message message) {
|
||||
super(null, null, true, false);
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Message message() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Floodgate
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.core.link;
|
||||
|
||||
import io.micronaut.context.annotation.Replaces;
|
||||
import io.micronaut.context.annotation.Requires;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.floodgate.core.database.PendingLinkRepository;
|
||||
import org.geysermc.floodgate.core.database.PlayerLinkRepository;
|
||||
import org.geysermc.floodgate.core.database.entity.LinkRequest;
|
||||
import org.geysermc.floodgate.core.database.entity.LinkedPlayer;
|
||||
|
||||
@Requires(property = "config.playerLink.enableOwnLinking", value = "true")
|
||||
@Replaces(DisabledPlayerLink.class)
|
||||
@Named("localLinking")
|
||||
@Singleton
|
||||
public class PlayerLinkHibernate extends CommonPlayerLink {
|
||||
@Inject PlayerLinkRepository linkRepository;
|
||||
@Inject PendingLinkRepository pendingLinkRepository;
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LinkedPlayer> addLink(
|
||||
@NonNull UUID javaUniqueId,
|
||||
@NonNull String javaUsername,
|
||||
@NonNull UUID bedrockId
|
||||
) {
|
||||
return linkRepository.save(
|
||||
new LinkedPlayer()
|
||||
.javaUniqueId(javaUniqueId)
|
||||
.javaUsername(javaUsername)
|
||||
.bedrockId(bedrockId)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LinkedPlayer> fetchLink(@NonNull UUID uuid) {
|
||||
return CompletableFuture.supplyAsync(() ->
|
||||
linkRepository.findByBedrockIdOrJavaUniqueId(uuid, uuid).orElse(null)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> isLinked(@NonNull UUID uuid) {
|
||||
return CompletableFuture.supplyAsync(() ->
|
||||
linkRepository.existsByBedrockIdOrJavaUniqueId(uuid, uuid)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> unlink(@NonNull UUID uuid) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
linkRepository.deleteByBedrockIdOrJavaUniqueId(uuid, uuid);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LinkRequest> createLinkRequest(
|
||||
@NonNull UUID javaUniqueId,
|
||||
@NonNull String javaUsername,
|
||||
@NonNull String bedrockUsername,
|
||||
@NonNull String code
|
||||
) {
|
||||
return pendingLinkRepository.save(
|
||||
new LinkRequest()
|
||||
.javaUniqueId(javaUniqueId)
|
||||
.javaUsername(javaUsername)
|
||||
.bedrockUsername(bedrockUsername)
|
||||
.linkCode(code)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LinkRequest> linkRequest(@NonNull String javaUsername) {
|
||||
return pendingLinkRepository.findByJavaUsername(javaUsername);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> invalidateLinkRequest(@NonNull LinkRequest request) {
|
||||
return pendingLinkRepository.delete(request);
|
||||
}
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Floodgate
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.core.link;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.micronaut.context.ApplicationContext;
|
||||
import io.micronaut.context.annotation.Bean;
|
||||
import io.micronaut.context.annotation.Factory;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.geysermc.event.Listener;
|
||||
import org.geysermc.floodgate.api.link.PlayerLink;
|
||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.core.config.FloodgateConfig;
|
||||
import org.geysermc.floodgate.core.config.FloodgateConfig.PlayerLinkConfig;
|
||||
import org.geysermc.floodgate.core.util.Constants;
|
||||
import org.geysermc.floodgate.core.util.Utils;
|
||||
|
||||
@Listener
|
||||
@Factory
|
||||
@SuppressWarnings("unchecked")
|
||||
public final class PlayerLinkHolder {
|
||||
private final ApplicationContext currentContext;
|
||||
|
||||
private URLClassLoader classLoader;
|
||||
private ApplicationContext childContext;
|
||||
private PlayerLink instance;
|
||||
|
||||
@Inject
|
||||
PlayerLinkHolder(ApplicationContext context) {
|
||||
this.currentContext = context;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Singleton
|
||||
PlayerLink load(
|
||||
FloodgateConfig config,
|
||||
FloodgateLogger logger,
|
||||
@Named("dataDirectory") Path dataDirectory
|
||||
) {
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
if (config == null) {
|
||||
throw new IllegalStateException("Config cannot be null!");
|
||||
}
|
||||
|
||||
PlayerLinkConfig linkConfig = config.playerLink();
|
||||
if (!linkConfig.enabled()) {
|
||||
return new DisabledPlayerLink();
|
||||
}
|
||||
|
||||
List<Path> files;
|
||||
try (Stream<Path> list = Files.list(dataDirectory)) {
|
||||
files = list
|
||||
.filter(path -> Files.isRegularFile(path) && path.toString().endsWith(".jar"))
|
||||
.collect(Collectors.toList());
|
||||
} catch (IOException exception) {
|
||||
logger.error("Failed to list possible database implementations", exception);
|
||||
return new DisabledPlayerLink();
|
||||
}
|
||||
|
||||
// we can skip the rest if global linking is enabled and no database implementations has
|
||||
// been found, or when global linking is enabled and own player linking is disabled.
|
||||
if (linkConfig.enableGlobalLinking() &&
|
||||
(files.isEmpty() || !linkConfig.enableOwnLinking())) {
|
||||
return currentContext.getBean(GlobalPlayerLinking.class);
|
||||
}
|
||||
|
||||
if (files.isEmpty()) {
|
||||
logger.error("Failed to find a database implementation");
|
||||
return new DisabledPlayerLink();
|
||||
}
|
||||
|
||||
Path implementationPath = files.get(0);
|
||||
final String databaseName;
|
||||
|
||||
// We only want to load one database implementation
|
||||
if (files.size() > 1) {
|
||||
boolean found = false;
|
||||
databaseName = linkConfig.type();
|
||||
|
||||
String expectedName = "floodgate-" + databaseName + "-database.jar";
|
||||
for (Path path : files) {
|
||||
if (expectedName.equalsIgnoreCase(path.getFileName().toString())) {
|
||||
implementationPath = path;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
logger.error(
|
||||
"Failed to find an implementation for type: {}", linkConfig.type()
|
||||
);
|
||||
return new DisabledPlayerLink();
|
||||
}
|
||||
} else {
|
||||
String name = implementationPath.getFileName().toString();
|
||||
if (!Utils.isValidDatabaseName(name)) {
|
||||
logger.error(
|
||||
"Found database {} but the name doesn't match {}",
|
||||
name, Constants.DATABASE_NAME_FORMAT
|
||||
);
|
||||
return new DisabledPlayerLink();
|
||||
}
|
||||
int firstSplit = name.indexOf('-') + 1;
|
||||
databaseName = name.substring(firstSplit, name.indexOf('-', firstSplit));
|
||||
}
|
||||
|
||||
boolean init = true;
|
||||
|
||||
try {
|
||||
URL pluginUrl = implementationPath.toUri().toURL();
|
||||
|
||||
// we don't have a way to close this properly since we have no stop method and we have
|
||||
// to be able to load classes on the fly, but that doesn't matter anyway since Floodgate
|
||||
// doesn't support reloading
|
||||
classLoader = new URLClassLoader(
|
||||
new URL[]{pluginUrl},
|
||||
PlayerLinkHolder.class.getClassLoader()
|
||||
);
|
||||
|
||||
String mainClassName;
|
||||
JsonObject dbInitConfig;
|
||||
|
||||
try (InputStream linkConfigStream = classLoader.getResourceAsStream("init.json")) {
|
||||
requireNonNull(linkConfigStream, "Implementation should have an init file");
|
||||
|
||||
dbInitConfig = new Gson().fromJson(
|
||||
new InputStreamReader(linkConfigStream), JsonObject.class
|
||||
);
|
||||
|
||||
mainClassName = dbInitConfig.get("mainClass").getAsString();
|
||||
}
|
||||
|
||||
Class<? extends PlayerLink> mainClass =
|
||||
(Class<? extends PlayerLink>) classLoader.loadClass(mainClassName);
|
||||
|
||||
init = false;
|
||||
|
||||
// childContext = ApplicationContext.builder()
|
||||
// .singletons()
|
||||
// .classLoader(classLoader)
|
||||
// .packages()
|
||||
// .properties(Map.of(
|
||||
// "database.name", databaseName,
|
||||
// "database.classloader", classLoader,
|
||||
// "database.init.data", dbInitConfig
|
||||
// ))
|
||||
// .build();
|
||||
//
|
||||
// childContext.registerBeanDefinition(RuntimeBeanDefinition.builder(null).)
|
||||
//
|
||||
// childContext.environment(helo -> helo.);
|
||||
|
||||
instance = childContext.getBean(mainClass);
|
||||
|
||||
// we use our own internal PlayerLinking when global linking is enabled
|
||||
if (linkConfig.enableGlobalLinking()) {
|
||||
GlobalPlayerLinking linking = childContext.getBean(GlobalPlayerLinking.class);
|
||||
linking.setDatabaseImpl(instance);
|
||||
linking.load();
|
||||
return linking;
|
||||
} else {
|
||||
instance.load();
|
||||
return instance;
|
||||
}
|
||||
} catch (ClassCastException exception) {
|
||||
logger.error(
|
||||
"The database implementation ({}) doesn't extend the PlayerLink class!",
|
||||
implementationPath.getFileName().toString(), exception
|
||||
);
|
||||
return new DisabledPlayerLink();
|
||||
} catch (Exception exception) {
|
||||
if (init) {
|
||||
logger.error("Error while initialising database jar", exception);
|
||||
} else {
|
||||
logger.error("Error while loading database jar", exception);
|
||||
}
|
||||
return new DisabledPlayerLink();
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
void close() throws Exception {
|
||||
instance.stop();
|
||||
childContext.close();
|
||||
classLoader.close();
|
||||
}
|
||||
}
|
||||
@@ -26,10 +26,13 @@
|
||||
package org.geysermc.floodgate.core.module;
|
||||
|
||||
import io.micronaut.context.annotation.Bean;
|
||||
import io.micronaut.context.annotation.BootstrapContextCompatible;
|
||||
import io.micronaut.context.annotation.Factory;
|
||||
import io.netty.util.AttributeKey;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
@@ -39,6 +42,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import org.geysermc.event.Listener;
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
import org.geysermc.floodgate.core.util.Constants;
|
||||
import org.geysermc.floodgate.core.util.GlobalBeanCache;
|
||||
import org.geysermc.floodgate.crypto.AesCipher;
|
||||
import org.geysermc.floodgate.crypto.AesKeyProducer;
|
||||
import org.geysermc.floodgate.crypto.Base64Topping;
|
||||
@@ -46,6 +50,7 @@ import org.geysermc.floodgate.crypto.FloodgateCipher;
|
||||
import org.geysermc.floodgate.crypto.KeyProducer;
|
||||
|
||||
@Factory
|
||||
@BootstrapContextCompatible
|
||||
@Listener
|
||||
public class CommonModule {
|
||||
@Bean(preDestroy = "shutdown")
|
||||
@@ -63,15 +68,26 @@ public class CommonModule {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@BootstrapContextCompatible
|
||||
@Singleton
|
||||
public KeyProducer keyProducer() {
|
||||
return new AesKeyProducer();
|
||||
@Named("dataDirectory")
|
||||
public Path dataDirectory() {
|
||||
// todo open discussion asking how you can register bootstrap context beans
|
||||
return Paths.get("plugins", "floodgate");
|
||||
}
|
||||
|
||||
@Bean
|
||||
@BootstrapContextCompatible
|
||||
@Singleton
|
||||
public KeyProducer keyProducer() {
|
||||
return GlobalBeanCache.cacheIfAbsent("keyProducer", AesKeyProducer::new);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@BootstrapContextCompatible
|
||||
@Singleton
|
||||
public FloodgateCipher cipher() {
|
||||
return new AesCipher(new Base64Topping());
|
||||
return GlobalBeanCache.cacheIfAbsent("cipher", () -> new AesCipher(new Base64Topping()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -192,7 +192,7 @@ public abstract class CommandUtil {
|
||||
* @return true if the player has been whitelisted, false if the player was already whitelisted.
|
||||
* Defaults to false when this platform doesn't support whitelisting.
|
||||
*/
|
||||
public boolean whitelistPlayer(String xuid, String username) {
|
||||
public boolean whitelistPlayer(long xuid, String username) {
|
||||
UUID uuid = Utils.getJavaUuid(xuid);
|
||||
return whitelistPlayer(uuid, username);
|
||||
}
|
||||
@@ -217,7 +217,7 @@ public abstract class CommandUtil {
|
||||
* @return true if the player has been removed from the whitelist, false if the player wasn't
|
||||
* whitelisted. Defaults to false when this platform doesn't support whitelisting.
|
||||
*/
|
||||
public boolean removePlayerFromWhitelist(String xuid, String username) {
|
||||
public boolean removePlayerFromWhitelist(long xuid, String username) {
|
||||
UUID uuid = Utils.getJavaUuid(xuid);
|
||||
return removePlayerFromWhitelist(uuid, username);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ import org.geysermc.floodgate.core.addon.data.HandshakeDataImpl;
|
||||
import org.geysermc.floodgate.core.addon.data.HandshakeHandlersImpl;
|
||||
import org.geysermc.floodgate.core.api.SimpleFloodgateApi;
|
||||
import org.geysermc.floodgate.core.config.FloodgateConfig;
|
||||
import org.geysermc.floodgate.core.link.CommonPlayerLink;
|
||||
import org.geysermc.floodgate.core.skin.SkinUploadManager;
|
||||
import org.geysermc.floodgate.core.util.Constants;
|
||||
import org.geysermc.floodgate.core.util.LanguageManager;
|
||||
@@ -63,6 +64,7 @@ import org.geysermc.floodgate.util.LinkedPlayer;
|
||||
public final class FloodgateHandshakeHandler {
|
||||
@Inject HandshakeHandlersImpl handshakeHandlers;
|
||||
@Inject SimpleFloodgateApi api;
|
||||
@Inject CommonPlayerLink link;
|
||||
@Inject FloodgateCipher cipher;
|
||||
@Inject FloodgateConfig config;
|
||||
@Inject SkinUploadManager skinUploadManager;
|
||||
@@ -246,10 +248,16 @@ public final class FloodgateHandshakeHandler {
|
||||
}
|
||||
|
||||
private CompletableFuture<Pair<BedrockData, LinkedPlayer>> fetchLinkedPlayer(BedrockData data) {
|
||||
if (!api.getPlayerLink().isEnabled()) {
|
||||
if (!link.isEnabled()) {
|
||||
return CompletableFuture.completedFuture(new ObjectObjectImmutablePair<>(data, null));
|
||||
}
|
||||
return api.getPlayerLink().getLinkedPlayer(Utils.getJavaUuid(data.getXuid()))
|
||||
return link.fetchLink(Utils.getJavaUuid(data.getXuid()))
|
||||
.thenApply(link -> {
|
||||
if (link == null) {
|
||||
return null;
|
||||
}
|
||||
return LinkedPlayer.of(link.javaUsername(), link.javaUniqueId(), link.bedrockId());
|
||||
})
|
||||
.thenApply(link -> new ObjectObjectImmutablePair<>(data, link))
|
||||
.handle((result, error) -> {
|
||||
if (error != null) {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Floodgate
|
||||
*/
|
||||
|
||||
package org.geysermc.floodgate.core.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class GlobalBeanCache {
|
||||
private static final Map<String, Object> cache = new HashMap<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T cacheIfAbsent(String id, Supplier<T> object) {
|
||||
return (T) cache.computeIfAbsent(id, $ -> object.get());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T get(String id) {
|
||||
return (T) cache.get(id);
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,6 @@
|
||||
package org.geysermc.floodgate.core.util;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import io.micronaut.context.BeanProvider;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.net.URL;
|
||||
@@ -47,7 +45,7 @@ import org.geysermc.floodgate.core.config.FloodgateConfig;
|
||||
public final class LanguageManager {
|
||||
private final Map<String, Properties> localeMappings = new HashMap<>();
|
||||
|
||||
@Inject BeanProvider<FloodgateLogger> logger;
|
||||
@Inject FloodgateLogger logger;
|
||||
|
||||
/**
|
||||
* The locale used in console and as a fallback
|
||||
@@ -72,16 +70,11 @@ public final class LanguageManager {
|
||||
/**
|
||||
* Tries to load the log's locale file once a string has been requested
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
@Inject
|
||||
public void init(FloodgateConfig config) {
|
||||
if (!loadLocale("en_US")) {
|
||||
logger.get().error("Failed to load the fallback language. This will likely cause errors!");
|
||||
logger.error("Failed to load the fallback language. This will likely cause errors!");
|
||||
}
|
||||
defaultLocale = "en_US";
|
||||
}
|
||||
|
||||
public void loadConfiguredDefaultLocale(FloodgateConfig config) {
|
||||
FloodgateLogger logger = this.logger.get();
|
||||
defaultLocale = formatLocale(config.defaultLocale());
|
||||
|
||||
if (!"system".equals(defaultLocale) && isValidLanguage(defaultLocale)) {
|
||||
@@ -126,7 +119,7 @@ public final class LanguageManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.get().warn("Missing locale file: " + formatLocale);
|
||||
logger.warn("Missing locale file: " + formatLocale);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -187,7 +180,7 @@ public final class LanguageManager {
|
||||
.getResource("/languages/texts/" + locale + ".properties");
|
||||
|
||||
if (languageFile == null) {
|
||||
logger.get().warn(locale + " is not a supported Floodgate language.");
|
||||
logger.warn(locale + " is not a supported Floodgate language.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -99,7 +99,7 @@ public final class Metrics {
|
||||
);
|
||||
|
||||
metricsBase.addCustomChart(
|
||||
new SimplePie("floodgate_version", () -> Constants.VERSION)
|
||||
new SimplePie("floodgate_version", () -> Constants.FULL_VERSION)
|
||||
);
|
||||
|
||||
metricsBase.addCustomChart(
|
||||
|
||||
@@ -34,15 +34,17 @@ import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Utils {
|
||||
private static final Pattern NON_UNIQUE_PREFIX = Pattern.compile("^\\w{0,16}$");
|
||||
private static final Pattern DATABASE_NAME = Pattern.compile(Constants.DATABASE_NAME_FORMAT);
|
||||
private static final Random random = new SecureRandom();
|
||||
|
||||
/**
|
||||
* This method is used in Addons.<br> Most addons can be removed once the player associated to
|
||||
@@ -99,6 +101,22 @@ public class Utils {
|
||||
return DATABASE_NAME.matcher(databaseName).matches();
|
||||
}
|
||||
|
||||
public static String generateCode(int length) {
|
||||
StringBuilder code = new StringBuilder(length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
code.append(generateCodeChar());
|
||||
}
|
||||
return code.toString();
|
||||
}
|
||||
|
||||
public static char generateCodeChar() {
|
||||
var codeChar = random.nextInt() % (10 + 26);
|
||||
if (codeChar < 10) {
|
||||
return (char) ('0' + codeChar);
|
||||
}
|
||||
return (char) ('A' + codeChar);
|
||||
}
|
||||
|
||||
public static int readVarInt(ByteBuf buffer) {
|
||||
int out = 0;
|
||||
int count = 0;
|
||||
@@ -119,18 +137,4 @@ public class Utils {
|
||||
throwable.printStackTrace(new PrintWriter(writer));
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new CompletableFuture that is already completed exceptionally with the given
|
||||
* exception.
|
||||
*
|
||||
* @param ex the exception
|
||||
* @param <U> the type of the value
|
||||
* @return the exceptionally completed CompletableFuture
|
||||
*/
|
||||
public static <U> CompletableFuture<U> failedFuture(Throwable ex) {
|
||||
CompletableFuture<U> future = new CompletableFuture<>();
|
||||
future.completeExceptionally(ex);
|
||||
return future;
|
||||
}
|
||||
}
|
||||
|
||||
3
core/src/main/resources/application.yaml
Normal file
3
core/src/main/resources/application.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
http:
|
||||
baseUrl: https://api.geysermc.org
|
||||
userAgent: GeyserMC/Floodgate
|
||||
@@ -26,7 +26,8 @@
|
||||
package org.geysermc.floodgate.core.util;
|
||||
|
||||
public final class Constants {
|
||||
public static final String VERSION = "@floodgateVersion@";
|
||||
public static final String FULL_VERSION = "@fullVersion@";
|
||||
public static final String VERSION = "@version@";
|
||||
public static final int BUILD_NUMBER = Integer.parseInt("@buildNumber@");
|
||||
public static final String GIT_BRANCH = "@branch@";
|
||||
public static final int METRICS_ID = 14649;
|
||||
|
||||
Reference in New Issue
Block a user