Added starter implementation.
This commit is contained in:
parent
cec1853d73
commit
4efc1002a9
|
@ -0,0 +1,37 @@
|
|||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
|
||||
config.json
|
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.andrewlalis</groupId>
|
||||
<artifactId>mc-status-bot</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>net.dv8tion</groupId>
|
||||
<artifactId>JDA</artifactId>
|
||||
<version>5.0.0-beta.18</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.16.0</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>2.16.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addClasspath>true</addClasspath>
|
||||
<mainClass>com.andrewlalis.mc_status_bot.McStatusBot</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals><goal>shade</goal></goals>
|
||||
<configuration>
|
||||
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -0,0 +1,15 @@
|
|||
package com.andrewlalis.mc_status_bot;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class McStatusBot {
|
||||
public static void main(String[] args) throws Exception {
|
||||
Executor executor = Executors.newVirtualThreadPerTaskExecutor();
|
||||
for (ServerBot bot : ServerBot.read(Path.of("config.json"))) {
|
||||
System.out.println("Starting server status bot for " + bot.getServerIp());
|
||||
executor.execute(bot);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
package com.andrewlalis.mc_status_bot;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.JDABuilder;
|
||||
import net.dv8tion.jda.api.OnlineStatus;
|
||||
import net.dv8tion.jda.api.entities.Activity;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.utils.cache.CacheFlag;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public class ServerBot implements Runnable {
|
||||
private final JDA jda;
|
||||
private final String serverIp;
|
||||
private final long channelId;
|
||||
private final ServerStatusFetcher serverStatusFetcher;
|
||||
|
||||
private int lastPlayerCount = -1;
|
||||
private final Set<String> lastPlayerNames = new HashSet<>();
|
||||
|
||||
public ServerBot(JDA jda, String serverIp, long channelId, ServerStatusFetcher serverStatusFetcher) {
|
||||
this.jda = jda;
|
||||
this.serverIp = serverIp;
|
||||
this.channelId = channelId;
|
||||
this.serverStatusFetcher = serverStatusFetcher;
|
||||
}
|
||||
|
||||
public String getServerIp() {
|
||||
return serverIp;
|
||||
}
|
||||
|
||||
public static Collection<ServerBot> read(Path jsonConfigFile) throws Exception {
|
||||
if (Files.notExists(jsonConfigFile)) throw new IOException("File " + jsonConfigFile + " doesn't exist.");
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
ObjectNode configData;
|
||||
try (var in = Files.newInputStream(jsonConfigFile)) {
|
||||
configData = mapper.readValue(in, ObjectNode.class);
|
||||
}
|
||||
|
||||
ArrayNode serversArray = configData.withArray("servers");
|
||||
List<ServerBot> bots = new ArrayList<>(serversArray.size());
|
||||
ServerStatusFetcher serverStatusFetcher = new ServerStatusFetcher();
|
||||
for (JsonNode node : serversArray) {
|
||||
String token = node.get("discord-token").asText();
|
||||
String serverIp = node.get("server-ip").asText();
|
||||
long channelId = node.get("channel-id").asLong();
|
||||
JDABuilder builder = JDABuilder.create(token, Collections.emptyList());
|
||||
builder.disableCache(CacheFlag.ACTIVITY, CacheFlag.VOICE_STATE);
|
||||
JDA jda = builder.build();
|
||||
bots.add(new ServerBot(jda, serverIp, channelId, serverStatusFetcher));
|
||||
}
|
||||
return bots;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
jda.awaitReady();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("Interrupted while awaiting ready status.", e);
|
||||
}
|
||||
while (true) {
|
||||
try {
|
||||
ServerStatus status = serverStatusFetcher.fetch(serverIp);
|
||||
displayStatus(status);
|
||||
} catch (IOException e) {
|
||||
handleError(e);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace(System.err);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void displayStatus(ServerStatus status) {
|
||||
String text = String.format("%d/%d players", status.playersOnline(), status.maxPlayers());
|
||||
OnlineStatus onlineStatus = jda.getPresence().getStatus();
|
||||
OnlineStatus newOnlineStatus = status.playersOnline() > 0 ? OnlineStatus.ONLINE : OnlineStatus.IDLE;
|
||||
Activity activity = jda.getPresence().getActivity();
|
||||
Activity newActivity = Activity.customStatus(text);
|
||||
|
||||
boolean shouldUpdate = onlineStatus != newOnlineStatus ||
|
||||
activity == null ||
|
||||
!activity.getName().equals(newActivity.getName());
|
||||
if (shouldUpdate) {
|
||||
jda.getPresence().setPresence(newOnlineStatus, newActivity);
|
||||
}
|
||||
|
||||
if (lastPlayerCount != -1 && lastPlayerCount != status.playersOnline()) {
|
||||
final TextChannel channel = jda.getTextChannelById(channelId);
|
||||
for (String name : getPlayersJoined(status.playerNames())) {
|
||||
channel.sendMessage(name + " joined the server.").queue();
|
||||
}
|
||||
for (String name : getPlayersLeft(status.playerNames())) {
|
||||
channel.sendMessage(name + " left the server.").queue();
|
||||
}
|
||||
lastPlayerCount = status.playersOnline();
|
||||
lastPlayerNames.clear();
|
||||
lastPlayerNames.addAll(status.playerNames());
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> getPlayersJoined(Set<String> current) {
|
||||
Set<String> set = new HashSet<>(current);
|
||||
set.removeAll(lastPlayerNames);
|
||||
return set;
|
||||
}
|
||||
|
||||
private Set<String> getPlayersLeft(Set<String> current) {
|
||||
Set<String> set = new HashSet<>(lastPlayerNames);
|
||||
set.removeAll(current);
|
||||
return set;
|
||||
}
|
||||
|
||||
private void handleError(IOException e) {
|
||||
if (jda.getPresence().getStatus() != OnlineStatus.DO_NOT_DISTURB) {
|
||||
jda.getPresence().setStatus(OnlineStatus.DO_NOT_DISTURB);
|
||||
jda.getPresence().setActivity(Activity.customStatus("Error."));
|
||||
}
|
||||
e.printStackTrace(System.err);
|
||||
lastPlayerCount = -1;
|
||||
lastPlayerNames.clear();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.andrewlalis.mc_status_bot;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public record ServerStatus(
|
||||
int playersOnline,
|
||||
int maxPlayers,
|
||||
Set<String> playerNames
|
||||
) {}
|
|
@ -0,0 +1,48 @@
|
|||
package com.andrewlalis.mc_status_bot;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class ServerStatusFetcher {
|
||||
private final HttpClient client = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(3))
|
||||
.executor(Executors.newVirtualThreadPerTaskExecutor())
|
||||
.build();
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public ServerStatus fetch(String ip) throws IOException {
|
||||
HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.mcsrvstat.us/3/" + ip))
|
||||
.GET()
|
||||
.timeout(Duration.ofSeconds(5))
|
||||
.build();
|
||||
try {
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (response.statusCode() != 200) throw new IOException("Non-200 status code: " + response.statusCode());
|
||||
ObjectNode data = objectMapper.readValue(response.body(), ObjectNode.class);
|
||||
Set<String> playerNames = new HashSet<>();
|
||||
ArrayNode playersArray = data.get("players").withArray("list");
|
||||
for (JsonNode node : playersArray) {
|
||||
playerNames.add(node.get("name").asText());
|
||||
}
|
||||
return new ServerStatus(
|
||||
data.get("players").get("online").asInt(),
|
||||
data.get("players").get("max").asInt(),
|
||||
playerNames
|
||||
);
|
||||
} catch (IOException | InterruptedException e) {
|
||||
throw new IOException("Failed to get server status.", e);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue