1
0
Fork 0
forked from mc/VTools

Compare commits

...

3 commits

Author SHA1 Message Date
cba3248484 split package name and re-write server closer 2023-12-21 15:58:21 +08:00
1a3c8872d1 整理代码 2023-12-21 15:53:37 +08:00
f36607df8d readme 2023-12-21 15:53:13 +08:00
12 changed files with 345 additions and 262 deletions

View file

@ -1,6 +1,8 @@
# VTools # VTools
Tools for Velocity proxy server. Tools for Velocity proxy server.
add telegram/auto shutdown support for spec server.
## Commands ## Commands
|Command|What is does?|Permission| |Command|What is does?|Permission|
|-------|-------------|----------| |-------|-------------|----------|

View file

@ -1,8 +1,9 @@
package de.strifel.VTools; package com.alpt.vtools.listeners;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpServer;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import de.strifel.VTools.VTools;
import org.jetbrains.annotations.TestOnly; import org.jetbrains.annotations.TestOnly;
import java.io.IOException; import java.io.IOException;
@ -13,18 +14,26 @@ import java.util.Collection;
public class OnlinePlayerQueryService { public class OnlinePlayerQueryService {
public static OnlinePlayerQueryService INSTANCE;
private final VTools plugin; private final VTools plugin;
public OnlinePlayerQueryService(VTools plugin) { private OnlinePlayerQueryService(VTools plugin) {
this.plugin = plugin; this.plugin = plugin;
} }
private static final Gson gson = new Gson(); private static final Gson gson = new Gson();
public void register() { public static OnlinePlayerQueryService createInstance(VTools plugin) {
if (INSTANCE != null) return INSTANCE;
INSTANCE = new OnlinePlayerQueryService(plugin);
INSTANCE.register();
return INSTANCE;
}
private void register() {
int port = Integer.parseInt(plugin.getConfigOrDefault("http_service_port", "17611"));
try { try {
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 17611), 0); HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", port), 0);
server.createContext("/api/getOnlinePlayers", exchange -> { server.createContext("/api/getOnlinePlayers", exchange -> {
Collection<Player> players = plugin.getServer().getAllPlayers(); Collection<Player> players = plugin.getServer().getAllPlayers();

View file

@ -0,0 +1,223 @@
package com.alpt.vtools.listeners;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.proxy.ProxyServer;
import de.strifel.VTools.VTools;
import de.strifel.VTools.listeners.TGBridge;
import okhttp3.*;
import java.io.IOException;
import java.util.concurrent.LinkedBlockingQueue;
public class ServerCloser {
private static final long MINUTE = 60L * 1000;
private static final OkHttpClient CLIENT = new OkHttpClient();
private static final String ACTION_STOP_JSON = "{\"action\":\"stop\"}";
@SuppressWarnings("java:S1068")
private static final String ACTION_START_JSON = "{\"action\":\"start\"}";
private final VTools plugin;
private final ProxyServer server;
private final LinkedBlockingQueue<Counter> lock = new LinkedBlockingQueue<>();
public static ServerCloser INSTANCE;
private ServerCloser(VTools plugin) {
this.plugin = plugin;
this.server = plugin.getServer();
}
private boolean executeAzure() {
return executeAzure(ACTION_STOP_JSON);
}
private boolean executeAzure(String action) {
RequestBody requestBody = RequestBody.create(action, MediaType.parse("application/json"));
Request request = new Request.Builder()
.url(plugin.getConfigOrDefault("azure_api_url", "https://example.com/"))
.post(requestBody)
.addHeader("Content-Type", "application/json")
.build();
try (Response response = CLIENT.newCall(request).execute()) {
String body = response.body() == null ? "null" : response.body().string();
plugin.logger.info("ServerCloser: http request response: {} ({}).", body, response.code());
return (response.code() >= 200 && response.code() < 300);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
private void close() {
if (!INSTANCE.server.getAllPlayers().isEmpty()) {
plugin.logger.error("ServerCloser: 定时器到点时发现服务器有人。这不应发生,因为定时器本应该被打断。");
TGBridge.error("ServerCloser: #bug @NaAlOH4 定时器到点时发现服务器有人。这不应发生,因为定时器本应该被打断。");
}
boolean httpSuccess = false;
for (int i = 0; i < 3; i++) {
httpSuccess = executeAzure();
if (httpSuccess) {
TGBridge.log("ServerCloser: 向azure发送关机命令成功正在关闭 pymcd.");
TGBridge.setShuttingDown(0);
break;
}
}
if (!httpSuccess) {
TGBridge.error("服务器关机 http 请求失效,服务器可能没有正常关闭。");
} else {
try {
Runtime.getRuntime().exec(new String[]{"pkill", "pymcd"});
} catch (IOException e) {
TGBridge.error("关闭 pymcd 时出现问题:" + e.getMessage());
}
}
}
private boolean firstInit;
public static ServerCloser createInstance(VTools plugin) {
if (INSTANCE != null) return INSTANCE;
INSTANCE = new ServerCloser(plugin);
INSTANCE.server.getEventManager().register(plugin, INSTANCE);
INSTANCE.firstInit = true;
INSTANCE.update();
return INSTANCE;
}
@SuppressWarnings("java:S106")
@Subscribe
public void onDisconnect(DisconnectEvent event) {
update();
}
@Subscribe
public void onServerConnected(ServerConnectedEvent event) {
update();
}
private void update() {
if (server.getAllPlayers().isEmpty()) {
initCountdown();
return;
}
boolean canceledSomething = false;
synchronized (lock) {
while (!lock.isEmpty()) {
lock.poll().cancel();
canceledSomething = true;
}
}
if (canceledSomething) {
plugin.logger.info("ServerCloser: 有玩家在线,干掉任何可能的关机计时器。");
}
TGBridge.setShuttingDown(-1);
}
private void initCountdown() {
startCountdown(firstInit);
firstInit = false;
}
private void startCountdown(boolean isFirstInit) {
synchronized (lock) {
while (!lock.isEmpty()) {
lock.poll().cancel();
}
lock.add(new Counter(INSTANCE, isFirstInit ? 60 : 15).start());
}
}
public void fastShutdown() {
synchronized (lock) {
while (!lock.isEmpty()) {
lock.poll().cancel();
}
lock.add(new Counter(INSTANCE, 1).start());
}
}
public void slowShutdown() {
synchronized (lock) {
while (!lock.isEmpty()) {
lock.poll().cancel();
}
lock.add(new Counter(INSTANCE, 60).start());
}
}
private static class Counter {
private final ServerCloser instance;
private final int totalMin;
private int minLeft;
private boolean canceled = false;
protected Counter(ServerCloser instance, int minute) {
this.instance = instance;
totalMin = minute;
minLeft = minute;
}
protected synchronized void cancel() {
canceled = true;
this.notifyAll();
}
private boolean started = false;
private final Object startLock = new Object();
protected Counter start() {
synchronized (startLock) {
if (started) return this;
started = true;
}
new Thread(this::run).start();
return this;
}
private synchronized void run() {
while (true) {
if (canceled) return;
TGBridge.setShuttingDown(minLeft);
try {
this.wait(MINUTE);
} catch (InterruptedException ignored) {}
if (canceled) return;
if (!instance.server.getAllPlayers().isEmpty()) {
instance.plugin.logger.error("ServerCloser: 定时器发现服务器有人。这不应发生,因为定时器本应该被直接打断。");
TGBridge.error("ServerCloser: #bug @NaAlOH4 定时器发现服务器有人。这不应发生,因为定时器本应该被直接打断。");
canceled = true;
}
if (canceled) return;
minLeft--;
switch (minLeft) {
case 1 -> {
String msg = "服务器即将在一分钟后关机";
instance.plugin.logger.info(msg);
TGBridge.log(msg);
}
case 0 -> {
String msg = "ServerCloser: 距离上一个玩家离开已经过了 %s 分钟,即将关机。".formatted(totalMin);
instance.plugin.logger.info(msg);
TGBridge.log(msg);
instance.close();
canceled = true;
}
case -1 -> {
instance.plugin.logger.error("ServerCloser: 定时器写炸了。");
TGBridge.error("ServerCloser: #bug @NaAlOH4 定时器写炸了。");
canceled = true;
}
default -> instance.plugin.logger.info("服务器即将在 {} 分钟后关机", minLeft);
}
}
}
}
}

View file

@ -1,4 +1,4 @@
package de.strifel.VTools.listeners; package com.alpt.vtools.utils;
import com.pengrad.telegrambot.model.Message; import com.pengrad.telegrambot.model.Message;
import com.pengrad.telegrambot.model.MessageEntity; import com.pengrad.telegrambot.model.MessageEntity;

View file

@ -1,191 +0,0 @@
package de.strifel.VTools;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.proxy.ProxyServer;
import de.strifel.VTools.listeners.TGBridge;
import okhttp3.*;
import org.yaml.snakeyaml.Yaml;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
public class ServerCloser {
private static final long INACTIVITY_TIMEOUT_MILLISECOND = 15L * 60 * 1000; // 15 minutes
private static final long MINUTE = 60L * 1000; // 15 minutes
private static final OkHttpClient CLIENT = new OkHttpClient();
private String apiUrl;
private static final String ACTION_STOP_JSON = "{\"action\":\"stop\"}";
@SuppressWarnings("java:S1068")
private static final String ACTION_START_JSON = "{\"action\":\"start\"}";
private final VTools plugin;
private final ProxyServer server;
private Timer closeServerTimer = new Timer();
private final Object lock = new Object();
public ServerCloser(VTools plugin) {
this.plugin = plugin;
this.server = plugin.getServer();
}
private boolean executeAzure() {
return executeAzure(ACTION_STOP_JSON);
}
private boolean executeAzure(String action) {
RequestBody requestBody = RequestBody.create(action, MediaType.parse("application/json"));
Request request = new Request.Builder()
.url(apiUrl)
.post(requestBody)
.addHeader("Content-Type", "application/json")
.build();
try {
Response response = CLIENT.newCall(request).execute();
plugin.logger.info("ServerCloser: http request response: {} ({}).", response.body().string(), response.code());
return (response.code() >= 200 && response.code() < 300);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
private void close() {
boolean httpSuccess = false;
for (int i = 0; i < 3; i++) {
httpSuccess = executeAzure();
if (httpSuccess) {
TGBridge.log("ServerCloser: 向azure发送关机命令成功正在关闭 pymcd.");
TGBridge.setShuttingDown(0);
break;
}
}
if (!httpSuccess) {
TGBridge.error("服务器关机 http 请求失效,服务器可能没有正常关闭。");
} else {
try {
Runtime.getRuntime().exec("pkill pymcd");
} catch (IOException e) {
TGBridge.error("关闭 pymcd 时出现问题:" + e.getMessage());
}
}
}
private boolean firstInit;
private int countDown = -1;
public void register() {
server.getEventManager().register(plugin, this);
firstInit = true;
update();
loadConfig();
}
private void loadConfig() {
try {
File configDir = plugin.dataDirectory.toFile();
if (!configDir.exists()) {
configDir.mkdir();
}
File configFile = new File(configDir, "config.yaml");
if (!configFile.exists()) {
Files.write(Path.of(configFile.toURI()), "chat_id: \"0\"\ntoken: \"\"\n".getBytes(StandardCharsets.UTF_8));
}
String configStr = Files.readString(Path.of(configFile.toURI()), StandardCharsets.UTF_8);
Yaml yaml = new Yaml();
Map<String, String> config = yaml.load(configStr);
apiUrl = config.getOrDefault("azure_api_url", "https://example.com/");
} catch (Exception e) {
plugin.logger.error("parsing config", e);
}
}
@SuppressWarnings("java:S106")
@Subscribe
public void onDisconnect(DisconnectEvent event) {
update();
}
@Subscribe
public void onServerConnected(ServerConnectedEvent event) {
update();
}
private void update() {
var hasPlayer = !server.getAllPlayers().isEmpty();
if (hasPlayer) {
plugin.logger.info("ServerCloser: 有玩家在线,干掉任何可能的关机计时器。");
closeServerTimer.cancel();
countDown = -1;
TGBridge.setShuttingDown(-1);
return;
}
initCountdown();
}
private void initCountdown() {
countDown = firstInit ? 60 : 15;
firstInit = false;
startCountdown();
}
private void startCountdown() {
TGBridge.setShuttingDown(countDown);
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
@SuppressWarnings("java:S1199")
public void run() {
{
countDown--;
if (countDown < 0) {
TGBridge.error("ServerCloser: #bug @NaAlOH4 计时器写炸了");
closeServerTimer.cancel();
return;
}
if (server.getAllPlayers().isEmpty()) {
switch (countDown) {
case 1 -> {
TGBridge.log("服务器即将在一分钟后关机");
startCountdown();
}
case 0 -> {
plugin.logger.info("ServerCloser: 距离上一个玩家离开已经过了%s即将关机。".formatted(firstInit ? "一小时" : "15分钟"));
TGBridge.log("ServerCloser: 距离上一个玩家离开已经过了%s即将关机。".formatted(firstInit ? "一小时" : "15分钟"));
close();
closeServerTimer.cancel();
}
default -> startCountdown();
}
} else {
plugin.logger.error("ServerCloser: 定时器到点时发现服务器有人。这不应发生,因为定时器本应该被打断。");
TGBridge.error("ServerCloser: #bug @NaAlOH4 定时器到点时发现服务器有人。这不应发生,因为定时器本应该被打断。");
closeServerTimer.cancel();
}
}
}
}, MINUTE);
plugin.logger.info("ServerCloser: 将在 {} 分钟后自动关机。", countDown);
synchronized (lock) {
closeServerTimer.cancel();
closeServerTimer = timer;
}
}
}

View file

@ -1,5 +1,7 @@
package de.strifel.VTools; package de.strifel.VTools;
import com.alpt.vtools.listeners.OnlinePlayerQueryService;
import com.alpt.vtools.listeners.ServerCloser;
import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.plugin.Plugin;
@ -9,11 +11,17 @@ import de.strifel.VTools.commands.*;
import de.strifel.VTools.listeners.*; import de.strifel.VTools.listeners.*;
import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextColor;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.yaml.snakeyaml.Yaml;
import javax.inject.Inject; import javax.inject.Inject;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
@Plugin(id = "vtools", name="VTools", version="1.0-SNAPSHOT", description="Some commands!", authors="unnamed") @Plugin(id = "vtools", name = "VTools", version = "1.0-SNAPSHOT", description = "Some commands!", authors = "unnamed")
public class VTools { public class VTools {
private final ProxyServer server; private final ProxyServer server;
public final Logger logger; public final Logger logger;
@ -42,11 +50,46 @@ public class VTools {
server.getCommandManager().register("tps", new CommandTp(server), "jump"); server.getCommandManager().register("tps", new CommandTp(server), "jump");
server.getCommandManager().register("server", new CommandServer(server), "serverv"); server.getCommandManager().register("server", new CommandServer(server), "serverv");
server.getCommandManager().register("servers", new CommandServers(server), "allservers"); server.getCommandManager().register("servers", new CommandServers(server), "allservers");
loadConfig();
new TGBridge(this).register(); new TGBridge(this).register();
new PlayerStatus(this).register(); new PlayerStatus(this).register();
new GlobalChat(this).register(); new GlobalChat(this).register();
new ServerCloser(this).register(); ServerCloser.createInstance(this);
new OnlinePlayerQueryService(this).register(); OnlinePlayerQueryService.createInstance(this);
}
private Map<String, String> config = new HashMap<>();
public String getConfig(String k) {
return config.get(k);
}
public String getConfigOrDefault(String k, String v) {
return config.getOrDefault(k, v);
}
private void loadConfig() {
try {
File configDir = dataDirectory.toFile();
if (!configDir.exists()) {
configDir.mkdir();
}
File configFile = new File(configDir, "config.yaml");
if (!configFile.exists()) {
String defVal = """
chat_id: "0"
token: ""
azure_api_url: "https://example.com/"
http_service_port: 17611
""";
Files.write(Path.of(configFile.toURI()), defVal.getBytes(StandardCharsets.UTF_8));
}
String configStr = Files.readString(Path.of(configFile.toURI()), StandardCharsets.UTF_8);
Yaml yaml = new Yaml();
config = yaml.load(configStr);
} catch (Exception e) {
logger.error("parsing config", e);
}
} }
public ProxyServer getServer() { public ProxyServer getServer() {

View file

@ -54,7 +54,7 @@ public class CommandGlobalChat implements SimpleCommand {
@Override @Override
public List<String> suggest(Invocation invocation) { public List<String> suggest(Invocation invocation) {
return new ArrayList<String>(); return new ArrayList<>();
} }
@Override @Override

View file

@ -1,6 +1,5 @@
package de.strifel.VTools.commands; package de.strifel.VTools.commands;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.SimpleCommand; import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
@ -19,7 +18,6 @@ public class CommandRestart implements SimpleCommand {
@Override @Override
public void execute(SimpleCommand.Invocation invocation) { public void execute(SimpleCommand.Invocation invocation) {
CommandSource commandSource = invocation.source();
String[] strings = invocation.arguments(); String[] strings = invocation.arguments();
if (strings.length > 0) { if (strings.length > 0) {
@ -33,7 +31,7 @@ public class CommandRestart implements SimpleCommand {
@Override @Override
public List<String> suggest(SimpleCommand.Invocation invocation) { public List<String> suggest(SimpleCommand.Invocation invocation) {
return new ArrayList<String>(); return new ArrayList<>();
} }
@Override @Override

View file

@ -9,10 +9,7 @@ import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -39,23 +36,20 @@ public class CommandSend implements SimpleCommand {
for (Player player : server.getAllPlayers()) { for (Player player : server.getAllPlayers()) {
oPlayer.add(player); oPlayer.add(player);
} }
} } else if (strings[0].equals("current")) {
else if (strings[0].equals("current")) {
if (commandSource instanceof Player) { if (commandSource instanceof Player) {
Player playerSource = (Player)commandSource; Player playerSource = (Player) commandSource;
Optional<ServerConnection> conn = playerSource.getCurrentServer(); Optional<ServerConnection> conn = playerSource.getCurrentServer();
if (conn.isPresent()) { if (conn.isPresent()) {
for (Player player : conn.get().getServer().getPlayersConnected()) { for (Player player : conn.get().getServer().getPlayersConnected()) {
oPlayer.add(player); oPlayer.add(player);
} }
} }
} } else {
else {
commandSource.sendMessage(Component.text("Command is only for players.").color(COLOR_RED)); commandSource.sendMessage(Component.text("Command is only for players.").color(COLOR_RED));
return; return;
} }
} } else {
else {
Optional<Player> p = server.getPlayer(strings[0]); Optional<Player> p = server.getPlayer(strings[0]);
if (p.isPresent()) { if (p.isPresent()) {
oPlayer.add(p.get()); oPlayer.add(p.get());
@ -94,9 +88,9 @@ public class CommandSend implements SimpleCommand {
} }
})); }));
String sendResults = results.isEmpty() ? "nothing" : results.entrySet().stream().map( String sendResults = results.isEmpty() ? "nothing" : results.entrySet().stream().map(
entry -> String.format("%s : %d", entry.getKey(), entry.getValue()) entry -> String.format("%s : %d", entry.getKey(), entry.getValue())
).collect(Collectors.joining("\n")); ).collect(Collectors.joining("\n"));
commandSource.sendMessage(Component.text(String.format("Send Results:\n%s", sendResults)).color(COLOR_YELLOW)); commandSource.sendMessage(Component.text("Send Results:%n%s".formatted(sendResults)).color(COLOR_YELLOW));
}).start(); }).start();
} else { } else {
commandSource.sendMessage(Component.text("The server or user does not exist!").color(COLOR_RED)); commandSource.sendMessage(Component.text("The server or user does not exist!").color(COLOR_RED));
@ -108,21 +102,23 @@ public class CommandSend implements SimpleCommand {
public List<String> suggest(SimpleCommand.Invocation invocation) { public List<String> suggest(SimpleCommand.Invocation invocation) {
String[] currentArgs = invocation.arguments(); String[] currentArgs = invocation.arguments();
switch (currentArgs.length) {
List<String> arg = new ArrayList<String>(); case 0, 1 -> {
if (currentArgs.length <= 1) { List<String> arg = new ArrayList<>(server.getPlayerCount() + 2);
arg.add("all"); arg.add("all");
arg.add("current"); arg.add("current");
for (Player player : server.getAllPlayers()) { for (Player player : server.getAllPlayers()) {
arg.add(player.getUsername()); arg.add(player.getUsername());
}
return arg;
} }
return arg; case 2 -> {
} else if (currentArgs.length == 2) { return server.getAllServers().stream().map(s -> s.getServerInfo().getName()).toList();
for (RegisteredServer server : server.getAllServers()) { }
arg.add(server.getServerInfo().getName()); default -> {
return new ArrayList<>(0);
} }
} }
return arg;
} }
public boolean hasPermission(SimpleCommand.Invocation invocation) { public boolean hasPermission(SimpleCommand.Invocation invocation) {

View file

@ -34,7 +34,7 @@ public class CommandServers implements SimpleCommand {
@Override @Override
public List<String> suggest(SimpleCommand.Invocation invocation) { public List<String> suggest(SimpleCommand.Invocation invocation) {
return new ArrayList<String>(); return new ArrayList<>();
} }
@Override @Override

View file

@ -26,11 +26,11 @@ public class CommandTp implements SimpleCommand {
CommandSource commandSource = commandInvocation.source(); CommandSource commandSource = commandInvocation.source();
String[] strings = commandInvocation.arguments(); String[] strings = commandInvocation.arguments();
if (commandSource instanceof Player) { if (commandSource instanceof Player playerCommandSource) {
if (strings.length == 1) { if (strings.length == 1) {
Optional<Player> player = server.getPlayer(strings[0]); Optional<Player> player = server.getPlayer(strings[0]);
if (player.isPresent()) { if (player.isPresent()) {
player.get().getCurrentServer().ifPresent(serverConnection -> ((Player) commandSource).createConnectionRequest(serverConnection.getServer()).fireAndForget()); player.get().getCurrentServer().ifPresent(serverConnection -> playerCommandSource.createConnectionRequest(serverConnection.getServer()).fireAndForget());
commandSource.sendMessage(Component.text("Connecting to the server of " + strings[0]).color(COLOR_YELLOW)); commandSource.sendMessage(Component.text("Connecting to the server of " + strings[0]).color(COLOR_YELLOW));
} else { } else {
commandSource.sendMessage(Component.text("Player does not exists.").color(COLOR_RED)); commandSource.sendMessage(Component.text("Player does not exists.").color(COLOR_RED));

View file

@ -1,5 +1,7 @@
package de.strifel.VTools.listeners; package de.strifel.VTools.listeners;
import com.alpt.vtools.listeners.ServerCloser;
import com.alpt.vtools.utils.MarkdownString;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.pengrad.telegrambot.Callback; import com.pengrad.telegrambot.Callback;
@ -27,13 +29,8 @@ import de.strifel.VTools.VTools;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.yaml.snakeyaml.Yaml;
import java.io.IOException; import java.io.IOException;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*; import java.util.*;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@ -77,24 +74,9 @@ public class TGBridge {
} }
private void loadConfig() { private void loadConfig() {
try { synchronized (this) {
File configDir = plugin.dataDirectory.toFile(); CHAT_ID = Long.parseLong(plugin.getConfigOrDefault("chat_id", "0"));
if (!configDir.exists()) { TOKEN = plugin.getConfigOrDefault("token", "");
configDir.mkdir();
}
File configFile = new File(configDir, "config.yaml");
if (!configFile.exists()) {
Files.write(Path.of(configFile.toURI()), "chat_id: \"0\"\ntoken: \"\"\n".getBytes(StandardCharsets.UTF_8));
}
String configStr = Files.readString(Path.of(configFile.toURI()), StandardCharsets.UTF_8);
Yaml yaml = new Yaml();
Map<String, String> config = yaml.load(configStr);
synchronized (this) {
this.CHAT_ID = Long.parseLong(config.getOrDefault("chat_id", "0"));
this.TOKEN = config.getOrDefault("token", "");
}
} catch (Exception e) {
plugin.logger.error("parsing config", e);
} }
} }
@ -138,9 +120,10 @@ public class TGBridge {
if (message.text() != null && !message.text().isEmpty()) { if (message.text() != null && !message.text().isEmpty()) {
String msgText = message.text(); String msgText = message.text();
if (msgText.startsWith("/")) { if (msgText.startsWith("/")) {
String[] s = msgText.split("(@[A-Za-z0-9_]bot)?[\t \n]+", 2); String[] s = msgText.split("((@\\w+bot)|(@\\w+bot)?[\t \n]+)", 2);
String command = s[0]; String command = s[0];
@Nullable String arg = s.length == 2 ? s[1] : null; @Nullable String arg = s.length == 2 ? s[1] : null;
System.out.println(command);
switch (command) { switch (command) {
case "/list" -> outbound(genOnlineStatus(), ParseMode.MarkdownV2); case "/list" -> outbound(genOnlineStatus(), ParseMode.MarkdownV2);
case "/setpin" -> { case "/setpin" -> {
@ -188,6 +171,22 @@ public class TGBridge {
} }
}, ParseMode.MarkdownV2 }, ParseMode.MarkdownV2
); );
case "/shutdown" -> {
if (server.getAllPlayers().isEmpty()) {
ServerCloser.INSTANCE.fastShutdown();
outbound("server will shutdown in 1 minute.\nuse /fuck to cancel.");
} else {
outbound("still player online, can't shutdown.");
}
}
case "/fuck" -> {
if (server.getAllPlayers().isEmpty()) {
ServerCloser.INSTANCE.slowShutdown();
outbound("shutdown timer has been set to 60 minutes.");
} else {
outbound("still player online, can't shutdown.");
}
}
default -> {} default -> {}
} }
} }
@ -307,7 +306,7 @@ public class TGBridge {
} }
String result = String.join("\n", out); String result = String.join("\n", out);
if (shutdownCountMinutes < 0) return result; if (shutdownCountMinutes < 0) return result;
if (shutdownCountMinutes == 0) return "server is shutdown\\.%n%s".formatted(result); if (shutdownCountMinutes == 0 || PROXY_SHUT_DOWN) return "server already shutdown\\.%n%s".formatted(result);
return "server will shutdown after %s minute\\.%n%s".formatted(shutdownCountMinutes, result); return "server will shutdown after %s minute\\.%n%s".formatted(shutdownCountMinutes, result);
} }
@ -431,14 +430,14 @@ public class TGBridge {
} catch (InterruptedException ignored) {} } catch (InterruptedException ignored) {}
if (oldestRequest == null) { if (oldestRequest == null) {
plugin.logger.warn("updateRequests.take() return a null value, why?"); plugin.logger.warn("updateRequests.take() return a null value, why?");
sleep(1000); sleep(10000);
continue; continue;
} }
updateRequests.clear(); updateRequests.clear();
if (!updateOnlineStatus()) { if (!updateOnlineStatus()) {
updateRequests.add(oldestRequest); // 更新失败 回去吧您内 updateRequests.add(oldestRequest); // 更新失败 回去吧您内
sleep(1000); sleep(10000);
} }
} }
}).start(); }).start();
@ -479,7 +478,8 @@ public class TGBridge {
} }
protected boolean setOnlineStatusNotAvailable() { protected boolean setOnlineStatusNotAvailable() {
return editOnlineStatusMessage("proxy already shutdown"); return updateOnlineStatus();
// return editOnlineStatusMessage("proxy already shutdown");
} }
private static final Gson prettyGson = new GsonBuilder().setPrettyPrinting().create(); private static final Gson prettyGson = new GsonBuilder().setPrettyPrinting().create();
@ -490,8 +490,11 @@ public class TGBridge {
} }
BaseResponse response; BaseResponse response;
try { try {
response = bot.execute(new EditMessageText(CHAT_ID, ONLINE_STATUS_MESSAGE_ID, markdownText).parseMode(ParseMode.MarkdownV2)); response = bot.execute(new EditMessageText(CHAT_ID, ONLINE_STATUS_MESSAGE_ID, markdownText).parseMode(ParseMode.MarkdownV2).disableWebPagePreview(true));
if (!response.isOk()) { if (!response.isOk()) {
if (response.description().equals("Bad Request: message is not modified: specified new message content and reply markup are exactly the same as a current content and reply markup of the message")) {
return true;
}
String responseJSON = prettyGson.toJson(response); String responseJSON = prettyGson.toJson(response);
plugin.logger.warn("update failed: {}", responseJSON); plugin.logger.warn("update failed: {}", responseJSON);
} }
@ -526,7 +529,7 @@ public class TGBridge {
protected JoinLeftAnnounceMessage(String firstMessage) { protected JoinLeftAnnounceMessage(String firstMessage) {
text = new StringBuilder(firstMessage); text = new StringBuilder(firstMessage);
time = System.currentTimeMillis(); time = System.currentTimeMillis();
timeMinute = time/MINUTE; timeMinute = time / MINUTE;
sendAnnounceMessage(); sendAnnounceMessage();
} }