Skip to content

Commit

Permalink
Merge pull request #14 from Brettanda/main
Browse files Browse the repository at this point in the history
Added support for Crafty 4
  • Loading branch information
Kamesuta authored Jan 18, 2025
2 parents ce8ac54 + aec70c1 commit 8e9c950
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.kamesuta.bungeepteropower.api.BungeePteroPowerAPI;
import com.kamesuta.bungeepteropower.api.PowerController;
import com.kamesuta.bungeepteropower.power.PterodactylController;
import com.kamesuta.bungeepteropower.power.CraftyController;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginManager;

Expand Down Expand Up @@ -65,9 +66,10 @@ public void onEnable() {
// Load config and translations
reload();

// Create PowerController map and register PterodactylController
// Create PowerController map and register PterodactylController and CraftyController
powerControllers = new ConcurrentHashMap<>();
powerControllers.put("pterodactyl", new PterodactylController());
powerControllers.put("crafty", new CraftyController());

// Check config
config.validateConfig(getProxy().getConsole());
Expand Down
59 changes: 43 additions & 16 deletions src/main/java/com/kamesuta/bungeepteropower/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ public class Config {
* Pterodactyl API Key
*/
public final String pterodactylApiKey;
/**
* Crafty-4 API URL
*/
public final URI craftyUrl;
/**
* Crafty-4 API Key
*/
public final String craftyApiKey;
/**
* Per-server configuration
*/
Expand Down Expand Up @@ -152,6 +160,10 @@ public Config() {
this.pterodactylUrl = new URI(configuration.getString("pterodactyl.url"));
this.pterodactylApiKey = configuration.getString("pterodactyl.apiKey");

// Crafty 4 API credentials
this.craftyUrl = new URI(configuration.getString("crafty.url"));
this.craftyApiKey = configuration.getString("crafty.apiKey");

// Bungeecord server name -> Pterodactyl server ID list
serverMap = new HashMap<>();
Configuration servers = configuration.getSection("servers");
Expand Down Expand Up @@ -247,22 +259,37 @@ public void validateConfig(CommandSender sender) {
}
}

// Validate the pterodactyl URL
if (pterodactylUrl == null) {
sender.sendMessage(plugin.messages.prefix().append("Warning: The Pterodactyl URL in the configuration is not set.").create());
}
if (pterodactylUrl.getHost().endsWith(".example.com")) {
sender.sendMessage(plugin.messages.prefix().append("Warning: The Pterodactyl URL in the configuration is example.com. Please set the correct URL.").create());
}
// Validate the pterodactyl API key
if (pterodactylApiKey == null) {
sender.sendMessage(plugin.messages.prefix().append("Warning: The Pterodactyl API key in the configuration is not set.").create());
}
if (!pterodactylApiKey.startsWith("ptlc_")) {
sender.sendMessage(plugin.messages.prefix().append("Warning: The Pterodactyl API key should start with 'ptlc_'.").create());
}
if (pterodactylApiKey.startsWith("ptlc_0000")) {
sender.sendMessage(plugin.messages.prefix().append("Warning: The Pterodactyl API key in the configuration is the default key. Please set the correct key.").create());
if (powerControllerType == "pterodactyl") {
// Validate the pterodactyl URL
if (pterodactylUrl == null) {
sender.sendMessage(plugin.messages.prefix().append("Warning: The Pterodactyl URL in the configuration is not set.").create());
}
if (pterodactylUrl.getHost().endsWith(".example.com")) {
sender.sendMessage(plugin.messages.prefix().append("Warning: The Pterodactyl URL in the configuration is example.com. Please set the correct URL.").create());
}
// Validate the pterodactyl API key
if (pterodactylApiKey == null) {
sender.sendMessage(plugin.messages.prefix().append("Warning: The Pterodactyl API key in the configuration is not set.").create());
}
if (!pterodactylApiKey.startsWith("ptlc_")) {
sender.sendMessage(plugin.messages.prefix().append("Warning: The Pterodactyl API key should start with 'ptlc_'.").create());
}
if (pterodactylApiKey.startsWith("ptlc_0000")) {
sender.sendMessage(plugin.messages.prefix().append("Warning: The Pterodactyl API key in the configuration is the default key. Please set the correct key.").create());
}
} else if (powerControllerType == "crafty") {
// Validate the crafty URL
if (craftyUrl == null) {
sender.sendMessage(plugin.messages.prefix().append("Warning: The Crafty URL in the configuration is not set.").create());
}
if (craftyUrl.getHost().endsWith(".example.com")) {
sender.sendMessage(plugin.messages.prefix().append("Warning: The Crafty URL in the configuration is example.com. Please set the correct URL.").create());
}
// Validate the crafty API key
if (craftyApiKey == null) {
sender.sendMessage(plugin.messages.prefix().append("Warning: The Crafty API key in the configuration is not set.").create());
}

}

// Validate the server names
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.kamesuta.bungeepteropower.power;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.kamesuta.bungeepteropower.api.PowerController;
import com.kamesuta.bungeepteropower.api.PowerSignal;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.logging.Level;

import static com.kamesuta.bungeepteropower.BungeePteroPower.logger;
import static com.kamesuta.bungeepteropower.BungeePteroPower.plugin;

/**
* Crafty API client.
*/
public class CraftyController implements PowerController {
/**
* Send a power signal to the Crafty server.
*
* @param serverName The name of the server to start
* @param serverId The Crafty server ID
* @param signalType The power signal to send
* @return A future that completes when the request is finished
*/
@Override
public CompletableFuture<Void> sendPowerSignal(String serverName, String serverId, PowerSignal signalType) {
String signal = signalType.getSignal();
String action = signalType == PowerSignal.START ? "start_server" : "stop_server";
logger.info(String.format("%s server: %s (Crafty server ID: %s)", action, serverName, serverId));

// Create a path
String path = "/api/v2/servers/" + serverId + "/action/" + action;

HttpClient client = HttpClient.newHttpClient();

// Create a request
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(plugin.config.craftyUrl.resolve(path).toString()))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + plugin.config.craftyApiKey)
.POST(HttpRequest.BodyPublishers.ofString(""))
.build();

return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(status -> {
int code = status.statusCode();
logger.info(status.toString());
if (code == 200) {
logger.info("Successfully sent " + signal + " signal to the server: " + serverName);
return (Void) null;
} else {
String message = "Failed to send " + signal + " signal to the server: " + serverName + ". Response code: " + code;
logger.warning(message);
logger.info("Request: " + request + ", Response: " + code + " " + status.body());
throw new RuntimeException(message);
}
})
.exceptionally(e -> {
logger.log(Level.WARNING, "Failed to send " + action + " signal to the server: " + serverName, e);
throw new CompletionException(e);
});
}

/**
* Restore from a backup.
* Send a stop signal to the server, wait until the server is offline, and then
* restore from a backup.
*
* @param serverName The name of the server
* @param serverId The Crafty server ID
* @param backupName The file name of the backup
* @return A future that completes when the request to restore from the backup
* is sent after the server becomes offline
*/
@Override
public CompletableFuture<Void> sendRestoreSignal(String serverName, String serverId, String backupName) {
throw new UnsupportedOperationException(
"Feature incomplete at this time. The Crafty 4 Controller API doesn't provide restore function.");
}
}
9 changes: 9 additions & 0 deletions src/main/resources/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ pterodactyl:
# You can find the client api key in the "API Credentials" tab of the "Account" page.
apiKey: "ptlc_000000000000000000000000000000000000000000"

# Crafty configuration
crafty:
# The URL of your crafty panel
# If you use Cloudflare Tunnel, you need to allow the ip in the bypass setting.
url: "https://panel.example.com"
# The client api key of your crafty panel. Can be found in Panel Config > Users > Edit User > API Keys
# The only permission you need is: COMMANDS
apiKey: ""

# Per server configuration
servers:
pvp:
Expand Down

0 comments on commit 8e9c950

Please sign in to comment.