Compare commits
3 commits
Author | SHA1 | Date | |
---|---|---|---|
d0ecef7b37 | |||
b51a964033 | |||
ee63121964 |
19 changed files with 36 additions and 1713 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,5 +1,4 @@
|
|||
.idea/
|
||||
out/
|
||||
target/
|
||||
*.iml
|
||||
/dependency-reduced-pom.xml
|
||||
*.iml
|
|
@ -1,8 +1,6 @@
|
|||
# VTools
|
||||
Tools for Velocity proxy server.
|
||||
|
||||
add telegram/auto shutdown support for spec server.
|
||||
|
||||
## Commands
|
||||
|Command|What is does?|Permission|
|
||||
|-------|-------------|----------|
|
||||
|
|
25
pom.xml
25
pom.xml
|
@ -17,26 +17,6 @@
|
|||
<target>17</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.4.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactSet>
|
||||
<excludes>
|
||||
<exclude>com.velocitypowered:velocity-api</exclude>
|
||||
</excludes>
|
||||
</artifactSet>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
@ -54,11 +34,6 @@
|
|||
<version>3.2.0-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.pengrad</groupId>
|
||||
<artifactId>java-telegram-bot-api</artifactId>
|
||||
<version>6.7.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
package com.alpt.vtools.listeners;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import de.strifel.VTools.VTools;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
public class OnlinePlayerQueryService {
|
||||
|
||||
public static OnlinePlayerQueryService INSTANCE;
|
||||
private final VTools plugin;
|
||||
|
||||
private OnlinePlayerQueryService(VTools plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
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 {
|
||||
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", port), 0);
|
||||
|
||||
server.createContext("/api/getOnlinePlayers", exchange -> {
|
||||
Collection<Player> players = plugin.getServer().getAllPlayers();
|
||||
ArrayList<String> playerNames = new ArrayList<>(players.size());
|
||||
for (Player player : players) {
|
||||
playerNames.add(player.getUsername());
|
||||
}
|
||||
exchange.getResponseHeaders().set("Content-Type", "application/json");
|
||||
exchange.sendResponseHeaders(200, 0);
|
||||
|
||||
try (OutputStream os = exchange.getResponseBody()) {
|
||||
String response = gson.toJson(playerNames);
|
||||
os.write(response.getBytes());
|
||||
}
|
||||
});
|
||||
|
||||
server.start();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public static void main(String[] args) {
|
||||
ArrayList<String> arrayList = new ArrayList<>();
|
||||
arrayList.add("jerry");
|
||||
System.out.println(gson.toJson(arrayList));
|
||||
}
|
||||
}
|
|
@ -1,210 +0,0 @@
|
|||
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 java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
public class ServerCloser {
|
||||
private static final long MINUTE = 60L * 1000;
|
||||
|
||||
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 void close() {
|
||||
if (!INSTANCE.server.getAllPlayers().isEmpty()) {
|
||||
plugin.logger.error("ServerCloser: 定时器到点时发现服务器有人。这不应发生,因为定时器本应该被打断。");
|
||||
TGBridge.error("ServerCloser: #bug @NaAlOH4 定时器到点时发现服务器有人。这不应发生,因为定时器本应该被打断。");
|
||||
return;
|
||||
}
|
||||
String apiUrl = plugin.getConfigOrDefault("azure_api_url", "https://example.com/");
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{"systemd-run", "--description=pymcd_stop", "--quiet", "--user", "--collect", "-p", "StandardInput=data", "-p", "StandardInputData=c2V0IC1lCnA9IiQocGdyZXAgLWYgcHltY2QucHkgLUFvKSIKa2lsbCAtSU5UICIkcCIKdGFpbCAtLXBpZD0iJHAiIC1mIC9kZXYvbnVsbApjdXJsIC1zIC1IICJDb250ZW50LVR5cGU6IGFwcGxpY2F0aW9uL2pzb24iIC1kICJ7XCJhY3Rpb25cIjpcInN0b3BcIn0iICIkMSIK", "bash", "-s", apiUrl});
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
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).reason("收到关机命令已经过了 %s 分钟,即将关机。").noLastWarning().start());
|
||||
}
|
||||
}
|
||||
|
||||
public void slowShutdown() {
|
||||
synchronized (lock) {
|
||||
while (!lock.isEmpty()) {
|
||||
lock.poll().cancel();
|
||||
}
|
||||
lock.add(new Counter(INSTANCE, 60).reason("虽然关机被取消,但 %s 分钟内依旧没有玩家上线,即将关机。").start());
|
||||
}
|
||||
}
|
||||
|
||||
public void noShutdown() {
|
||||
synchronized (lock) {
|
||||
while (!lock.isEmpty()) {
|
||||
lock.poll().cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Counter {
|
||||
private final ServerCloser instance;
|
||||
private final int totalMin;
|
||||
private int minLeft;
|
||||
private boolean canceled = false;
|
||||
private String reason;
|
||||
private static final String DEFAULT_REASON = "距离上一个玩家离开已经过了 %s 分钟,即将关机。";
|
||||
private boolean lastWarning = true;
|
||||
|
||||
protected Counter(ServerCloser instance, int minute) {
|
||||
this.instance = instance;
|
||||
totalMin = minute;
|
||||
minLeft = minute;
|
||||
this.reason = DEFAULT_REASON;
|
||||
}
|
||||
|
||||
public Counter reason(String reason) {
|
||||
this.reason = reason;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Counter noLastWarning() {
|
||||
lastWarning = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
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);
|
||||
switch (minLeft) {
|
||||
case 1 -> {
|
||||
String msg = "服务器即将在一分钟后关机,使用 /fuck 以取消。";
|
||||
instance.plugin.logger.info(msg);
|
||||
if (lastWarning) {
|
||||
TGBridge.log(msg);
|
||||
}
|
||||
}
|
||||
case 0 -> {
|
||||
String msg = "ServerCloser: " + reason.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);
|
||||
}
|
||||
|
||||
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--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,241 +0,0 @@
|
|||
package com.alpt.vtools.utils;
|
||||
|
||||
import com.pengrad.telegrambot.model.Message;
|
||||
import com.pengrad.telegrambot.model.MessageEntity;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class MarkdownString {
|
||||
|
||||
private MarkdownString() {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
/**
|
||||
* 这里的 format 假设 BiFunction 的输入都是转义过的。
|
||||
*/
|
||||
private static final Map<MessageEntity.Type, BiFunction<StringBuilder, MessageEntity, StringBuilder>> formats;
|
||||
|
||||
static {
|
||||
Map<MessageEntity.Type, BiFunction<StringBuilder, MessageEntity, StringBuilder>> map = new EnumMap<>(MessageEntity.Type.class);
|
||||
|
||||
map.put(MessageEntity.Type.bold, (s, e) -> {
|
||||
assert e.type() == MessageEntity.Type.bold;
|
||||
return s.insert(0, "*").append("*");
|
||||
});
|
||||
map.put(MessageEntity.Type.strikethrough, (s, e) -> {
|
||||
assert e.type() == MessageEntity.Type.strikethrough;
|
||||
return s.insert(0, "~").append("~");
|
||||
});
|
||||
map.put(MessageEntity.Type.spoiler, (s, e) -> {
|
||||
assert e.type() == MessageEntity.Type.spoiler;
|
||||
return s.insert(0, "||").append("||");
|
||||
});
|
||||
|
||||
|
||||
map.put(MessageEntity.Type.italic, (s, e) -> {
|
||||
assert e.type() == MessageEntity.Type.italic;
|
||||
return s.insert(0, "\r_\r").append("\r_\r"); // todo: 精简一下 到底哪里需要加\r
|
||||
});
|
||||
map.put(MessageEntity.Type.underline, (s, e) -> {
|
||||
assert e.type() == MessageEntity.Type.underline;
|
||||
return s.insert(0, "\r__\r").append("\r__\r");
|
||||
});
|
||||
|
||||
// https://core.telegram.org/bots/api#formatting-options
|
||||
// 上面这几个套娃友好,自身随意套娃,但不能和等宽组合
|
||||
|
||||
|
||||
map.put(MessageEntity.Type.code, (s, e) -> {
|
||||
assert e.type() == MessageEntity.Type.code;
|
||||
return s.insert(0, "`").append("`");
|
||||
});
|
||||
map.put(MessageEntity.Type.pre, (rawStr, messageEntity) -> {
|
||||
assert messageEntity.type() == MessageEntity.Type.pre;
|
||||
rawStr.insert(0, "\n");
|
||||
String language = messageEntity.language();
|
||||
if (language != null && language.length() > 0) {
|
||||
rawStr.insert(0, language);
|
||||
}
|
||||
return rawStr.insert(0, "```").append("\n```");
|
||||
});
|
||||
|
||||
// 等宽(pre 和 code)完全禁止套娃
|
||||
|
||||
map.put(MessageEntity.Type.text_link, (s, e) -> {
|
||||
assert e.type() == MessageEntity.Type.text_link;
|
||||
return s.insert(0, "[").append("](").append(escapeStr(e.url())).append(")");
|
||||
});
|
||||
map.put(MessageEntity.Type.text_mention, (s, e) -> {
|
||||
assert e.type() == MessageEntity.Type.text_mention;
|
||||
if (e.user() == null || e.user().id() == null) {
|
||||
return s; // 没有id时爆炸(
|
||||
}
|
||||
return s.insert(0, "[").append("](").append("tg://user?id=").append(e.user().id()).append(")");
|
||||
});
|
||||
|
||||
// 链接类不能和链接类套娃,但是可以和加粗等套娃友好的组合
|
||||
|
||||
formats = Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
|
||||
private static class StringBlock {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StringBlock{" +
|
||||
"text='" + text + '\'' +
|
||||
", entities=" + entities +
|
||||
", offset=" + offset +
|
||||
'}';
|
||||
}
|
||||
|
||||
private final String text;
|
||||
private Map<MessageEntity.Type, MessageEntity> entities;
|
||||
private final int offset;
|
||||
|
||||
StringBlock(String text, int offset) {
|
||||
this.text = text;
|
||||
this.offset = offset;
|
||||
this.entities = new EnumMap<>(MessageEntity.Type.class);
|
||||
}
|
||||
|
||||
private String getMarkdownTextWithoutLink() { // todo: 压缩 *text1**_text2_* 为 *text1_text2_*
|
||||
// 链接类不能和链接类套娃
|
||||
assert (!entities.containsKey(MessageEntity.Type.text_link)) || (!entities.containsKey(MessageEntity.Type.text_mention));
|
||||
// 等宽(pre 和 code)完全禁止套娃
|
||||
assert (!entities.containsKey(MessageEntity.Type.pre) && !entities.containsKey(MessageEntity.Type.code)) || (entities.size() <= 1);
|
||||
|
||||
StringBuilder s = new StringBuilder(escapeStr(text));
|
||||
for (var entry : entities.entrySet()) {
|
||||
BiFunction<StringBuilder, MessageEntity, StringBuilder> repeater = (sb, e) -> sb;
|
||||
s = formats.getOrDefault(entry.getKey(), repeater).apply(s, entry.getValue()); // 无用赋值...理论上。
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static String markdownString(Message message) {
|
||||
if (message.entities() == null || message.entities().length == 0) return message.text();
|
||||
|
||||
List<StringBlock> stringBlocks = createStringBlocks(message);
|
||||
|
||||
|
||||
StringBuilder s = new StringBuilder();
|
||||
for (int i = 0; i < stringBlocks.size(); i++) {
|
||||
|
||||
|
||||
var stringBlock = stringBlocks.get(i);
|
||||
var entities = stringBlock.entities;
|
||||
String withoutLink = stringBlock.getMarkdownTextWithoutLink();
|
||||
if (entities.containsKey(MessageEntity.Type.text_mention) ||
|
||||
entities.containsKey(MessageEntity.Type.text_link)) {
|
||||
assert (!entities.containsKey(MessageEntity.Type.text_mention) ||
|
||||
!entities.containsKey(MessageEntity.Type.text_link));
|
||||
StringBuilder linkText = new StringBuilder();
|
||||
MessageEntity entity = entities.containsKey(MessageEntity.Type.text_mention) ?
|
||||
stringBlock.entities.get(MessageEntity.Type.text_mention) :
|
||||
stringBlock.entities.get(MessageEntity.Type.text_link);
|
||||
assert entity.offset() == stringBlock.offset;
|
||||
int end = entity.length() + entity.offset();
|
||||
|
||||
while (true) {
|
||||
linkText.append(stringBlock.getMarkdownTextWithoutLink());
|
||||
if (stringBlock.text.length() + stringBlock.offset == end) {
|
||||
break;
|
||||
}
|
||||
if (stringBlock.text.length() + stringBlock.offset > end) {
|
||||
throw new IllegalStateException("奶冰可爱捏");
|
||||
}
|
||||
i++;
|
||||
stringBlock = stringBlocks.get(i);
|
||||
}
|
||||
|
||||
s.append(formats.get(entity.type()).apply(linkText, entity));
|
||||
} else {
|
||||
s.append(withoutLink);
|
||||
}
|
||||
}
|
||||
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 一组 StringBlock,被完全切开的文本,每部分都和相邻部分格式不一样,顺序的。
|
||||
*/
|
||||
private static List<StringBlock> createStringBlocks(Message message) {
|
||||
List<StringBlock> stringBlocks = new ArrayList<>();
|
||||
Set<Integer> splitPoints = new HashSet<>();
|
||||
|
||||
// 定位切割点
|
||||
for (MessageEntity entity : message.entities()) {
|
||||
if (formats.containsKey(entity.type())) {
|
||||
Integer offset = entity.offset();
|
||||
Integer length = entity.length();
|
||||
splitPoints.add(offset);
|
||||
splitPoints.add(offset + length);
|
||||
}
|
||||
}
|
||||
|
||||
// 切割文本
|
||||
String text = message.text();
|
||||
List<Integer> sortedSplitPoints = new ArrayList<>(splitPoints);
|
||||
Collections.sort(sortedSplitPoints);
|
||||
|
||||
int start = 0;
|
||||
for (int splitPoint : sortedSplitPoints) {
|
||||
if (splitPoint == 0) continue;// 跳过第一个空的
|
||||
stringBlocks.add(new StringBlock(text.substring(start, splitPoint), start));
|
||||
start = splitPoint;
|
||||
}
|
||||
if (start < text.length()) {
|
||||
stringBlocks.add(new StringBlock(text.substring(start), start));
|
||||
}
|
||||
|
||||
// 给文本重新赋格式(MessageEntity)
|
||||
for (MessageEntity entity : message.entities()) {
|
||||
if (!formats.containsKey(entity.type())) {
|
||||
continue;
|
||||
}
|
||||
for (StringBlock stringBlock : stringBlocks) {
|
||||
int blockStart = stringBlock.offset;
|
||||
int blockEnd = blockStart + stringBlock.text.length();
|
||||
|
||||
int entityStart = entity.offset();
|
||||
int entityEnd = entityStart + entity.length();
|
||||
|
||||
assert (blockStart < blockEnd) && (entityStart < entityEnd);
|
||||
if (blockStart >= entityEnd || blockEnd <= entityStart) {
|
||||
continue;
|
||||
}
|
||||
assert (blockStart >= entityStart) && (blockEnd <= entityEnd) : String.format("%s,%s,%s,%s", blockStart, blockEnd, entityStart, entityEnd);
|
||||
MessageEntity old = stringBlock.entities.put(entity.type(), entity);
|
||||
assert (old == null);
|
||||
}
|
||||
}
|
||||
return stringBlocks;
|
||||
}
|
||||
|
||||
private static final boolean[] SHOULD_ESCAPE = new boolean[128];
|
||||
|
||||
static {
|
||||
// In all other places characters '_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!' must be escaped with the preceding character '\'.
|
||||
char[] chars = {'_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'};
|
||||
for (char c : chars) {
|
||||
SHOULD_ESCAPE[c] = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static String escapeStr(String s) {
|
||||
StringBuilder r = new StringBuilder();
|
||||
for (String character : s.split("")) {
|
||||
int codePoint = character.codePointAt(0);
|
||||
if (codePoint < 128 && SHOULD_ESCAPE[codePoint]) {
|
||||
r.append('\\');
|
||||
}
|
||||
r.append(character);
|
||||
}
|
||||
return r.toString();
|
||||
}
|
||||
}
|
|
@ -1,41 +1,26 @@
|
|||
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.proxy.ProxyInitializeEvent;
|
||||
import com.velocitypowered.api.plugin.Plugin;
|
||||
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import de.strifel.VTools.commands.*;
|
||||
import de.strifel.VTools.listeners.*;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.slf4j.Logger;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
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.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 {
|
||||
private final ProxyServer server;
|
||||
public final Logger logger;
|
||||
public final Path dataDirectory;
|
||||
|
||||
public static final TextColor COLOR_RED = TextColor.fromCSSHexString("#FF5555");
|
||||
public static final TextColor COLOR_YELLOW = TextColor.fromCSSHexString("#FFFF55");
|
||||
public static final TextColor COLOR_ORANGE = TextColor.fromCSSHexString("#FFA500");
|
||||
|
||||
@Inject
|
||||
public VTools(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) {
|
||||
public VTools(ProxyServer server, Logger logger) {
|
||||
this.server = server;
|
||||
this.logger = logger;
|
||||
this.dataDirectory = dataDirectory;
|
||||
}
|
||||
|
||||
|
||||
|
@ -43,57 +28,11 @@ public class VTools {
|
|||
public void onProxyInitialization(ProxyInitializeEvent event) {
|
||||
server.getCommandManager().register("send", new CommandSend(server));
|
||||
server.getCommandManager().register("broadcast", new CommandBroadcast(server), "bc", "alert");
|
||||
server.getCommandManager().register("globalchat", new CommandGlobalChat(server), "global", "g");
|
||||
server.getCommandManager().register("find", new CommandFind(server), "search");
|
||||
server.getCommandManager().register("staffchat", new CommandStaffChat(server), "sc");
|
||||
server.getCommandManager().register("restart", new CommandRestart(server));
|
||||
server.getCommandManager().register("tps", new CommandTp(server), "jump");
|
||||
server.getCommandManager().register("server", new CommandServer(server), "serverv");
|
||||
server.getCommandManager().register("servers", new CommandServers(server), "allservers");
|
||||
loadConfig();
|
||||
new TGBridge(this).register();
|
||||
new PlayerStatus(this).register();
|
||||
new GlobalChat(this).register();
|
||||
ServerCloser.createInstance(this);
|
||||
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() {
|
||||
return server;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,8 +5,6 @@ import com.velocitypowered.api.command.SimpleCommand;
|
|||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -31,12 +29,7 @@ public class CommandFind implements SimpleCommand {
|
|||
if (strings.length == 1) {
|
||||
Optional<Player> player = server.getPlayer(strings[0]);
|
||||
if (player.isPresent() && player.get().getCurrentServer().isPresent()) {
|
||||
String serverName = player.get().getCurrentServer().get().getServerInfo().getName();
|
||||
commandSource.sendMessage(Component.empty()
|
||||
.append(Component.text("Player " + strings[0] + " is on ").color(COLOR_YELLOW))
|
||||
.append(Component.text(serverName).clickEvent(ClickEvent.runCommand("/server " + serverName)).color(NamedTextColor.GRAY))
|
||||
.append(Component.text("!").color(COLOR_YELLOW))
|
||||
);
|
||||
commandSource.sendMessage(Component.text("Player " + strings[0] + " is on " + player.get().getCurrentServer().get().getServerInfo().getName() + "!").color(COLOR_YELLOW));
|
||||
} else {
|
||||
commandSource.sendMessage(Component.text("The player is not online!").color(COLOR_YELLOW));
|
||||
}
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
package de.strifel.VTools.commands;
|
||||
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.command.SimpleCommand;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static de.strifel.VTools.VTools.COLOR_RED;
|
||||
|
||||
public class CommandGlobalChat implements SimpleCommand {
|
||||
private final ProxyServer server;
|
||||
|
||||
public CommandGlobalChat(ProxyServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Invocation invocation) {
|
||||
CommandSource commandSource = invocation.source();
|
||||
String[] strings = invocation.arguments();
|
||||
if (strings.length > 0) {
|
||||
String commandSourceName = "console";
|
||||
RegisteredServer senderServer = null;
|
||||
if (commandSource instanceof Player) {
|
||||
Player player = (Player) commandSource;
|
||||
commandSourceName = player.getUsername();
|
||||
if (player.getCurrentServer().isPresent()) {
|
||||
senderServer = player.getCurrentServer().get().getServer();
|
||||
}
|
||||
}
|
||||
String message = String.join(" ", strings).replace("&", "§");
|
||||
for (Player player : server.getAllPlayers()) {
|
||||
RegisteredServer receiverServer = null;
|
||||
if (player.getCurrentServer().isPresent()) {
|
||||
receiverServer = player.getCurrentServer().get().getServer();
|
||||
}
|
||||
if (senderServer != null && senderServer.equals(receiverServer)) {
|
||||
player.sendMessage(Component.text(String.format("[global] <%s> %s", commandSourceName, message)));
|
||||
}
|
||||
else {
|
||||
String s = senderServer == null ? "?" : senderServer.getServerInfo().getName();
|
||||
player.sendMessage(Component.text(String.format("[global] [%s] <%s> %s", s, commandSourceName, message)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
commandSource.sendMessage(Component.text("Usage: /globalchat <message>").color(COLOR_RED));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> suggest(Invocation invocation) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(Invocation invocation) {
|
||||
return invocation.source().hasPermission("vtools.globalchat.old");
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package de.strifel.VTools.commands;
|
||||
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.command.SimpleCommand;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
|
@ -18,6 +19,7 @@ public class CommandRestart implements SimpleCommand {
|
|||
|
||||
@Override
|
||||
public void execute(SimpleCommand.Invocation invocation) {
|
||||
CommandSource commandSource = invocation.source();
|
||||
String[] strings = invocation.arguments();
|
||||
|
||||
if (strings.length > 0) {
|
||||
|
@ -31,7 +33,7 @@ public class CommandRestart implements SimpleCommand {
|
|||
|
||||
@Override
|
||||
public List<String> suggest(SimpleCommand.Invocation invocation) {
|
||||
return new ArrayList<>();
|
||||
return new ArrayList<String>();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -9,7 +9,10 @@ import com.velocitypowered.api.proxy.ServerConnection;
|
|||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -36,20 +39,23 @@ public class CommandSend implements SimpleCommand {
|
|||
for (Player player : server.getAllPlayers()) {
|
||||
oPlayer.add(player);
|
||||
}
|
||||
} else if (strings[0].equals("current")) {
|
||||
}
|
||||
else if (strings[0].equals("current")) {
|
||||
if (commandSource instanceof Player) {
|
||||
Player playerSource = (Player) commandSource;
|
||||
Player playerSource = (Player)commandSource;
|
||||
Optional<ServerConnection> conn = playerSource.getCurrentServer();
|
||||
if (conn.isPresent()) {
|
||||
for (Player player : conn.get().getServer().getPlayersConnected()) {
|
||||
oPlayer.add(player);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
commandSource.sendMessage(Component.text("Command is only for players.").color(COLOR_RED));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
Optional<Player> p = server.getPlayer(strings[0]);
|
||||
if (p.isPresent()) {
|
||||
oPlayer.add(p.get());
|
||||
|
@ -88,9 +94,9 @@ public class CommandSend implements SimpleCommand {
|
|||
}
|
||||
}));
|
||||
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"));
|
||||
commandSource.sendMessage(Component.text("Send Results:%n%s".formatted(sendResults)).color(COLOR_YELLOW));
|
||||
commandSource.sendMessage(Component.text(String.format("Send Results:\n%s", sendResults)).color(COLOR_YELLOW));
|
||||
}).start();
|
||||
} else {
|
||||
commandSource.sendMessage(Component.text("The server or user does not exist!").color(COLOR_RED));
|
||||
|
@ -102,26 +108,21 @@ public class CommandSend implements SimpleCommand {
|
|||
|
||||
public List<String> suggest(SimpleCommand.Invocation invocation) {
|
||||
String[] currentArgs = invocation.arguments();
|
||||
switch (currentArgs.length) {
|
||||
case 0, 1 -> {
|
||||
List<String> args = new ArrayList<>(server.getPlayerCount() + 2);
|
||||
args.add("all");
|
||||
args.add("current");
|
||||
for (Player player : server.getAllPlayers()) {
|
||||
args.add(player.getUsername());
|
||||
}
|
||||
return currentArgs.length==0?args:
|
||||
args.stream().filter(arg->arg.regionMatches(true,0,currentArgs[0],0,currentArgs[0].length())).toList();
|
||||
|
||||
List<String> arg = new ArrayList<String>();
|
||||
if (currentArgs.length <= 1) {
|
||||
arg.add("all");
|
||||
arg.add("current");
|
||||
for (Player player : server.getAllPlayers()) {
|
||||
arg.add(player.getUsername());
|
||||
}
|
||||
case 2 -> {
|
||||
return server.getAllServers().stream().map(s -> s.getServerInfo().getName())
|
||||
.filter(name -> name.regionMatches(true, 0, currentArgs[1], 0, currentArgs[1].length()))
|
||||
.toList();
|
||||
}
|
||||
default -> {
|
||||
return new ArrayList<>(0);
|
||||
return arg;
|
||||
} else if (currentArgs.length == 2) {
|
||||
for (RegisteredServer server : server.getAllServers()) {
|
||||
arg.add(server.getServerInfo().getName());
|
||||
}
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
|
||||
public boolean hasPermission(SimpleCommand.Invocation invocation) {
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
package de.strifel.VTools.commands;
|
||||
|
||||
import com.velocitypowered.api.command.SimpleCommand;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import de.strifel.VTools.commands.utils.ServerUtil;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import static de.strifel.VTools.VTools.COLOR_RED;
|
||||
|
||||
public class CommandServer extends ServerUtil implements SimpleCommand {
|
||||
|
||||
private final ProxyServer server;
|
||||
|
||||
public CommandServer(ProxyServer server) {
|
||||
super(server);
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final SimpleCommand.Invocation invocation) {
|
||||
if (invocation.source() instanceof Player) {
|
||||
if (
|
||||
invocation.source().hasPermission("velocity.command.server") ||
|
||||
server.getAllPlayers().stream().anyMatch(p -> p.hasPermission("vtools.send") && p.hasPermission("vtools.send.recvmsg"))) {
|
||||
super.execute(invocation);
|
||||
}
|
||||
else {
|
||||
invocation.source().sendMessage(Component.text("Supervisor offline.").color(COLOR_RED));
|
||||
}
|
||||
}
|
||||
else {
|
||||
invocation.source().sendMessage(Component.text("Command is only for players.").color(COLOR_RED));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(Invocation commandInvocation) {
|
||||
return commandInvocation.source().hasPermission("vtools.server.auto");
|
||||
}
|
||||
}
|
|
@ -34,7 +34,7 @@ public class CommandServers implements SimpleCommand {
|
|||
|
||||
@Override
|
||||
public List<String> suggest(SimpleCommand.Invocation invocation) {
|
||||
return new ArrayList<>();
|
||||
return new ArrayList<String>();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -26,11 +26,11 @@ public class CommandTp implements SimpleCommand {
|
|||
CommandSource commandSource = commandInvocation.source();
|
||||
String[] strings = commandInvocation.arguments();
|
||||
|
||||
if (commandSource instanceof Player playerCommandSource) {
|
||||
if (commandSource instanceof Player) {
|
||||
if (strings.length == 1) {
|
||||
Optional<Player> player = server.getPlayer(strings[0]);
|
||||
if (player.isPresent()) {
|
||||
player.get().getCurrentServer().ifPresent(serverConnection -> playerCommandSource.createConnectionRequest(serverConnection.getServer()).fireAndForget());
|
||||
player.get().getCurrentServer().ifPresent(serverConnection -> ((Player) commandSource).createConnectionRequest(serverConnection.getServer()).fireAndForget());
|
||||
commandSource.sendMessage(Component.text("Connecting to the server of " + strings[0]).color(COLOR_YELLOW));
|
||||
} else {
|
||||
commandSource.sendMessage(Component.text("Player does not exists.").color(COLOR_RED));
|
||||
|
@ -56,6 +56,6 @@ public class CommandTp implements SimpleCommand {
|
|||
|
||||
@Override
|
||||
public boolean hasPermission(Invocation commandInvocation) {
|
||||
return commandInvocation.source().hasPermission("VTools.tps") || commandInvocation.source().hasPermission("vtools.server.auto");
|
||||
return commandInvocation.source().hasPermission("VTools.tps");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,176 +0,0 @@
|
|||
package de.strifel.VTools.commands.utils;
|
||||
|
||||
import static net.kyori.adventure.text.event.HoverEvent.showText;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.command.SimpleCommand;
|
||||
import com.velocitypowered.api.permission.Tristate;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
// https://github.com/PaperMC/Velocity/blob/40b76c633276fcd6aea165baeae74039b2d059c4/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/ServerCommand.java
|
||||
|
||||
/**
|
||||
* Implements Velocity's {@code /server} command.
|
||||
*/
|
||||
public abstract class ServerUtil implements SimpleCommand {
|
||||
|
||||
public static final int MAX_SERVERS_TO_LIST = 50;
|
||||
private final ProxyServer server;
|
||||
|
||||
public ServerUtil(ProxyServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final SimpleCommand.Invocation invocation) {
|
||||
final CommandSource source = invocation.source();
|
||||
final String[] args = invocation.arguments();
|
||||
|
||||
if (!(source instanceof Player)) {
|
||||
source.sendMessage(CommandMessages.PLAYERS_ONLY);
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = (Player) source;
|
||||
if (args.length == 1) {
|
||||
// Trying to connect to a server.
|
||||
String serverName = args[0];
|
||||
Optional<RegisteredServer> toConnect = server.getServer(serverName);
|
||||
if (toConnect.isEmpty()) {
|
||||
player.sendMessage(CommandMessages.SERVER_DOES_NOT_EXIST.args(Component.text(serverName)));
|
||||
return;
|
||||
}
|
||||
|
||||
player.createConnectionRequest(toConnect.get()).fireAndForget();
|
||||
} else {
|
||||
outputServerInformation(player);
|
||||
}
|
||||
}
|
||||
|
||||
private void outputServerInformation(Player executor) {
|
||||
String currentServer = executor.getCurrentServer().map(ServerConnection::getServerInfo)
|
||||
.map(ServerInfo::getName).orElse("<unknown>");
|
||||
executor.sendMessage(Component.translatable(
|
||||
"velocity.command.server-current-server",
|
||||
NamedTextColor.YELLOW,
|
||||
Component.text(currentServer)));
|
||||
|
||||
List<RegisteredServer> servers = BuiltinCommandUtil.sortedServerList(server);
|
||||
if (servers.size() > MAX_SERVERS_TO_LIST) {
|
||||
executor.sendMessage(Component.translatable(
|
||||
"velocity.command.server-too-many", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
// Assemble the list of servers as components
|
||||
TextComponent.Builder serverListBuilder = Component.text()
|
||||
.append(Component.translatable("velocity.command.server-available",
|
||||
NamedTextColor.YELLOW))
|
||||
.append(Component.space());
|
||||
for (int i = 0; i < servers.size(); i++) {
|
||||
RegisteredServer rs = servers.get(i);
|
||||
serverListBuilder.append(formatServerComponent(currentServer, rs));
|
||||
if (i != servers.size() - 1) {
|
||||
serverListBuilder.append(Component.text(", ", NamedTextColor.GRAY));
|
||||
}
|
||||
}
|
||||
|
||||
executor.sendMessage(serverListBuilder.build());
|
||||
}
|
||||
|
||||
private TextComponent formatServerComponent(String currentPlayerServer, RegisteredServer server) {
|
||||
ServerInfo serverInfo = server.getServerInfo();
|
||||
TextComponent serverTextComponent = Component.text(serverInfo.getName());
|
||||
|
||||
int connectedPlayers = server.getPlayersConnected().size();
|
||||
TranslatableComponent playersTextComponent;
|
||||
if (connectedPlayers == 1) {
|
||||
playersTextComponent = Component.translatable(
|
||||
"velocity.command.server-tooltip-player-online");
|
||||
} else {
|
||||
playersTextComponent = Component.translatable(
|
||||
"velocity.command.server-tooltip-players-online");
|
||||
}
|
||||
playersTextComponent = playersTextComponent.args(Component.text(connectedPlayers));
|
||||
if (serverInfo.getName().equals(currentPlayerServer)) {
|
||||
serverTextComponent = serverTextComponent.color(NamedTextColor.GREEN)
|
||||
.hoverEvent(
|
||||
showText(
|
||||
Component.translatable("velocity.command.server-tooltip-current-server")
|
||||
.append(Component.newline())
|
||||
.append(playersTextComponent))
|
||||
);
|
||||
} else {
|
||||
serverTextComponent = serverTextComponent.color(NamedTextColor.GRAY)
|
||||
.clickEvent(ClickEvent.runCommand("/server " + serverInfo.getName()))
|
||||
.hoverEvent(
|
||||
showText(
|
||||
Component.translatable("velocity.command.server-tooltip-offer-connect-server")
|
||||
.append(Component.newline())
|
||||
.append(playersTextComponent))
|
||||
);
|
||||
}
|
||||
return serverTextComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> suggest(final SimpleCommand.Invocation invocation) {
|
||||
final String[] currentArgs = invocation.arguments();
|
||||
Stream<String> possibilities = server.getAllServers().stream()
|
||||
.map(rs -> rs.getServerInfo().getName());
|
||||
|
||||
if (currentArgs.length == 0) {
|
||||
return possibilities.collect(Collectors.toList());
|
||||
} else if (currentArgs.length == 1) {
|
||||
return possibilities
|
||||
.filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length()))
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(final SimpleCommand.Invocation invocation) {
|
||||
return invocation.source().getPermissionValue("velocity.command.server") != Tristate.FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/PaperMC/Velocity/blob/b0862d2d16c4ba7560d3f24c824d78793ac3d9e0/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/CommandMessages.java
|
||||
|
||||
class CommandMessages {
|
||||
|
||||
public static final TranslatableComponent PLAYERS_ONLY = Component.translatable(
|
||||
"velocity.command.players-only", NamedTextColor.RED);
|
||||
public static final TranslatableComponent SERVER_DOES_NOT_EXIST = Component.translatable(
|
||||
"velocity.command.server-does-not-exist", NamedTextColor.RED);
|
||||
}
|
||||
|
||||
// https://github.com/PaperMC/Velocity/blob/b0862d2d16c4ba7560d3f24c824d78793ac3d9e0/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/BuiltinCommandUtil.java
|
||||
|
||||
class BuiltinCommandUtil {
|
||||
|
||||
private BuiltinCommandUtil() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
static List<RegisteredServer> sortedServerList(ProxyServer proxy) {
|
||||
List<RegisteredServer> servers = new ArrayList<>(proxy.getAllServers());
|
||||
servers.sort(Comparator.comparing(RegisteredServer::getServerInfo));
|
||||
return Collections.unmodifiableList(servers);
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package de.strifel.VTools.listeners;
|
||||
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.player.PlayerChatEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import de.strifel.VTools.VTools;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
public class GlobalChat {
|
||||
private final VTools plugin;
|
||||
private final ProxyServer server;
|
||||
|
||||
public GlobalChat(VTools plugin) {
|
||||
this.plugin = plugin;
|
||||
this.server = plugin.getServer();
|
||||
}
|
||||
|
||||
public void register() {
|
||||
server.getEventManager().register(plugin, this);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPlayerChat(PlayerChatEvent event) {
|
||||
RegisteredServer senderServer = null;
|
||||
if (event.getPlayer().getCurrentServer().isPresent()) {
|
||||
senderServer = event.getPlayer().getCurrentServer().get().getServer();
|
||||
if (event.getPlayer().hasPermission("vtools.globalchat")) {
|
||||
for (Player player : server.getAllPlayers()) {
|
||||
if (player.hasPermission("vtools.globalchat")) {
|
||||
if (player.getCurrentServer().isPresent()) {
|
||||
RegisteredServer receiverServer = player.getCurrentServer().get().getServer();
|
||||
if (!senderServer.equals(receiverServer)) {
|
||||
player.sendMessage(Component.text(String.format("[%s] <%s> %s", senderServer.getServerInfo().getName(), event.getPlayer().getUsername(), event.getMessage())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!event.getPlayer().hasPermission("vtools.globalchat.bypassbridge")) {
|
||||
TGBridge.INSTANCE.outbound(String.format("[%s] <%s> %s", senderServer == null ? "null" : senderServer.getServerInfo().getName(), event.getPlayer().getUsername(), event.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
package de.strifel.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.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import de.strifel.VTools.VTools;
|
||||
import de.strifel.VTools.listeners.utils.GlistUtil;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
public class PlayerStatus {
|
||||
private final VTools plugin;
|
||||
private final ProxyServer server;
|
||||
|
||||
public PlayerStatus(VTools plugin) {
|
||||
this.plugin = plugin;
|
||||
this.server = plugin.getServer();
|
||||
}
|
||||
|
||||
public void register() {
|
||||
server.getEventManager().register(plugin, this);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onServerConnected(ServerConnectedEvent event) {
|
||||
if (event.getPreviousServer().isEmpty()) {
|
||||
if (event.getPlayer().hasPermission("vtools.listener.playerstatus.atlogin")) {
|
||||
(new GlistUtil(server)).serverCount(event.getPlayer());
|
||||
}
|
||||
}
|
||||
for (Player player : server.getAllPlayers()) {
|
||||
if (player.hasPermission("vtools.listener.playerstatus")) {
|
||||
if (event.getPreviousServer().isEmpty()) {
|
||||
String serverName = event.getServer().getServerInfo().getName();
|
||||
player.sendMessage(Component.empty()
|
||||
.append(Component.text("[velocity] ").color(NamedTextColor.YELLOW))
|
||||
.append(Component.text(String.format("%s joined into ", event.getPlayer().getUsername())))
|
||||
.append(Component.text(serverName).clickEvent(ClickEvent.runCommand("/server " + serverName)).color(NamedTextColor.GRAY)));
|
||||
}
|
||||
else {
|
||||
if (player.hasPermission("vtools.listener.playerstatus.move")) {
|
||||
String serverName1 = event.getServer().getServerInfo().getName();
|
||||
String serverName0 = event.getPreviousServer().get().getServerInfo().getName();
|
||||
player.sendMessage(Component.empty()
|
||||
.append(Component.text("[velocity] ").color(NamedTextColor.YELLOW))
|
||||
.append(Component.text(String.format("%s moved from ", event.getPlayer().getUsername())))
|
||||
.append(Component.text(serverName0).clickEvent(ClickEvent.runCommand("/server " + serverName0)).color(NamedTextColor.GRAY))
|
||||
.append(Component.text(" to "))
|
||||
.append(Component.text(serverName1).clickEvent(ClickEvent.runCommand("/server " + serverName1)).color(NamedTextColor.GRAY)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDisconnect(DisconnectEvent event) {
|
||||
for (Player player : server.getAllPlayers()) {
|
||||
if (player.getUniqueId() != event.getPlayer().getUniqueId() && player.hasPermission("vtools.listener.playerstatus")) {
|
||||
player.sendMessage(Component.empty()
|
||||
.append(Component.text("[velocity] ").color(NamedTextColor.YELLOW))
|
||||
.append(Component.text(String.format("%s left the proxy", event.getPlayer().getUsername()))));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,596 +0,0 @@
|
|||
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.GsonBuilder;
|
||||
import com.pengrad.telegrambot.Callback;
|
||||
import com.pengrad.telegrambot.TelegramBot;
|
||||
import com.pengrad.telegrambot.UpdatesListener;
|
||||
import com.pengrad.telegrambot.model.Message;
|
||||
import com.pengrad.telegrambot.model.Update;
|
||||
import com.pengrad.telegrambot.model.User;
|
||||
import com.pengrad.telegrambot.model.request.ParseMode;
|
||||
import com.pengrad.telegrambot.request.EditMessageText;
|
||||
import com.pengrad.telegrambot.request.GetChat;
|
||||
import com.pengrad.telegrambot.request.SendMessage;
|
||||
import com.pengrad.telegrambot.response.BaseResponse;
|
||||
import com.pengrad.telegrambot.response.GetChatResponse;
|
||||
import com.pengrad.telegrambot.response.SendResponse;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||
import com.velocitypowered.api.event.player.ServerConnectedEvent;
|
||||
import com.velocitypowered.api.event.player.ServerPostConnectEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import de.strifel.VTools.VTools;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class TGBridge {
|
||||
private static final long MINUTE = 60_000;
|
||||
private final VTools plugin;
|
||||
private final ProxyServer server;
|
||||
@SuppressWarnings("java:S3008")
|
||||
protected static TGBridge INSTANCE = null;
|
||||
|
||||
private TelegramBot bot;
|
||||
private String TOKEN = "";
|
||||
private long CHAT_ID = 0L;
|
||||
|
||||
private int ONLINE_STATUS_MESSAGE_ID = -1;
|
||||
|
||||
private long backoffSec = 1L;
|
||||
|
||||
private static final String DIVIDER = "————————\n";
|
||||
/**
|
||||
* markdown escaped
|
||||
*/
|
||||
private String pinNote;
|
||||
|
||||
@SuppressWarnings("java:S3010")
|
||||
public TGBridge(@NotNull VTools plugin) {
|
||||
if (INSTANCE != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
INSTANCE = this;
|
||||
this.plugin = plugin;
|
||||
this.server = plugin.getServer();
|
||||
}
|
||||
|
||||
public void register() {
|
||||
server.getEventManager().register(plugin, this);
|
||||
botInit();
|
||||
initUpdateThread();
|
||||
}
|
||||
|
||||
private void loadConfig() {
|
||||
synchronized (this) {
|
||||
CHAT_ID = Long.parseLong(plugin.getConfigOrDefault("chat_id", "0"));
|
||||
TOKEN = plugin.getConfigOrDefault("token", "");
|
||||
}
|
||||
}
|
||||
|
||||
private void botInit() {
|
||||
loadConfig();
|
||||
if (TOKEN.isEmpty() || CHAT_ID == 0L) return;
|
||||
bot = new TelegramBot(TOKEN);
|
||||
getPinnedMessage();
|
||||
bot.setUpdatesListener(updates -> {
|
||||
backoffSec = 1L;
|
||||
for (Update update : updates) {
|
||||
|
||||
try {
|
||||
if (update == null || update.message() == null) continue;
|
||||
Message message = update.message();
|
||||
if (message.chat() == null || message.chat().id() != CHAT_ID || message.from() == null) continue;
|
||||
|
||||
mergeMessage.abort();
|
||||
|
||||
String text = "";
|
||||
Message replyTo = message.replyToMessage();
|
||||
if (replyTo != null) {
|
||||
text += "[reply > ";
|
||||
String replyText = "";
|
||||
String replyType = getMessageType(replyTo);
|
||||
if (replyTo.text() != null) {
|
||||
replyText += replyTo.text();
|
||||
}
|
||||
if (replyType != null) {
|
||||
replyText += replyType;
|
||||
if (replyTo.caption() != null) {
|
||||
replyText += " \"" + replyTo.caption() + "\"";
|
||||
}
|
||||
}
|
||||
if (replyText.equals("")) {
|
||||
replyText = "?";
|
||||
}
|
||||
text += replyText + "] ";
|
||||
}
|
||||
|
||||
if (message.text() != null && !message.text().isEmpty()) {
|
||||
String msgText = message.text();
|
||||
if (msgText.startsWith("/")) {
|
||||
String[] s = msgText.split("((@\\w+bot)|(@\\w+bot)?[\t \n]+)", 2);
|
||||
String command = s[0];
|
||||
@Nullable String arg = s.length == 2 ? s[1] : null;
|
||||
System.out.println(command);
|
||||
switch (command) {
|
||||
case "/list" -> outbound(genOnlineStatus(), ParseMode.MarkdownV2);
|
||||
case "/setpin" -> {
|
||||
if (arg == null || arg.length() == 0) {
|
||||
if (replyTo == null) {
|
||||
outbound("""
|
||||
usage:
|
||||
use "/setpin" reply a message that from the bot to set that message to pin-message,
|
||||
or use "/setpin <note>" to update current pinned message.""");
|
||||
continue;
|
||||
}
|
||||
ONLINE_STATUS_MESSAGE_ID = replyTo.messageId();
|
||||
updateOnlineStatus();
|
||||
continue;
|
||||
}
|
||||
if (replyTo != null && (replyTo.from() == null || replyTo.messageId() <= 0)) {
|
||||
outbound("must reply a message that from the bot (or reply nothing).");
|
||||
continue;
|
||||
}
|
||||
|
||||
String markdownString = MarkdownString.markdownString(message);
|
||||
|
||||
String shouldBeCommand = markdownString.substring(0, "/setpin ".length());
|
||||
if (!shouldBeCommand.matches("/setpin[\t \n]")) {
|
||||
outbound("\"/setpin\" must be plain text.");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
outbound("old pinned note: \n" + pinNote, ParseMode.MarkdownV2);
|
||||
|
||||
pinNote = markdownString.substring("/setpin ".length());
|
||||
if (replyTo != null) {
|
||||
ONLINE_STATUS_MESSAGE_ID = replyTo.messageId();
|
||||
}
|
||||
updateOnlineStatus();
|
||||
}
|
||||
case "/genpin" -> outbound(genPinMessage(),
|
||||
(sendMessage, sendResponse) -> {
|
||||
if (!sendResponse.isOk()) {
|
||||
plugin.logger.error(String.format("sendMessage error %d: %s", sendResponse.errorCode(), sendResponse.description()));
|
||||
} else {
|
||||
int messageId = sendResponse.message().messageId();
|
||||
ONLINE_STATUS_MESSAGE_ID = messageId > 0 ? messageId : ONLINE_STATUS_MESSAGE_ID;
|
||||
}
|
||||
}, ParseMode.MarkdownV2
|
||||
);
|
||||
case "/shutdown" -> {
|
||||
if (server.getAllPlayers().isEmpty()) {
|
||||
ServerCloser.INSTANCE.fastShutdown();
|
||||
outbound("服务器即将在一分钟后关机,使用 /fuck 以取消。");
|
||||
} else {
|
||||
outbound("still player online, can't shutdown.");
|
||||
}
|
||||
}
|
||||
case "/fuck" -> {
|
||||
if (server.getAllPlayers().isEmpty()) {
|
||||
if("fuck".equalsIgnoreCase(arg)){
|
||||
ServerCloser.INSTANCE.noShutdown();
|
||||
outbound("shutdown timer disabled until next player join & left.");
|
||||
}else {
|
||||
ServerCloser.INSTANCE.slowShutdown();
|
||||
outbound("shutdown timer has been set to 60 minutes.");
|
||||
}
|
||||
} else {
|
||||
outbound("still player online, will not shutdown.");
|
||||
}
|
||||
}
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
text += msgText;
|
||||
}
|
||||
String messageType = getMessageType(message);
|
||||
if (messageType != null) {
|
||||
text += "[" + messageType + (message.caption() != null ? " \"" + message.caption() + "\"]" : "]");
|
||||
}
|
||||
if (!text.equals("")) {
|
||||
tgInbound(message.from(), text);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.logger.error("handling update", e);
|
||||
}
|
||||
}
|
||||
return UpdatesListener.CONFIRMED_UPDATES_ALL;
|
||||
}, (e) -> {
|
||||
plugin.logger.error("getting update", e);
|
||||
plugin.logger.error(String.format("waiting %ds before getting another update", backoffSec));
|
||||
try {Thread.sleep(backoffSec * 1000);} catch (InterruptedException ignored) {}
|
||||
backoffSec *= 2L;
|
||||
if (backoffSec > 3600) {
|
||||
backoffSec = 3600;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static String getMessageType(Message message) {
|
||||
if (message.sticker() != null) {
|
||||
String emoji = message.sticker().emoji();
|
||||
return emoji != null ? "sticker " + emoji : "sticker";
|
||||
}
|
||||
|
||||
if (message.photo() != null) {
|
||||
return "photo";
|
||||
}
|
||||
if (message.audio() != null) {
|
||||
return "audio";
|
||||
}
|
||||
if (message.voice() != null) {
|
||||
return "voice";
|
||||
}
|
||||
if (message.document() != null) {
|
||||
return "document";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean getPinnedMessage() {
|
||||
try {
|
||||
GetChatResponse response = bot.execute(new GetChat(CHAT_ID));
|
||||
Message pinnedMessage = response.chat().pinnedMessage();
|
||||
readOldPinnedMessage(pinnedMessage);
|
||||
updateOnlineStatus();
|
||||
} catch (RuntimeException e) {
|
||||
plugin.logger.error("get group info failed.");
|
||||
throw e;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void readOldPinnedMessage(Message message) {
|
||||
ONLINE_STATUS_MESSAGE_ID = message.messageId();
|
||||
String markdownText = MarkdownString.markdownString(message);
|
||||
|
||||
String[] s = markdownText.split(DIVIDER, 2);
|
||||
pinNote = (s.length == 2) ?
|
||||
s[1] :
|
||||
"\r_\r" + MarkdownString.escapeStr("(use \"/setpin\" <note> to set note here)") + "\r_\r";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return markdown escaped str
|
||||
*/
|
||||
private String genPinMessage() {
|
||||
return (pinNote != null && pinNote.length() != 0) ?
|
||||
genOnlineStatus() + "\n\n" + DIVIDER + pinNote :
|
||||
genOnlineStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return markdown escaped str
|
||||
*/
|
||||
|
||||
private int shutdownCountMinutes = -1;
|
||||
|
||||
public static void setShuttingDown(int minute) {
|
||||
INSTANCE.shutdownCountMinutes = minute;
|
||||
INSTANCE.updateOnlineStatus();
|
||||
}
|
||||
|
||||
private String genOnlineStatus() {
|
||||
ArrayList<String> out = new ArrayList<>();
|
||||
int playerCount = server.getAllPlayers().size();
|
||||
out.add(switch (playerCount) {
|
||||
case 0 -> "nobody here\\.";
|
||||
case 1 -> "only one player online\\.";
|
||||
default -> playerCount + " players online\\.";
|
||||
});
|
||||
List<RegisteredServer> registeredServers = new ArrayList<>(server.getAllServers());
|
||||
for (RegisteredServer registeredServer : registeredServers) {
|
||||
LinkedList<Player> onServer = new LinkedList<>();
|
||||
for (Player player : registeredServer.getPlayersConnected()) {
|
||||
if (!lastDisconnect.equals(player.getUsername())) {
|
||||
onServer.add(player);
|
||||
}
|
||||
}
|
||||
if (!onServer.isEmpty()) {
|
||||
out.add(
|
||||
String.format("\\[%s\\] \\(%d\\): %s",
|
||||
"`" + MarkdownString.escapeStr(registeredServer.getServerInfo().getName()) + "`",
|
||||
onServer.size(),
|
||||
onServer.stream().map(player -> "`" + MarkdownString.escapeStr(player.getUsername()) + "`").collect(Collectors.joining(", ")))
|
||||
);
|
||||
}
|
||||
}
|
||||
String result = String.join("\n", out);
|
||||
if (shutdownCountMinutes < 0) return 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);
|
||||
}
|
||||
|
||||
protected void tgInbound(User user, String content) {
|
||||
inbound(String.format("[tg] <%s> %s", user.lastName() == null ? user.firstName() : String.format("%s %s", user.firstName(), user.lastName()), content));
|
||||
}
|
||||
|
||||
protected void inbound(String content) {
|
||||
for (Player player : server.getAllPlayers()) {
|
||||
if (player.getCurrentServer().isPresent()) {
|
||||
player.sendMessage(Component.text(content));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void outbound(String content, ParseMode parseMode) {
|
||||
outbound(content, (sendMessage, sendResponse) -> {
|
||||
if (!sendResponse.isOk()) {
|
||||
plugin.logger.error(String.format("sendMessage error %d: %s", sendResponse.errorCode(), sendResponse.description()));
|
||||
}
|
||||
}, parseMode);
|
||||
}
|
||||
|
||||
public static void error(String context) {
|
||||
INSTANCE.appendMessage("*" + MarkdownString.escapeStr(context) + "*");
|
||||
}
|
||||
|
||||
public static void log(String context) {
|
||||
INSTANCE.appendMessage("_" + MarkdownString.escapeStr(context) + "_");
|
||||
}
|
||||
|
||||
protected void outbound(String content) {outbound(content, (ParseMode) null);}
|
||||
|
||||
|
||||
protected void outbound(String content, @NotNull BiConsumer<SendMessage, SendResponse> onResponse) {outbound(content, onResponse, null);}
|
||||
|
||||
protected void outbound(String content, @NotNull BiConsumer<SendMessage, SendResponse> onResponse, ParseMode parseMode) {
|
||||
if (bot == null) return;
|
||||
if (content.length() > 4000) {
|
||||
content = content.substring(0, 4000);
|
||||
}
|
||||
|
||||
SendMessage sendMessage = new SendMessage(CHAT_ID, content);
|
||||
if (parseMode != null) {
|
||||
boolean a = sendMessage == sendMessage.parseMode(parseMode);
|
||||
assert a;
|
||||
}
|
||||
bot.execute(sendMessage, new Callback<SendMessage, SendResponse>() {
|
||||
@Override
|
||||
public void onResponse(SendMessage sendMessage, SendResponse sendResponse) {
|
||||
onResponse.accept(sendMessage, sendResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(SendMessage sendMessage, IOException e) {
|
||||
plugin.logger.error("sending message", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onProxyShutdown(ProxyShutdownEvent event) {
|
||||
if (bot == null) return;
|
||||
bot.removeGetUpdatesListener();
|
||||
bot.shutdown();
|
||||
PROXY_SHUT_DOWN = true;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onServerConnected(ServerConnectedEvent event) {
|
||||
if (event.getPreviousServer().isEmpty() && !event.getPlayer().hasPermission("vtools.globalchat.bypassbridge.join")) {
|
||||
String username = event.getPlayer().getUsername();
|
||||
if (lastDisconnect.equals(username)) {
|
||||
lastDisconnect = "";
|
||||
}
|
||||
joinLeftAnnounce(String.format("`%s` joined the server\\.", MarkdownString.escapeStr(username)));
|
||||
}
|
||||
updateRequests.add(new UpdateRequest());
|
||||
}
|
||||
|
||||
private String lastDisconnect = "";
|
||||
|
||||
@Subscribe
|
||||
public void onDisconnect(DisconnectEvent event) {
|
||||
if (!event.getPlayer().hasPermission("vtools.globalchat.bypassbridge.join")) {
|
||||
String username = event.getPlayer().getUsername();
|
||||
if (username != null) {
|
||||
lastDisconnect = username;
|
||||
joinLeftAnnounce(String.format("`%s` left the server\\.", MarkdownString.escapeStr(username)));
|
||||
}
|
||||
}
|
||||
updateRequests.add(new UpdateRequest());
|
||||
}
|
||||
|
||||
|
||||
@Subscribe
|
||||
public void onServerPostConnect(ServerPostConnectEvent event) {
|
||||
updateRequests.add(new UpdateRequest());
|
||||
}
|
||||
|
||||
|
||||
private boolean PROXY_SHUT_DOWN = false;
|
||||
|
||||
private static class UpdateRequest {}
|
||||
|
||||
private LinkedBlockingQueue<UpdateRequest> updateRequests = new LinkedBlockingQueue<>();
|
||||
|
||||
@SuppressWarnings("java:S3776")
|
||||
private void initUpdateThread() {
|
||||
new Thread(() -> {
|
||||
while (true) {
|
||||
if (PROXY_SHUT_DOWN) {
|
||||
setOnlineStatusNotAvailable();
|
||||
return;
|
||||
}
|
||||
UpdateRequest oldestRequest = null;
|
||||
try {
|
||||
oldestRequest = updateRequests.take();
|
||||
} catch (InterruptedException ignored) {}
|
||||
if (oldestRequest == null) {
|
||||
plugin.logger.warn("updateRequests.take() return a null value, why?");
|
||||
sleep(10000);
|
||||
continue;
|
||||
}
|
||||
updateRequests.clear();
|
||||
|
||||
if (!updateOnlineStatus()) {
|
||||
updateRequests.add(oldestRequest); // 更新失败 回去吧您内
|
||||
sleep(10000);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
new Thread(() -> {
|
||||
while (true) {
|
||||
if (PROXY_SHUT_DOWN) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String oldestMessage = announceQueue.take();
|
||||
appendMessage(oldestMessage);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private static final Object mergeMessageLock = new Object();
|
||||
|
||||
private void appendMessage(String message) {
|
||||
synchronized (mergeMessageLock) {
|
||||
if (!mergeMessage.isValid()) {
|
||||
mergeMessage = new MergeMessage(message);
|
||||
} else {
|
||||
mergeMessage.addLines(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void sleep(int millis) {
|
||||
try {
|
||||
Thread.sleep(millis);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
|
||||
protected boolean updateOnlineStatus() {
|
||||
return editOnlineStatusMessage(genPinMessage());
|
||||
}
|
||||
|
||||
protected boolean setOnlineStatusNotAvailable() {
|
||||
return updateOnlineStatus();
|
||||
// return editOnlineStatusMessage("(proxy already shutdown)");
|
||||
}
|
||||
|
||||
private static final Gson prettyGson = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
protected boolean editOnlineStatusMessage(String markdownText) {
|
||||
if (ONLINE_STATUS_MESSAGE_ID < 1) {
|
||||
return true;
|
||||
}
|
||||
BaseResponse response;
|
||||
try {
|
||||
response = bot.execute(new EditMessageText(CHAT_ID, ONLINE_STATUS_MESSAGE_ID, markdownText).parseMode(ParseMode.MarkdownV2).disableWebPagePreview(true));
|
||||
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);
|
||||
plugin.logger.warn("update failed: {}", responseJSON);
|
||||
}
|
||||
return response.isOk();
|
||||
} catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class MergeMessage {
|
||||
private int messageId;
|
||||
private long time;
|
||||
private long timeMinute;
|
||||
|
||||
private StringBuilder text;
|
||||
private boolean abort = false;
|
||||
|
||||
boolean isValid() {
|
||||
if (abort || messageId < 1) return false;
|
||||
long current = System.currentTimeMillis();
|
||||
if (current / MINUTE != timeMinute) return false;
|
||||
long dt = current - time;
|
||||
return dt <= 30_000 && dt >= 0;
|
||||
}
|
||||
|
||||
private void abort() {
|
||||
abort = true;
|
||||
}
|
||||
|
||||
protected MergeMessage(String firstMessage) {
|
||||
text = new StringBuilder(firstMessage);
|
||||
time = System.currentTimeMillis();
|
||||
timeMinute = time / MINUTE;
|
||||
send();
|
||||
}
|
||||
|
||||
private void send() {
|
||||
if (bot == null) {
|
||||
messageId = -1;
|
||||
return;
|
||||
}
|
||||
SendResponse response;
|
||||
try {
|
||||
response = bot.execute(new SendMessage(CHAT_ID, text.toString()).parseMode(ParseMode.MarkdownV2));
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
messageId = -1;
|
||||
return;
|
||||
}
|
||||
if (!response.isOk()) {
|
||||
messageId = -1;
|
||||
return;
|
||||
}
|
||||
messageId = response.message().messageId();
|
||||
}
|
||||
|
||||
protected MergeMessage() {
|
||||
messageId = 0;
|
||||
time = 0;
|
||||
timeMinute = 0;
|
||||
text = new StringBuilder();
|
||||
abort = false;
|
||||
} //dummy
|
||||
|
||||
private void addLines(String... messages) {
|
||||
for (String message : messages) {
|
||||
text.append('\n').append(message);
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
private void update() {
|
||||
if (!isValid()) {
|
||||
plugin.logger.error("message should only push to a valid object");
|
||||
return;
|
||||
}
|
||||
if (bot == null) {
|
||||
messageId = -1;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
bot.execute(new EditMessageText(CHAT_ID, messageId, text.toString()).parseMode(ParseMode.MarkdownV2));
|
||||
} catch (RuntimeException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
private MergeMessage mergeMessage = new MergeMessage();
|
||||
private LinkedBlockingQueue<String> announceQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
private void joinLeftAnnounce(String message) {
|
||||
announceQueue.add(message);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
package de.strifel.VTools.listeners.utils;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class GlistUtil {
|
||||
private final ProxyServer server;
|
||||
public GlistUtil(ProxyServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
// https://github.com/PaperMC/Velocity/blob/7f776abf550001186a1f11f4e5ff760dd66e3b04/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/BuiltinCommandUtil.java#L33
|
||||
public static List<RegisteredServer> sortedServerList(ProxyServer proxy) {
|
||||
List<RegisteredServer> servers = new ArrayList<>(proxy.getAllServers());
|
||||
servers.sort(Comparator.comparing(RegisteredServer::getServerInfo));
|
||||
return Collections.unmodifiableList(servers);
|
||||
}
|
||||
|
||||
// https://github.com/PaperMC/Velocity/blob/7f776abf550001186a1f11f4e5ff760dd66e3b04/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/GlistCommand.java
|
||||
|
||||
public void serverCount(final Player source) {
|
||||
sendTotalProxyCount(source);
|
||||
for (RegisteredServer server : sortedServerList(server)) {
|
||||
sendServerPlayers(source, server, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendTotalProxyCount(Player target) {
|
||||
int online = server.getPlayerCount() - 1;
|
||||
TranslatableComponent msg = online <= 1
|
||||
? Component.translatable("velocity.command.glist-player-singular")
|
||||
: Component.translatable("velocity.command.glist-player-plural");
|
||||
target.sendMessage(msg.color(NamedTextColor.YELLOW)
|
||||
.args(Component.text(Integer.toString(online), NamedTextColor.GREEN)));
|
||||
}
|
||||
|
||||
private void sendServerPlayers(Player target, RegisteredServer server, boolean fromAll) {
|
||||
List<Player> onServer = ImmutableList.copyOf(server.getPlayersConnected());
|
||||
if (onServer.isEmpty() && fromAll) {
|
||||
return;
|
||||
}
|
||||
|
||||
TextComponent.Builder builder = Component.text()
|
||||
.append(Component.text("[" + server.getServerInfo().getName() + "] ",
|
||||
NamedTextColor.DARK_AQUA)
|
||||
.clickEvent(ClickEvent.runCommand("/server " + server.getServerInfo().getName()))
|
||||
|
||||
)
|
||||
.append(Component.text("(" + onServer.size() + ")", NamedTextColor.GRAY))
|
||||
.append(Component.text(": "))
|
||||
.resetStyle();
|
||||
|
||||
for (int i = 0; i < onServer.size(); i++) {
|
||||
Player player = onServer.get(i);
|
||||
builder.append(Component.text(player.getUsername()));
|
||||
|
||||
if (i + 1 < onServer.size()) {
|
||||
builder.append(Component.text(", "));
|
||||
}
|
||||
}
|
||||
|
||||
target.sendMessage(builder.build());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue