Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for Crafty 4 #14

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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