mirror of
https://codeberg.org/Andre601/AdvancedServerList.git
synced 2026-11-05 17:02:34 -05:00
Compare commits
76
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1079bc81b5 | ||
|
|
ab68372ceb | ||
|
|
9d050dee49 | ||
|
|
263067376e | ||
|
|
8b73eb3c8c | ||
|
|
ce14274e4c | ||
|
|
a5d777f4f1 | ||
|
|
f94e578694 | ||
|
|
7c441e6ab5 | ||
|
|
2779f565a9 | ||
|
|
6d3cd0c505 | ||
|
|
cf95f11fdb | ||
|
|
679dbb8c0b | ||
|
|
f44e2759b9 | ||
|
|
29239cc4ed | ||
|
|
bcd5e77166 | ||
|
|
5d2fa03601 | ||
|
|
8f1a5e5a37 | ||
|
|
05fde902a4 | ||
|
|
d69e70c485 | ||
|
|
fba1e84171 | ||
|
|
530f0fd954 | ||
|
|
8bec42bb91 | ||
|
|
94982ff1e8 | ||
|
|
1f9795ed97 | ||
|
|
c6f95cb78a | ||
|
|
571f9f6b38 | ||
|
|
6d7fbb8c25 | ||
|
|
d3d83e5e11 | ||
|
|
82fb9c90f4 | ||
|
|
6841a5a6e0 | ||
|
|
6f41cd4e3a | ||
|
|
379f36832e | ||
|
|
5d8a39cd22 | ||
|
|
9dba820840 | ||
|
|
58712ca5c5 | ||
|
|
e19e942799 | ||
|
|
03434130c4 | ||
|
|
99df25551a | ||
|
|
4f31e4ce6f | ||
|
|
ba097c538d | ||
|
|
aa52435591 | ||
|
|
f44194d24f | ||
|
|
0e2d760ea6 | ||
|
|
c1e7e6b3ff | ||
|
|
96d383a5a1 | ||
|
|
79ba7877bf | ||
|
|
86b9aaa7c6 | ||
|
|
0cb3c036af | ||
|
|
b1f7b75e2d | ||
|
|
e9bc72c165 | ||
|
|
93203249fe | ||
|
|
2523ccb425 | ||
|
|
94972dad31 | ||
|
|
f8ac310259 | ||
|
|
5d8f5d81ed | ||
|
|
3111d7933d | ||
|
|
55f7e17d34 | ||
|
|
0e88a50deb | ||
|
|
83f351de6d | ||
|
|
b96d1cb1a9 | ||
|
|
517ff1df82 | ||
|
|
e8c7c4ee18 | ||
|
|
da44eac70e | ||
|
|
e85c121ee4 | ||
|
|
0c175999fc | ||
|
|
9c3f237f6d | ||
|
|
777545c94f | ||
|
|
dd197609e1 | ||
|
|
ad460f3728 | ||
|
|
7aba9c6243 | ||
|
|
ba81bed9a2 | ||
|
|
f452495d4b | ||
|
|
e00a628f31 | ||
|
|
9d8ccf42fe | ||
|
|
789e1299ab |
No files matched your search
@@ -16,6 +16,7 @@ on:
|
||||
|
||||
env:
|
||||
BRANCH_NAME: "${{ github.event.repository.name }}/${{ github.event.pull_request.number }}"
|
||||
RUN_URL: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_number }}"
|
||||
|
||||
jobs:
|
||||
createPreview:
|
||||
@@ -91,7 +92,7 @@ jobs:
|
||||
|---------|----------------------------------------------------------------------------------------------------------|
|
||||
| Commit | ${{ github.event.pull_request.head.sha }} |
|
||||
| Preview | https://preview.andre601.ch/@${{ github.event.repository.name }}~${{ github.event.pull_request.number }} |
|
||||
| Logs | ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} |
|
||||
| Logs | ${{ env.RUN_URL }} |
|
||||
comment-id: "${{ steps.comment.outputs.comment-id }}"
|
||||
issue-number: "${{ github.event.pull_request.number }}"
|
||||
token: "${{ secrets.BOT_TOKEN }}"
|
||||
@@ -105,10 +106,10 @@ jobs:
|
||||
|
||||
Pull request Preview failed! Check the logs for details.
|
||||
|
||||
| Name | Link |
|
||||
|---------|-------------------------------------------------------------------------------------|
|
||||
| Commit | ${{ github.event.pull_request.head.sha }} |
|
||||
| Logs | ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} |
|
||||
| Name | Link |
|
||||
|---------|-------------------------------------------|
|
||||
| Commit | ${{ github.event.pull_request.head.sha }} |
|
||||
| Logs | ${{ RUN_URL }} |
|
||||
comment-id: "${{ steps.comment.outputs.comment-id }}"
|
||||
issue-number: "${{ github.event.pull_request.number }}"
|
||||
token: "${{ secrets.BOT_TOKEN }}"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -5,20 +5,22 @@ when:
|
||||
steps:
|
||||
- name: "Build Jar and Upload"
|
||||
image: maven:3.9.9-eclipse-temurin-21
|
||||
secrets:
|
||||
- modrinth_api_token
|
||||
- hangar_api_token
|
||||
- discord_webhook_url
|
||||
environment:
|
||||
CODEBERG_API_TOKEN:
|
||||
from_secret: cb_token
|
||||
MODRINTH_API_TOKEN:
|
||||
from_secret: modrinth_api_token
|
||||
HANGAR_API_TOKEN:
|
||||
from_secret: hangar_api_token
|
||||
DISCORD_WEBHOOK_URL:
|
||||
from_secret: discord_webhook_url
|
||||
commands:
|
||||
- mvn clean install
|
||||
- cd version-uploader/target
|
||||
- mv VersionUploader.jar ../../
|
||||
- cd ../../
|
||||
- java -jar VersionUploader.jar --all
|
||||
- name: "Upload Jars to Codeber"
|
||||
- name: "Upload Jars to Codeberg"
|
||||
image: woodpeckerci/plugin-gitea-release
|
||||
settings:
|
||||
base_url: https://codeberg.org
|
||||
|
||||
+2
-2
@@ -8,12 +8,12 @@ To ensure proper safety and support are only certain versions and Platforms of A
|
||||
|---------------|-----------|
|
||||
| v5 | Yes |
|
||||
| v4 | Yes* |
|
||||
| v3 | Yes** |
|
||||
| v3 | No** |
|
||||
| v2 | No |
|
||||
| v1 | No |
|
||||
|
||||
\*Only receives support but no updates or bug fixes.
|
||||
\*\*Support ends at the end of 2024.
|
||||
\*\*Support ended on December 31st, 2024.
|
||||
|
||||
## Supported Server/Proxy Versions
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
<dependency>
|
||||
<groupId>space.arim.libertybans</groupId>
|
||||
<artifactId>bans-api</artifactId>
|
||||
<version>1.1.0-RC1</version>
|
||||
<version>1.1.0-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>ch.andre601.advancedserverlist</groupId>
|
||||
<version>parent</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>asl-placeholderapi</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>${plugin.version}</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>16</maven.compiler.source>
|
||||
<maven.compuler.target>16</maven.compuler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>papermc-repo</id>
|
||||
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>placeholderapi-repo</id>
|
||||
<url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>1.21-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.clip</groupId>
|
||||
<artifactId>placeholderapi</artifactId>
|
||||
<version>2.11.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>AdvancedServerList-PlaceholderAPI-Addon-${plugin.version}</finalName>
|
||||
<resources>
|
||||
<resource>
|
||||
<filtering>true</filtering>
|
||||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,70 @@
|
||||
package ch.andre601.placeholderapi;
|
||||
|
||||
import ch.andre601.placeholderapi.messenger.ASLPluginMessageListener;
|
||||
import ch.andre601.placeholderapi.messenger.BungeeCordMessageListener;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public final class ASLPlaceholderAPI extends JavaPlugin{
|
||||
|
||||
private String serverName = null;
|
||||
|
||||
@Override
|
||||
public void onEnable(){
|
||||
// Own Plugin Channel
|
||||
getServer().getMessenger().registerOutgoingPluginChannel(this, pluginChannel());
|
||||
getServer().getMessenger().registerIncomingPluginChannel(this, pluginChannel(), new ASLPluginMessageListener(this));
|
||||
|
||||
// BungeeCord Plugin Channel
|
||||
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
|
||||
getServer().getMessenger().registerIncomingPluginChannel(this, "BungeeCord", new BungeeCordMessageListener(this));
|
||||
|
||||
requestServerName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable(){
|
||||
getServer().getMessenger().unregisterOutgoingPluginChannel(this);
|
||||
getServer().getMessenger().unregisterIncomingPluginChannel(this);
|
||||
}
|
||||
|
||||
public void serverName(String serverName){
|
||||
this.serverName = serverName;
|
||||
}
|
||||
|
||||
public String pluginChannel(){
|
||||
return "advancedserverlist:action";
|
||||
}
|
||||
|
||||
public String serverName(){
|
||||
return serverName;
|
||||
}
|
||||
|
||||
public void waitUntil(Callable<Boolean> condition, Runnable action){
|
||||
Bukkit.getScheduler().runTaskTimer(this, (task) -> {
|
||||
try{
|
||||
if(condition.call()){
|
||||
action.run();
|
||||
task.cancel();
|
||||
}
|
||||
}catch(Exception ex){
|
||||
getLogger().log(Level.WARNING, "Encountered Exception while waiting for a task to finish.", ex);
|
||||
}
|
||||
}, 0L, 1L);
|
||||
}
|
||||
|
||||
private void requestServerName(){
|
||||
waitUntil(() -> !getServer().getOnlinePlayers().isEmpty(), () -> {
|
||||
Player plmSender = Bukkit.getOnlinePlayers().iterator().next();
|
||||
ByteArrayDataOutput out = ByteStreams.newDataOutput();
|
||||
out.writeUTF("GetServer");
|
||||
plmSender.sendPluginMessage(this, "BungeeCord", out.toByteArray());
|
||||
});
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package ch.andre601.placeholderapi.messenger;
|
||||
|
||||
import ch.andre601.placeholderapi.ASLPlaceholderAPI;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class ASLPluginMessageListener implements PluginMessageListener{
|
||||
|
||||
private final ASLPlaceholderAPI plugin;
|
||||
|
||||
public ASLPluginMessageListener(ASLPlaceholderAPI plugin){
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte[] message){
|
||||
if(!channel.equals(plugin.pluginChannel()))
|
||||
return;
|
||||
|
||||
if(plugin.serverName() == null)
|
||||
return;
|
||||
|
||||
ByteArrayInputStream stream = new ByteArrayInputStream(message);
|
||||
DataInputStream in = new DataInputStream(stream);
|
||||
try{
|
||||
String action = in.readUTF();
|
||||
switch(action){
|
||||
case "findPlugins" -> sendPluginMessage("hasPlugin", plugin.serverName());
|
||||
case "parsePlaceholder" -> {
|
||||
UUID playerUuid = UUID.fromString(in.readUTF());
|
||||
String text = in.readUTF();
|
||||
if(!Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")){
|
||||
sendPluginMessage("parsed", playerUuid.toString(), text);
|
||||
return;
|
||||
}
|
||||
|
||||
OfflinePlayer targetPlayer = Bukkit.getOfflinePlayer(playerUuid);
|
||||
String parsed = PlaceholderAPI.setPlaceholders(targetPlayer, text);
|
||||
sendPluginMessage("parsed", playerUuid.toString(), parsed);
|
||||
}
|
||||
default -> plugin.getLogger().warning("Received unknown plugin Message sub channel: " + action);
|
||||
}
|
||||
}catch(Exception ex){
|
||||
plugin.getLogger().log(Level.WARNING, "Encountered an Exception while handling a plugin message.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendPluginMessage(String channel, String... values){
|
||||
plugin.waitUntil(
|
||||
() -> !Bukkit.getOnlinePlayers().isEmpty() && plugin.serverName() != null,
|
||||
() -> {
|
||||
Player plmSender = Bukkit.getOnlinePlayers().iterator().next();
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
DataOutputStream dataOut = new DataOutputStream(outStream);
|
||||
try{
|
||||
dataOut.writeUTF(channel);
|
||||
for(String value : values){
|
||||
dataOut.writeUTF(value);
|
||||
}
|
||||
}catch(Exception ex){
|
||||
plugin.getLogger().log(Level.WARNING, "Encountered an Exception while writiing data for plugin message.", ex);
|
||||
}
|
||||
plmSender.sendPluginMessage(plugin, plugin.pluginChannel(), outStream.toByteArray());
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package ch.andre601.placeholderapi.messenger;
|
||||
|
||||
import ch.andre601.placeholderapi.ASLPlaceholderAPI;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class BungeeCordMessageListener implements PluginMessageListener{
|
||||
|
||||
private final ASLPlaceholderAPI plugin;
|
||||
|
||||
public BungeeCordMessageListener(ASLPlaceholderAPI plugin){
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte[] message){
|
||||
if(!channel.equals("BungeeCord"))
|
||||
return;
|
||||
|
||||
DataInputStream stream = new DataInputStream(new ByteArrayInputStream(message));
|
||||
try{
|
||||
String subChannel = stream.readUTF();
|
||||
if("GetServer".equals(subChannel))
|
||||
plugin.serverName(stream.readUTF());
|
||||
}catch(Exception ex){
|
||||
plugin.getLogger().log(Level.WARNING, "Encountered Exception while reading plugin message from Bungeecord channel.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
name: ASL-PlaceholderAPI
|
||||
version: '${plugin.version}'
|
||||
main: ch.andre601.placeholderapi.ASLPlaceholderAPI
|
||||
api-version: '1.21'
|
||||
dependencies:
|
||||
server:
|
||||
PlaceholderAPI:
|
||||
load: BEFORE
|
||||
authors: [ Andre601 ]
|
||||
description: Addon to allow PlaceholderAPI placeholders on your Velocity/BungeeCord proxy.
|
||||
+18
-2
@@ -49,6 +49,10 @@
|
||||
<id>sonatype</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>repo-sayandevelopment</id>
|
||||
<url>https://repo.sayandev.org/snapshots</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
@@ -69,14 +73,14 @@
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-api</artifactId>
|
||||
<version>1.20-R0.2</version>
|
||||
<version>1.21-R0.1-SNAPSHOT</version>
|
||||
<type>jar</type>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-api</artifactId>
|
||||
<version>1.20-R0.2</version>
|
||||
<version>1.21-R0.1-SNAPSHOT</version>
|
||||
<type>javadoc</type>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
@@ -124,6 +128,18 @@
|
||||
<version>2.9.0-4</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.sayandev</groupId>
|
||||
<artifactId>sayanvanish-api</artifactId>
|
||||
<version>1.6.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.sayandev</groupId>
|
||||
<artifactId>sayanvanish-proxy-bungeecord</artifactId>
|
||||
<version>1.6.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Other -->
|
||||
<dependency>
|
||||
|
||||
+30
-9
@@ -26,8 +26,9 @@
|
||||
package ch.andre601.advancedserverlist.bungeecord;
|
||||
|
||||
import ch.andre601.advancedserverlist.bungeecord.commands.CmdAdvancedServerList;
|
||||
import ch.andre601.advancedserverlist.bungeecord.listeners.JoinEvent;
|
||||
import ch.andre601.advancedserverlist.bungeecord.listeners.PingEvent;
|
||||
import ch.andre601.advancedserverlist.bungeecord.listeners.JoinListener;
|
||||
import ch.andre601.advancedserverlist.bungeecord.listeners.PingListener;
|
||||
import ch.andre601.advancedserverlist.bungeecord.listeners.PluginMessageListener;
|
||||
import ch.andre601.advancedserverlist.bungeecord.logging.BungeeLogger;
|
||||
import ch.andre601.advancedserverlist.bungeecord.objects.placeholders.BungeePlayerPlaceholders;
|
||||
import ch.andre601.advancedserverlist.bungeecord.objects.placeholders.BungeeProxyPlaceholders;
|
||||
@@ -35,9 +36,12 @@ import ch.andre601.advancedserverlist.bungeecord.objects.placeholders.BungeeServ
|
||||
import ch.andre601.advancedserverlist.core.AdvancedServerList;
|
||||
import ch.andre601.advancedserverlist.core.interfaces.PluginLogger;
|
||||
import ch.andre601.advancedserverlist.core.interfaces.core.PluginCore;
|
||||
import ch.andre601.advancedserverlist.core.objects.PluginMessageUtil;
|
||||
import ch.andre601.advancedserverlist.core.profiles.handlers.FaviconHandler;
|
||||
import com.alessiodp.libby.BungeeLibraryManager;
|
||||
import com.alessiodp.libby.Library;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import de.myzelyam.api.vanish.BungeeVanishAPI;
|
||||
import net.kyori.adventure.platform.bungeecord.BungeeAudiences;
|
||||
import net.md_5.bungee.api.Favicon;
|
||||
@@ -47,6 +51,7 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import org.bstats.bungeecord.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.sayandev.sayanvanish.bungeecord.api.SayanVanishBungeeAPI;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.nio.file.Path;
|
||||
@@ -55,9 +60,12 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
|
||||
|
||||
public static final String ASL_PLUGIN_CHANNEL = "advancedserverlist:action";
|
||||
|
||||
private final PluginLogger logger = new BungeeLogger(this);
|
||||
private final Map<String, ServerPing> fetchedServers = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -71,12 +79,13 @@ public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
|
||||
|
||||
@Override
|
||||
public void onLoad(){
|
||||
logger.info("Loading libraries. This may take a while...");
|
||||
// Using getLogger() here, because PluginLogger requires MiniMessage, which isn't loaded at this stage.
|
||||
getLogger().info("Loading libraries. This may take a while...");
|
||||
successfulLoad = loadLibs();
|
||||
if(successfulLoad){
|
||||
logger.info("Library loading completed successfully!");
|
||||
getLogger().info("Library loading completed successfully!");
|
||||
}else{
|
||||
logger.warn("Library loading completed with errors!");
|
||||
getLogger().warning("Library loading completed with errors!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +93,7 @@ public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
|
||||
public void onEnable(){
|
||||
if(!successfulLoad){
|
||||
logger.warn("There were issues during the plugins Loading-phase! Please see the console for possible reasons.");
|
||||
logger.warn("The plugin won't be enabled to avoid problems.");
|
||||
logger.warn("Aborting further enabling of the plugin to avoid errors.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -97,17 +106,20 @@ public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
|
||||
public void onDisable(){
|
||||
core.disable();
|
||||
getProxy().getScheduler().cancel(this);
|
||||
audiences.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadCommands(){
|
||||
getProxy().getPluginManager().registerCommand(this, new CmdAdvancedServerList(this));
|
||||
getPluginLogger().success("Registered <white>/advancedserverlist</white>!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadEvents(){
|
||||
new JoinEvent(this);
|
||||
new PingEvent(this);
|
||||
new JoinListener(this);
|
||||
new PingListener(this);
|
||||
new PluginMessageListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -206,6 +218,9 @@ public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
|
||||
// Exclude players when PremiumVanish is enabled and player is hidden.
|
||||
if(getProxy().getPluginManager().getPlugin("PremiumVanish") != null){
|
||||
players.removeIf(BungeeVanishAPI::isInvisible);
|
||||
}else // Do the same if SayanVanish is enabled.
|
||||
if(getProxy().getPluginManager().getPlugin("SayanVanish") != null){
|
||||
players.removeIf(player -> SayanVanishBungeeAPI.getInstance().isVanished(player.getUniqueId()));
|
||||
}
|
||||
|
||||
return players.size();
|
||||
@@ -226,18 +241,24 @@ public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
|
||||
|
||||
return true;
|
||||
}catch(Exception ex){
|
||||
logger.warn("Encountered an Exception while trying to load dependencies.", ex);
|
||||
getLogger().log(Level.WARNING, "Encountered an Exception while trying to load dependencies.", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchServers(){
|
||||
fetchedServers.clear();
|
||||
PluginMessageUtil.get().clear();
|
||||
getProxy().getServers().forEach((name, server) -> server.ping((ping, throwable) -> {
|
||||
if(throwable != null)
|
||||
return;
|
||||
|
||||
fetchedServers.put(name, ping);
|
||||
|
||||
ByteArrayDataOutput out = ByteStreams.newDataOutput();
|
||||
out.writeUTF("findPlugins");
|
||||
|
||||
server.sendData(ASL_PLUGIN_CHANNEL, out.toByteArray());
|
||||
}));
|
||||
}
|
||||
}
|
||||
+27
-21
@@ -32,19 +32,20 @@ import ch.andre601.advancedserverlist.bungeecord.BungeeCordCore;
|
||||
import ch.andre601.advancedserverlist.bungeecord.objects.impl.BungeePlayerImpl;
|
||||
import ch.andre601.advancedserverlist.bungeecord.objects.impl.BungeeProxyImpl;
|
||||
import ch.andre601.advancedserverlist.core.compat.maintenance.MaintenanceUtil;
|
||||
import ch.andre601.advancedserverlist.core.compat.papi.PAPIUtil;
|
||||
import ch.andre601.advancedserverlist.core.interfaces.core.PluginCore;
|
||||
import ch.andre601.advancedserverlist.core.interfaces.events.GenericEventWrapper;
|
||||
import ch.andre601.advancedserverlist.core.objects.CachedPlayer;
|
||||
import ch.andre601.advancedserverlist.core.objects.PluginMessageUtil;
|
||||
import ch.andre601.advancedserverlist.core.parsing.ComponentParser;
|
||||
import ch.andre601.advancedserverlist.core.profiles.replacer.StringReplacer;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
|
||||
import net.md_5.bungee.api.Favicon;
|
||||
import net.md_5.bungee.api.ServerPing;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.ProxyPingEvent;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
@@ -118,6 +119,11 @@ public class BungeeEventWrapper implements GenericEventWrapper<Favicon, BungeePl
|
||||
ping.getPlayers().setSample(players);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayersHidden(){
|
||||
ping.getPlayers().setSample(new ServerPing.PlayerInfo[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFavicon(Favicon favicon){
|
||||
ping.setFavicon(favicon);
|
||||
@@ -169,25 +175,25 @@ public class BungeeEventWrapper implements GenericEventWrapper<Favicon, BungeePl
|
||||
|
||||
@Override
|
||||
public String parsePAPIPlaceholders(String text, BungeePlayerImpl player){
|
||||
if(plugin.getProxy().getPluginManager().getPlugin("PAPIProxyBridge") == null)
|
||||
return text;
|
||||
|
||||
if(!PAPIUtil.get().isCompatible())
|
||||
return text;
|
||||
|
||||
String server = PAPIUtil.get().getServer();
|
||||
if(server == null || server.isEmpty())
|
||||
return text;
|
||||
|
||||
ServerInfo serverInfo = plugin.getProxy().getServerInfo(server);
|
||||
if(serverInfo == null || serverInfo.getPlayers().isEmpty())
|
||||
return text;
|
||||
|
||||
ProxiedPlayer carrier = PAPIUtil.get().getPlayer(serverInfo.getPlayers());
|
||||
if(carrier == null)
|
||||
return text;
|
||||
|
||||
return PAPIUtil.get().parse(text, carrier.getUniqueId(), player.getUUID());
|
||||
return PluginMessageUtil.get().getOrExecute(player.getUUID().toString(), text, (uuid, textValue) -> {
|
||||
List<String> servers = PluginMessageUtil.get().getKnownServers();
|
||||
if(servers.isEmpty())
|
||||
return textValue;
|
||||
|
||||
ServerInfo serverInfo = plugin.getProxy().getServerInfo(servers.get(0));
|
||||
if(serverInfo == null)
|
||||
return textValue;
|
||||
|
||||
ByteArrayDataOutput out = ByteStreams.newDataOutput();
|
||||
out.writeUTF("parse");
|
||||
out.writeUTF(uuid);
|
||||
out.writeUTF(textValue);
|
||||
|
||||
serverInfo.sendData(BungeeCordCore.ASL_PLUGIN_CHANNEL, out.toByteArray());
|
||||
PluginMessageUtil.get().queue(uuid, textValue, false);
|
||||
|
||||
return PluginMessageUtil.get().getOrDefault(uuid, textValue);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -34,10 +34,10 @@ import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public class JoinEvent implements Listener{
|
||||
public class JoinListener implements Listener{
|
||||
private final BungeeCordCore plugin;
|
||||
|
||||
public JoinEvent(BungeeCordCore plugin){
|
||||
public JoinListener(BungeeCordCore plugin){
|
||||
this.plugin = plugin;
|
||||
plugin.getProxy().getPluginManager().registerListener(plugin, this);
|
||||
}
|
||||
+2
-2
@@ -33,11 +33,11 @@ import net.md_5.bungee.api.event.ProxyPingEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
public class PingEvent implements Listener{
|
||||
public class PingListener implements Listener{
|
||||
|
||||
private final BungeeCordCore plugin;
|
||||
|
||||
public PingEvent(BungeeCordCore plugin){
|
||||
public PingListener(BungeeCordCore plugin){
|
||||
this.plugin = plugin;
|
||||
plugin.getProxy().getPluginManager().registerListener(plugin, this);
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package ch.andre601.advancedserverlist.bungeecord.listeners;
|
||||
|
||||
import ch.andre601.advancedserverlist.bungeecord.BungeeCordCore;
|
||||
import ch.andre601.advancedserverlist.core.objects.PluginMessageUtil;
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
public class PluginMessageListener implements Listener{
|
||||
|
||||
public PluginMessageListener(BungeeCordCore plugin){
|
||||
plugin.getProxy().getPluginManager().registerListener(plugin, this);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPluginMessage(PluginMessageEvent event){
|
||||
if(!event.getTag().equals(BungeeCordCore.ASL_PLUGIN_CHANNEL))
|
||||
return;
|
||||
|
||||
ByteArrayDataInput input = ByteStreams.newDataInput(event.getData());
|
||||
String subChannel = input.readUTF();
|
||||
switch(subChannel){
|
||||
case "hasPlugin" -> {
|
||||
String serverName = input.readUTF();
|
||||
PluginMessageUtil.get().addServer(serverName);
|
||||
}
|
||||
case "parsed" -> {
|
||||
String uuid = input.readUTF();
|
||||
String parsed = input.readUTF();
|
||||
|
||||
PluginMessageUtil.get().queue(uuid, parsed, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -27,6 +27,7 @@ package ch.andre601.advancedserverlist.bungeecord.logging;
|
||||
|
||||
import ch.andre601.advancedserverlist.bungeecord.BungeeCordCore;
|
||||
import ch.andre601.advancedserverlist.core.interfaces.PluginLogger;
|
||||
import ch.andre601.advancedserverlist.core.parsing.ComponentParser;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
@@ -55,21 +56,21 @@ public class BungeeLogger implements PluginLogger{
|
||||
|
||||
@Override
|
||||
public void info(String msg, Object... args){
|
||||
logger.info(String.format(msg, args));
|
||||
logger.info(ComponentParser.textFormatted(msg, args).toStringStriped());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String msg, Object... args){
|
||||
logger.warning(String.format(msg, args));
|
||||
logger.warning(ComponentParser.textFormatted(msg, args).toStringStriped());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String msg, Throwable throwable){
|
||||
logger.log(Level.WARNING, msg, throwable);
|
||||
logger.log(Level.WARNING, ComponentParser.text(msg).toStringStriped(), throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String msg, Throwable throwable, Object... args){
|
||||
logger.log(Level.WARNING, String.format(msg, args), throwable);
|
||||
logger.log(Level.WARNING, ComponentParser.textFormatted(msg, args).toStringStriped(), throwable);
|
||||
}
|
||||
}
|
||||
+16
-4
@@ -41,7 +41,7 @@
|
||||
<properties>
|
||||
<maven.compiler.source>16</maven.compiler.source>
|
||||
<maven.compiler.target>16</maven.compiler.target>
|
||||
<plugin.config.version>6</plugin.config.version>
|
||||
<plugin.config.version>8</plugin.config.version>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
@@ -77,9 +77,11 @@
|
||||
<dependency>
|
||||
<groupId>ch.andre601</groupId>
|
||||
<artifactId>expressionparser</artifactId>
|
||||
<version>1.4.3</version>
|
||||
<version>1.5.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Configuration -->
|
||||
<dependency>
|
||||
<groupId>org.spongepowered</groupId>
|
||||
<artifactId>configurate-yaml</artifactId>
|
||||
@@ -129,7 +131,7 @@
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>33.3.1-jre</version>
|
||||
<version>33.4.0-jre</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -157,7 +159,7 @@
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>5.11.3</version>
|
||||
<version>5.11.4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
@@ -168,6 +170,16 @@
|
||||
<resource>
|
||||
<filtering>true</filtering>
|
||||
<directory>${project.basedir}/src/main/resources</directory>
|
||||
<excludes>
|
||||
<exclude>char-widths.json.gz</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
<resource>
|
||||
<filtering>false</filtering>
|
||||
<directory>${project.basedir}/src/main/resources</directory>
|
||||
<includes>
|
||||
<include>char-widths.json.gz</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
|
||||
@@ -32,6 +32,7 @@ import ch.andre601.advancedserverlist.core.commands.CommandHandler;
|
||||
import ch.andre601.advancedserverlist.core.compat.maintenance.MaintenancePlaceholder;
|
||||
import ch.andre601.advancedserverlist.core.file.FileHandler;
|
||||
import ch.andre601.advancedserverlist.core.interfaces.core.PluginCore;
|
||||
import ch.andre601.advancedserverlist.core.parsing.TextCenterUtil;
|
||||
import ch.andre601.advancedserverlist.core.profiles.conditions.ProfileConditionParser;
|
||||
import ch.andre601.advancedserverlist.core.profiles.handlers.PlayerHandler;
|
||||
|
||||
@@ -47,6 +48,8 @@ public class AdvancedServerList<F>{
|
||||
private final FileHandler fileHandler;
|
||||
private final CommandHandler commandHandler;
|
||||
private final PlayerHandler playerHandler;
|
||||
private final TextCenterUtil textCenterUtil;
|
||||
|
||||
private static final AdvancedServerListAPI api = AdvancedServerListAPI.get();
|
||||
private final ProfileConditionParser parser = ProfileConditionParser.create();
|
||||
|
||||
@@ -59,18 +62,18 @@ public class AdvancedServerList<F>{
|
||||
this.fileHandler = new FileHandler(this);
|
||||
this.commandHandler = new CommandHandler(this);
|
||||
this.playerHandler = new PlayerHandler(this);
|
||||
this.textCenterUtil = new TextCenterUtil(this);
|
||||
|
||||
plugin.getPluginLogger().info("Registering internal Placeholders...");
|
||||
|
||||
placeholders.forEach(this::addPlaceholder);
|
||||
|
||||
if(plugin.isPluginEnabled("Maintenance")){
|
||||
plugin.getPluginLogger().info("Found Maintenance Plugin. Registering extra Placeholder...");
|
||||
addPlaceholder(new MaintenancePlaceholder(this));
|
||||
}
|
||||
|
||||
plugin.getPluginLogger().info("Completed internal Placeholder registration!");
|
||||
|
||||
resolveVersion();
|
||||
getPlugin().getPluginLogger().info("Starting <white>AdvancedServerList v%s</white>...", version);
|
||||
load();
|
||||
}
|
||||
|
||||
@@ -110,16 +113,20 @@ public class AdvancedServerList<F>{
|
||||
return updateChecker;
|
||||
}
|
||||
|
||||
public TextCenterUtil getTextCenterUtil(){
|
||||
return textCenterUtil;
|
||||
}
|
||||
|
||||
public void disable(){
|
||||
getPlugin().getPluginLogger().info("Saving playercache.json file...");
|
||||
getPlugin().getPluginLogger().info("[-] Saving <white>playercache.json</white> file...");
|
||||
getPlayerHandler().save();
|
||||
|
||||
if(updateChecker != null){
|
||||
getPlugin().getPluginLogger().info("Disabling Update Checker...");
|
||||
getPlugin().getPluginLogger().info("[-] Disabling Update Checker...");
|
||||
updateChecker.disable();
|
||||
}
|
||||
|
||||
getPlugin().getPluginLogger().info("AdvancedServerList disabled!");
|
||||
getPlugin().getPluginLogger().success("AdvancedServerList disabled!");
|
||||
}
|
||||
|
||||
public void clearFaviconCache(){
|
||||
@@ -131,70 +138,56 @@ public class AdvancedServerList<F>{
|
||||
}
|
||||
|
||||
private void load(){
|
||||
printBanner();
|
||||
resolveVersion();
|
||||
|
||||
getPlugin().getPluginLogger().info("Starting AdvancedServerList v%s...", version);
|
||||
getPlugin().getPluginLogger().info("Platform: %s", plugin.getPlatformInfo());
|
||||
|
||||
if(getFileHandler().loadConfig()){
|
||||
getPlugin().getPluginLogger().info("Successfully loaded config.yml!");
|
||||
}else{
|
||||
getPlugin().getPluginLogger().warn("Unable to load config.yml! Check previous lines for errors.");
|
||||
if(!getFileHandler().loadConfig()){
|
||||
getPlugin().getPluginLogger().failure("Unable to load <white>config.yml</white>! Check previous lines for errors.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(getFileHandler().isOldConfig()){
|
||||
getPlugin().getPluginLogger().info("Detected old config.yml. Attempting to migrate...");
|
||||
getPlugin().getPluginLogger().info("[-] Detected old <white>config.yml</white>. Attempting to migrate...");
|
||||
if(getFileHandler().migrateConfig()){
|
||||
getPlugin().getPluginLogger().info("Migration completed successfully!");
|
||||
getPlugin().getPluginLogger().success("Migration completed successfully!");
|
||||
}else{
|
||||
getPlugin().getPluginLogger().warn("Couldn't migrate config.yml! Check previous lines for errors.");
|
||||
getPlugin().getPluginLogger().failure("Couldn't migrate <white>config.yml</white>! Check previous lines for errors.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(getFileHandler().getBool(true, "printBanner")){
|
||||
printBanner();
|
||||
}
|
||||
|
||||
getPlugin().getPluginLogger().info("Platform: <white>%s</white>", plugin.getPlatformInfo());
|
||||
getPlugin().getPluginLogger().info("");
|
||||
|
||||
if(getFileHandler().loadProfiles()){
|
||||
getPlugin().getPluginLogger().info("Successfully loaded %d profile(s)!", getFileHandler().getProfiles().size());
|
||||
getPlugin().getPluginLogger().success("Successfully loaded <white>%d</white> profile(s)!", getFileHandler().getProfiles().size());
|
||||
}else{
|
||||
getPlugin().getPluginLogger().warn("Unable to load profiles! Check previous lines for errors.");
|
||||
getPlugin().getPluginLogger().failure("Unable to load profiles! Check previous lines for errors.");
|
||||
return;
|
||||
}
|
||||
|
||||
getPlugin().loadFaviconHandler(this);
|
||||
|
||||
getPlugin().getPluginLogger().info("Loading Commands...");
|
||||
plugin.loadCommands();
|
||||
getPlugin().getPluginLogger().info("Commands loaded!");
|
||||
|
||||
getPlugin().getPluginLogger().info("Loading events...");
|
||||
plugin.loadEvents();
|
||||
getPlugin().getPluginLogger().info("Events loaded!");
|
||||
|
||||
getPlugin().getPluginLogger().info("Loading playercache.json...");
|
||||
getPlayerHandler().load();
|
||||
|
||||
getPlugin().getPluginLogger().info("Loading bStats metrics. Disable it in the global config under /plugins/bstats/");
|
||||
plugin.loadMetrics();
|
||||
getPlugin().getPluginLogger().info("Metrics loaded!");
|
||||
|
||||
getPlugin().getPluginLogger().info("Enabling Schedulers...");
|
||||
plugin.startScheduler();
|
||||
getPlugin().getPluginLogger().info("Schedulers enabled.");
|
||||
|
||||
getPlugin().getPluginLogger().info("AdvancedServerList is ready!");
|
||||
getPlugin().getPluginLogger().success("<green>AdvancedServerList is ready!");
|
||||
|
||||
if(getFileHandler().getBoolean("checkUpdates"))
|
||||
this.updateChecker = new UpdateChecker(this);
|
||||
}
|
||||
|
||||
private void printBanner(){
|
||||
getPlugin().getPluginLogger().info(" _____ _");
|
||||
getPlugin().getPluginLogger().info(" /\\ / ____| |");
|
||||
getPlugin().getPluginLogger().info(" / \\ | (___ | |");
|
||||
getPlugin().getPluginLogger().info(" / /\\ \\ \\___ \\| |");
|
||||
getPlugin().getPluginLogger().info(" / ____ \\ ____) | |____");
|
||||
getPlugin().getPluginLogger().info("/_/ \\_\\_____/|______|");
|
||||
getPlugin().getPluginLogger().info("<#3b90ff> __ _______ ___");
|
||||
getPlugin().getPluginLogger().info("<#3b90ff> /\\ \\ / ____|_| | |");
|
||||
getPlugin().getPluginLogger().info("<#3b90ff> / \\ \\ | (_(___ | | |");
|
||||
getPlugin().getPluginLogger().info("<#3b90ff> / /\\ \\ \\ \\___ \\ \\| | |");
|
||||
getPlugin().getPluginLogger().info("<#3b90ff> / ____ \\ \\ ____) | | |_|____");
|
||||
getPlugin().getPluginLogger().info("<#3b90ff>/_/_/ \\_\\_\\_____/_/|______|_|");
|
||||
getPlugin().getPluginLogger().info("");
|
||||
}
|
||||
|
||||
@@ -211,7 +204,6 @@ public class AdvancedServerList<F>{
|
||||
}
|
||||
|
||||
private void addPlaceholder(PlaceholderProvider placeholderProvider){
|
||||
getPlugin().getPluginLogger().info("Registering %s Placeholders...", placeholderProvider.getIdentifier());
|
||||
AdvancedServerList.getApi().addPlaceholderProvider(placeholderProvider);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ package ch.andre601.advancedserverlist.core.check;
|
||||
import ch.andre601.advancedserverlist.core.AdvancedServerList;
|
||||
import ch.andre601.advancedserverlist.core.interfaces.PluginLogger;
|
||||
import ch.andre601.advancedserverlist.core.interfaces.commands.CmdSender;
|
||||
import ch.andre601.advancedserverlist.core.objects.ValueCache;
|
||||
import ch.andre601.advancedserverlist.core.objects.CacheUtil;
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
@@ -59,7 +59,7 @@ public class UpdateChecker{
|
||||
|
||||
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(new UpdateCheckThread());
|
||||
|
||||
private final ValueCache<CompletableFuture<ModrinthVersion>> cache = new ValueCache<>(Duration.ofMinutes(5));
|
||||
private final CacheUtil.SingletonCache<CompletableFuture<ModrinthVersion>> cache = new CacheUtil.SingletonCache<>(Duration.ofMinutes(5));
|
||||
|
||||
private final AdvancedServerList<?> core;
|
||||
private final PluginLogger logger;
|
||||
@@ -123,11 +123,11 @@ public class UpdateChecker{
|
||||
|
||||
private void startUpdateChecker(){
|
||||
executor.scheduleAtFixedRate(() -> {
|
||||
logger.info("Looking for an update...");
|
||||
logger.info("[-] Looking for an update...");
|
||||
|
||||
performUpdateCheck().whenComplete((version, throwable) -> {
|
||||
if(version == null || throwable != null){
|
||||
logger.warn("Failed to look for any updates. Check previous messages for causes.");
|
||||
logger.failure("Failed to look for any updates. Check previous messages for causes.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -136,9 +136,9 @@ public class UpdateChecker{
|
||||
int result = version.compare(core.getVersion());
|
||||
switch(result){
|
||||
case -1 -> printUpdateBanner(version);
|
||||
case 0 -> logger.info("You are running the latest version!");
|
||||
case 0 -> logger.success("You are running the latest version!");
|
||||
case 1 -> logger.info(
|
||||
"Your version (%s) seems to be newer than the latest release (%s). Are you running a dev build?",
|
||||
"[?] Your version (<white>%s</white>) seems to be newer than the latest release (<white>%s</white>). Are you running a dev build?",
|
||||
core.getVersion(), version.versionNumber()
|
||||
);
|
||||
}
|
||||
@@ -185,18 +185,18 @@ public class UpdateChecker{
|
||||
logger.warn("=======================================================================");
|
||||
logger.warn("You are running an outdated version of AdvancedServerList!");
|
||||
logger.warn("");
|
||||
logger.warn("Your version: %s", core.getVersion());
|
||||
logger.warn("Modrinth: %s", version.versionNumber());
|
||||
logger.warn("Your version: <white>%s</white>", core.getVersion());
|
||||
logger.warn("Modrinth: <white>%s</white>", version.versionNumber());
|
||||
logger.warn("");
|
||||
|
||||
if(!version.isRelease()){
|
||||
logger.warn("WARNING!");
|
||||
logger.warn("<bold>WARNING!</bold>");
|
||||
logger.warn("This is a %s version and may contain breaking changes and/or bugs!", version.versionType());
|
||||
logger.warn("");
|
||||
}
|
||||
|
||||
logger.warn("Download the latest version from here:");
|
||||
logger.warn("https://modrinth.com/plugin/advancedserverlist/version/%s", version.id());
|
||||
logger.warn("<white>https://modrinth.com/plugin/advancedserverlist/version/%s</white>", version.id());
|
||||
logger.warn("=======================================================================");
|
||||
}
|
||||
|
||||
|
||||
+53
-35
@@ -102,43 +102,27 @@ public class CommandHandler{
|
||||
public void handle(CmdSender sender, String[] args){
|
||||
sender.sendPrefixedMsg("- Commands. Hover for details.");
|
||||
for(PluginCommand command : core.getCommandHandler().getSubCommands()){
|
||||
sender.sendPrefixedMsg("");
|
||||
sender.sendPrefixedMsg(
|
||||
"<aqua><hover:show_text:\"%s\">" +
|
||||
sender.sendMsg();
|
||||
sender.sendMsg(
|
||||
"<click:suggest_command:/asl %s>" +
|
||||
"/asl <white>%s</white>" +
|
||||
"</click>" +
|
||||
"</hover></aqua>",
|
||||
getCommandHover(command),
|
||||
command.name(),
|
||||
command.usage() +
|
||||
"</click>",
|
||||
command.name()
|
||||
);
|
||||
sender.sendMsg("<grey>[<hover:show_text:\"%s\"><white>Permission</white></hover>]</grey>", command.permission());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String usage(){
|
||||
return "<aqua>/asl <white>help</white></aqua>";
|
||||
return new CommandStringBuilder("help", description())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description(){
|
||||
return "Shows all available commands.";
|
||||
}
|
||||
|
||||
private String getCommandHover(PluginCommand command){
|
||||
return String.format(
|
||||
"""
|
||||
<grey>Usage: %s
|
||||
Permission: <white>%s</white>
|
||||
|
||||
<white>%s</white>
|
||||
""",
|
||||
command.usage(),
|
||||
command.permission(),
|
||||
command.description()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Reload extends PluginCommand{
|
||||
@@ -172,7 +156,8 @@ public class CommandHandler{
|
||||
|
||||
@Override
|
||||
public String usage(){
|
||||
return "<aqua>/asl <white>reload</white></aqua>";
|
||||
return new CommandStringBuilder("reload", description())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -206,7 +191,8 @@ public class CommandHandler{
|
||||
|
||||
@Override
|
||||
public String usage(){
|
||||
return "<aqua>/asl <white>clearcache</white></aqua>";
|
||||
return new CommandStringBuilder("clearcache", description())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -268,7 +254,18 @@ public class CommandHandler{
|
||||
|
||||
@Override
|
||||
public String usage(){
|
||||
return "<aqua>/asl <white>migrate <grey>\\<</grey>Plugin<grey>></grey></white></aqua>";
|
||||
return new CommandStringBuilder("migrate", description())
|
||||
.appendSpace()
|
||||
.appendArgument(new CommandStringBuilder.ArgumentBuilder("Plugin").description(
|
||||
"""
|
||||
The Plugin to migrate from.
|
||||
Supported options:
|
||||
|
||||
- <white>ServerListPlus</white>
|
||||
- <white>MiniMOTD</white>
|
||||
"""
|
||||
).build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -360,7 +357,7 @@ public class CommandHandler{
|
||||
sender.sendErrorMsg("<red>There was an error while creating the file</red> %s<red>!", copy);
|
||||
sender.sendErrorMsg("<red>Check console for further details.");
|
||||
|
||||
core.getPlugin().getPluginLogger().warn("Encountered IOException while creating file %s", ex, copy);
|
||||
core.getPlugin().getPluginLogger().warn("Encountered IOException while creating file <white>%s</white>", ex, copy);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -378,7 +375,7 @@ public class CommandHandler{
|
||||
sender.sendErrorMsg("<red>There was an error while loading the file</red> %s<red>!", copy);
|
||||
sender.sendErrorMsg("<red>Check console for further details.");
|
||||
|
||||
core.getPlugin().getPluginLogger().warn("Encountered IOException while loading file %s", ex, copy);
|
||||
core.getPlugin().getPluginLogger().warn("Encountered IOException while loading file <white>%s</white>", ex, copy);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -398,7 +395,7 @@ public class CommandHandler{
|
||||
sender.sendErrorMsg("<red>There was an Error while copying values from</red> %s <red>to</red> %s<red>.", name, copy);
|
||||
sender.sendErrorMsg("<red>Check console for details.");
|
||||
|
||||
core.getPlugin().getPluginLogger().warn("Encountered SerializationException while set values for %s!", ex, copy);
|
||||
core.getPlugin().getPluginLogger().warn("Encountered SerializationException while set values for <white>%s</white>!", ex, copy);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -410,7 +407,7 @@ public class CommandHandler{
|
||||
sender.sendErrorMsg("<red>There was an Error while trying to save</red> %s<red>!", copy);
|
||||
sender.sendErrorMsg("<red>Check console for details.");
|
||||
|
||||
core.getPlugin().getPluginLogger().warn("Encountered IOException while saving ConfigurationNode for %s!", ex, copy);
|
||||
core.getPlugin().getPluginLogger().warn("Encountered IOException while saving ConfigurationNode for <white>%s</white>!", ex, copy);
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
@@ -422,11 +419,32 @@ public class CommandHandler{
|
||||
|
||||
@Override
|
||||
public String usage(){
|
||||
return "<aqua>/asl <white>profiles</white> <grey>\\<" +
|
||||
"<white>add</white> \\<<white>name</white>> | " +
|
||||
"<white>copy</white> \\<<white>profile</white>> \\<<white>name</white>> | " +
|
||||
"<white>list</white>" +
|
||||
"></grey></aqua>";
|
||||
return new CommandStringBuilder("profiles", "Creates a new profile, copies one from an existing file, or lists all available profiles.")
|
||||
.appendSpace()
|
||||
.appendText("<grey>\\<")
|
||||
// add <name>
|
||||
.appendLiteralArgument("add", "Adds a new profile using the default values.")
|
||||
.appendSpace()
|
||||
.appendArgument(new CommandStringBuilder.ArgumentBuilder("name").description("Name of the new profile").required().build())
|
||||
// Divider
|
||||
.appendSpace()
|
||||
.appendText("|")
|
||||
.appendSpace()
|
||||
// copy <profile> <name>
|
||||
.appendLiteralArgument("copy", "Creates a copy of the provided profile.")
|
||||
.appendSpace()
|
||||
.appendArgument(new CommandStringBuilder.ArgumentBuilder("profile").description("The profile to copy.").required().build())
|
||||
.appendSpace()
|
||||
.appendArgument(new CommandStringBuilder.ArgumentBuilder("name").description("Name of the copy.").required().build())
|
||||
// Divider
|
||||
.appendSpace()
|
||||
.appendText("|")
|
||||
.appendSpace()
|
||||
// list
|
||||
.appendLiteralArgument("list", "Lists all currently know profiles and whether they are valid or not.")
|
||||
// Closing bracket
|
||||
.appendText("></grey>")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022-2025 Andre_601
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package ch.andre601.advancedserverlist.core.commands;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CommandStringBuilder{
|
||||
|
||||
private final List<CommandPart> commandParts = new ArrayList<>();
|
||||
|
||||
public CommandStringBuilder(String subcommand, String description){
|
||||
appendLiteralArgument(subcommand, description);
|
||||
}
|
||||
|
||||
public CommandStringBuilder appendArgument(Argument argument){
|
||||
this.commandParts.add(argument);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandStringBuilder appendText(String text){
|
||||
this.commandParts.add(new Text(text));
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandStringBuilder appendLiteralArgument(String name, String description){
|
||||
return appendText("<hover:show_text:\"<grey>" + description + "</grey>\"><white>" + name + "</white></hover>");
|
||||
}
|
||||
|
||||
public CommandStringBuilder appendSpace(){
|
||||
return appendText(" ");
|
||||
}
|
||||
|
||||
public String build(){
|
||||
StringBuilder builder = new StringBuilder("<aqua>/asl ");
|
||||
for(CommandPart part : commandParts){
|
||||
builder.append(part.build());
|
||||
}
|
||||
builder.append("</aqua>"); // Closing the first color tag.
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static class ArgumentBuilder{
|
||||
private final String name;
|
||||
|
||||
private String description = "No description provided.";
|
||||
private boolean required = false;
|
||||
|
||||
public ArgumentBuilder(String name){
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public ArgumentBuilder description(String description){
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ArgumentBuilder required(){
|
||||
this.required = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Argument build(){
|
||||
return new Argument(name, description, required);
|
||||
}
|
||||
}
|
||||
|
||||
private record Text(String text) implements CommandPart{
|
||||
@Override
|
||||
public String build(){
|
||||
return text();
|
||||
}
|
||||
}
|
||||
|
||||
private record Argument(String name, String description, boolean required) implements CommandPart{
|
||||
@Override
|
||||
public String build(){
|
||||
return "<hover:show_text:\"<grey>" + description() + "</grey>\">" +
|
||||
"<grey>" + (required() ? "\\<" : "[") + "<white>" + name() + "</white>" + (required() ? ">" : "]") +
|
||||
"</grey></hover>";
|
||||
}
|
||||
}
|
||||
|
||||
private interface CommandPart{
|
||||
String build();
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022-2023 Andre_601
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package ch.andre601.advancedserverlist.core.compat.papi;
|
||||
|
||||
import ch.andre601.advancedserverlist.core.objects.ValueCache;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import net.william278.papiproxybridge.api.PlaceholderAPI;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class PAPIUtil{
|
||||
|
||||
private static PAPIUtil instance;
|
||||
|
||||
private final ValueCache<CompletableFuture<Set<String>>> serversCache = new ValueCache<>(Duration.ofSeconds(10));
|
||||
private final Cache<UUID, CompletableFuture<String>> textCache = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(Duration.ofSeconds(10))
|
||||
.build();
|
||||
private final PlaceholderAPI papi;
|
||||
|
||||
private PAPIUtil(){
|
||||
this.papi = PlaceholderAPI.createInstance();
|
||||
}
|
||||
|
||||
public static PAPIUtil get(){
|
||||
if(instance != null)
|
||||
return instance;
|
||||
|
||||
return (instance = new PAPIUtil());
|
||||
}
|
||||
|
||||
// Make sure the version of PAPIProxyBridge we get has the required methods we need...
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean isCompatible(){
|
||||
try{
|
||||
papi.getClass().getMethod("getServers");
|
||||
return true;
|
||||
}catch(NoSuchMethodException ex){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getServer(){
|
||||
Set<String> values = serversCache.get(papi::getServers).getNow(Collections.emptySet());
|
||||
if(values.isEmpty())
|
||||
return null;
|
||||
|
||||
return List.copyOf(values).get(0);
|
||||
}
|
||||
|
||||
public <P> P getPlayer(Collection<P> players){
|
||||
if(players.isEmpty())
|
||||
return null;
|
||||
|
||||
return List.copyOf(players).get(0);
|
||||
}
|
||||
|
||||
public String parse(String text, UUID carrier, UUID player){
|
||||
try{
|
||||
return textCache.get(player, () -> papi.formatPlaceholders(text, carrier, player)).getNow(text);
|
||||
}catch(ExecutionException ex){
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
-7
@@ -36,6 +36,10 @@ import ch.andre601.advancedserverlist.core.parsing.ComponentParser;
|
||||
import ch.andre601.advancedserverlist.core.profiles.ServerListProfile;
|
||||
import ch.andre601.advancedserverlist.core.profiles.profile.ProfileManager;
|
||||
import ch.andre601.advancedserverlist.core.profiles.replacer.StringReplacer;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.JoinConfiguration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PingEventHandler{
|
||||
|
||||
@@ -138,19 +142,23 @@ public class PingEventHandler{
|
||||
GenericServer finalServer = event.createGenericServer(online, max, host);
|
||||
|
||||
if(ProfileManager.checkOption(entry.motd()) && ignoreMaintenance(event, "motd")){
|
||||
logger.debug(PingEventHandler.class, "'motd' option present. Applying '%s'...", String.join("\\n", entry.motd()));
|
||||
logger.debug(PingEventHandler.class, "'motd' option present. Applying ['%s']...", String.join("', '", entry.motd()));
|
||||
|
||||
event.setMotd(
|
||||
ComponentParser.list(entry.motd())
|
||||
List<Component> motd = entry.motd().stream()
|
||||
.map(line -> ComponentParser.text(line)
|
||||
.modifyText(text -> StringReplacer.replace(text, player, finalServer))
|
||||
.modifyText(text -> event.parsePAPIPlaceholders(text, player))
|
||||
.modifyText(text -> plugin.getCore().getTextCenterUtil().getCenteredText(text))
|
||||
.toComponent()
|
||||
);
|
||||
)
|
||||
.toList();
|
||||
|
||||
event.setMotd(Component.join(JoinConfiguration.separator(Component.newline()), motd));
|
||||
}
|
||||
|
||||
boolean hidePlayers = ProfileManager.checkOption(entry.hidePlayersEnabled());
|
||||
|
||||
if(hidePlayers && ignoreMaintenance(event, "hidePlayers")){
|
||||
if(hidePlayers && ignoreMaintenance(event, "hidePlayers") && ignoreMaintenance(event, "hidePlayersHover")){
|
||||
logger.debug(PingEventHandler.class, "'playerCount -> hidePlayers' enabled. Hiding player count...");
|
||||
|
||||
event.hidePlayers();
|
||||
@@ -167,8 +175,14 @@ public class PingEventHandler{
|
||||
);
|
||||
}
|
||||
|
||||
if(ProfileManager.checkOption(entry.players()) && !hidePlayers && ignoreMaintenance(event, "playerCountHover")){
|
||||
logger.debug(PingEventHandler.class, "'playerCount -> hover' option set. Applying '%s'...", String.join("\\n", entry.players()));
|
||||
boolean hidePlayersHover = ProfileManager.checkOption(entry.hidePlayersHoverEnabled());
|
||||
if(hidePlayersHover){
|
||||
logger.debug(PingEventHandler.class, "'playerCount -> hidePlayersHover' option set. Hiding Player Hover...");
|
||||
event.setPlayersHidden();
|
||||
}
|
||||
|
||||
if(ProfileManager.checkOption(entry.players()) && !hidePlayers && !hidePlayersHover && ignoreMaintenance(event, "playerCountHover")){
|
||||
logger.debug(PingEventHandler.class, "'playerCount -> hover' option set. Applying ['%s']...", String.join("', '", entry.players()));
|
||||
|
||||
event.setPlayers(entry.players(), player, server);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,9 @@ public class ConfigMigrator{
|
||||
public static ConfigurationTransformation.Versioned create(){
|
||||
return ConfigurationTransformation.versionedBuilder()
|
||||
.versionKey("configVersion")
|
||||
.addVersion(LATEST, fiveToSix())
|
||||
.addVersion(LATEST, sevenToEight())
|
||||
.addVersion(7, sixToSeven())
|
||||
.addVersion(6, fiveToSix())
|
||||
.addVersion(5, fourToFive())
|
||||
.addVersion(4, threeToFour())
|
||||
.addVersion(3, twoToThree())
|
||||
@@ -54,6 +56,26 @@ public class ConfigMigrator{
|
||||
.build();
|
||||
}
|
||||
|
||||
public static ConfigurationTransformation sevenToEight(){
|
||||
return ConfigurationTransformation.builder()
|
||||
.addAction(NodePath.path("disableDuringMaintenance"), (path, value) -> {
|
||||
value.node("hidePlayersHover").set(true);
|
||||
|
||||
return null;
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
public static ConfigurationTransformation sixToSeven(){
|
||||
return ConfigurationTransformation.builder()
|
||||
.addAction(NodePath.path(), (path, value) -> {
|
||||
value.node("printBanner").set(true);
|
||||
|
||||
return null;
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
public static ConfigurationTransformation fiveToSix(){
|
||||
return ConfigurationTransformation.builder()
|
||||
.addAction(NodePath.path("disableDuringMaintenance"), (path, value) -> {
|
||||
@@ -122,7 +144,7 @@ public class ConfigMigrator{
|
||||
versioned.apply(node);
|
||||
final int endVersion = versioned.version(node);
|
||||
if(startVersion < endVersion)
|
||||
logger.info("Migrated config.yml from v" + startVersion + " to v" + endVersion);
|
||||
logger.success("Successfully migrated config from <white>v%d</white> to <white>v%d</white>", startVersion, endVersion);
|
||||
}
|
||||
|
||||
return node;
|
||||
|
||||
@@ -72,7 +72,6 @@ public class FileHandler{
|
||||
}
|
||||
|
||||
public boolean loadConfig(){
|
||||
logger.info("Loading config.yml...");
|
||||
File folder = plugin.getPlugin().getFolderPath().toFile();
|
||||
if(!folder.exists() && !folder.mkdirs()){
|
||||
logger.warn("Couldn't create folder for plugin. Is it missing Write permissions?");
|
||||
@@ -87,7 +86,7 @@ public class FileHandler{
|
||||
}
|
||||
|
||||
Files.copy(stream, config);
|
||||
logger.info("Created new config.yml!");
|
||||
logger.success("Created new config.yml!");
|
||||
}catch(IOException ex){
|
||||
logger.warn("Cannot create config.yml for plugin.", ex);
|
||||
return false;
|
||||
@@ -98,7 +97,7 @@ public class FileHandler{
|
||||
}
|
||||
|
||||
public boolean loadProfiles(){
|
||||
logger.info("Loading profiles...");
|
||||
logger.info("[-] Loading profiles...");
|
||||
|
||||
if(createFile("default.yml")){
|
||||
return reloadProfiles();
|
||||
@@ -109,7 +108,7 @@ public class FileHandler{
|
||||
|
||||
public boolean createFile(String name){
|
||||
if(Files.notExists(profilesFolder) && profilesFolder.toFile().mkdirs()){
|
||||
logger.info("Successfully created profiles folder.");
|
||||
logger.debugSuccess(FileHandler.class, "Successfully created profiles folder.");
|
||||
}
|
||||
|
||||
File file = new File(profilesFolder.toFile(), name);
|
||||
@@ -126,7 +125,7 @@ public class FileHandler{
|
||||
Files.copy(stream, profilesFolder.resolve(name));
|
||||
return true;
|
||||
}catch(IOException ex){
|
||||
logger.warn("Encountered IOException while trying to create file %s", ex, name);
|
||||
logger.warn("Encountered IOException while trying to create file <white>%s</white>", ex, name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -160,7 +159,7 @@ public class FileHandler{
|
||||
|
||||
File[] files = profilesFolder.toFile().listFiles(((dir, name) -> name.endsWith(".yml")));
|
||||
if(files == null || files.length == 0){
|
||||
logger.warn("Cannot load files from profiles folder! No valid YAML files present.");
|
||||
logger.failure("Cannot load files from profiles folder! No valid YAML files present.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -170,11 +169,11 @@ public class FileHandler{
|
||||
continue;
|
||||
|
||||
profiles.add(ServerListProfile.Builder.resolve(file.getName(), tmp, logger).build());
|
||||
logger.info("Loaded '%s'!", file.getName());
|
||||
logger.debugSuccess(FileHandler.class, "Loaded '<white>%s</white>'!", file.getName());
|
||||
}
|
||||
|
||||
if(profiles.isEmpty()){
|
||||
logger.warn("Couldn't load any profile from profiles folder!");
|
||||
logger.failure("Couldn't load any profile from profiles folder!");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -191,7 +190,7 @@ public class FileHandler{
|
||||
try{
|
||||
return loader.load();
|
||||
}catch(IOException ex){
|
||||
logger.warn("Cannot load '%s' due to an IOException!", ex, path.toFile().getName());
|
||||
logger.warn("Cannot load '<white>%s</white>' due to an IOException!", ex, path.toFile().getName());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -218,11 +217,11 @@ public class FileHandler{
|
||||
}
|
||||
|
||||
private boolean makeBackup(){
|
||||
logger.info("Making backup of old config.yml...");
|
||||
logger.info("[-] Making backup of old config.yml...");
|
||||
|
||||
File backups = plugin.getPlugin().getFolderPath().resolve("backups").toFile();
|
||||
if(!backups.exists() && !backups.mkdirs()){
|
||||
logger.warn("Cannot create backups folder for migration!");
|
||||
logger.failure("Cannot create backups folder for migration!");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -231,16 +230,16 @@ public class FileHandler{
|
||||
File configBackup = new File(backups, "config_" + date.replace(":", "_") + ".yml");
|
||||
try{
|
||||
if(!configBackup.exists() && !configBackup.createNewFile()){
|
||||
logger.warn("Cannot create backup file for config.yml!");
|
||||
logger.failure("Cannot create backup file for config.yml!");
|
||||
return false;
|
||||
}
|
||||
|
||||
Files.copy(config, configBackup.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
logger.info("Saved backup as '%s'!", configBackup.getName());
|
||||
logger.success("Saved backup as '<white>%s</white>'!", configBackup.getName());
|
||||
|
||||
return true;
|
||||
}catch(IOException ex){
|
||||
logger.warn("Encountered IOException while trying to create a backup.", ex);
|
||||
logger.failure("Encountered IOException while trying to create a backup.", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,18 @@ package ch.andre601.advancedserverlist.core.interfaces;
|
||||
|
||||
public interface PluginLogger{
|
||||
|
||||
default void debugSuccess(Class<?> clazz, String msg, Object... args){
|
||||
debug(clazz, "[<green>✓</green>] " + msg, args);
|
||||
}
|
||||
|
||||
void debug(Class<?> clazz, String msg, Object... args);
|
||||
|
||||
void debugWarn(Class<?> clazz, String msg, Object... args);
|
||||
|
||||
default void success(String msg, Object... args){
|
||||
info("[<green>✓</green>] " + msg, args);
|
||||
}
|
||||
|
||||
void info(String msg, Object... args);
|
||||
|
||||
void warn(String msg, Object... args);
|
||||
@@ -38,4 +46,8 @@ public interface PluginLogger{
|
||||
void warn(String msg, Throwable throwable);
|
||||
|
||||
void warn(String msg, Throwable throwable, Object... args);
|
||||
|
||||
default void failure(String msg, Object... args){
|
||||
warn("[<red>x</red>] " + msg, args);
|
||||
}
|
||||
}
|
||||
+2
@@ -52,6 +52,8 @@ public interface GenericEventWrapper<F, P extends GenericPlayer>{
|
||||
|
||||
void setPlayers(List<String> players, P player, GenericServer server);
|
||||
|
||||
void setPlayersHidden();
|
||||
|
||||
void setFavicon(F favicon);
|
||||
|
||||
void setDefaultFavicon();
|
||||
|
||||
+10
-10
@@ -56,7 +56,7 @@ public class MiniMOTDConfigMigrator{
|
||||
|
||||
Path mainConf = core.getPlugin().getFolderPath().getParent().resolve(folder).resolve("main.conf");
|
||||
if(!Files.exists(mainConf)){
|
||||
logger.warn("[Migrator - MiniMOTD] Cannot find a main.conf file in /plugins/%s/ folder.", folder);
|
||||
logger.warn("[<white>Migrator - MiniMOTD</white>] Cannot find a main.conf file in <white>/plugins/%s/</white> folder.", folder);
|
||||
sender.sendErrorMsg(" -> <red>No main.conf file found.");
|
||||
return false;
|
||||
}
|
||||
@@ -76,13 +76,13 @@ public class MiniMOTDConfigMigrator{
|
||||
|
||||
config = node.get(MiniMOTDConfig.class);
|
||||
}catch(IOException ex){
|
||||
logger.warn("[Migrator - MiniMOTD] Encountered IOException while loading main.conf file.", ex);
|
||||
logger.warn("[<white>Migrator - MiniMOTD</white>] Encountered IOException while loading main.conf file.", ex);
|
||||
sender.sendErrorMsg(" -> <red>IOException while loading</red> main.conf <red>file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(config == null){
|
||||
logger.warn("[Migrator - MiniMOTD] Retrieved main.conf file returned null.");
|
||||
logger.warn("[<white>Migrator - MiniMOTD</white>] Retrieved main.conf file returned null.");
|
||||
sender.sendErrorMsg(" -> <red>Received invalid</red> main.conf <red>file.");
|
||||
return false;
|
||||
}
|
||||
@@ -147,14 +147,14 @@ public class MiniMOTDConfigMigrator{
|
||||
|
||||
boolean profilesInvalid = profiles.isEmpty() || profiles.stream().allMatch(ProfileEntry::isInvalid);
|
||||
if(entry.isInvalid() && profilesInvalid){
|
||||
logger.warn("[Migrator - MiniMOTD] Main ProfileEntry was invalid and profiles were empty.");
|
||||
logger.warn("[<white>Migrator - MiniMOTD</white>] Main ProfileEntry was invalid and profiles were empty.");
|
||||
sender.sendErrorMsg(" -> <red>Received invalid configuration.");
|
||||
return false;
|
||||
}
|
||||
|
||||
Path profile = core.getPlugin().getFolderPath().resolve("profiles").resolve("minimotd_migrated.yml");
|
||||
if(Files.exists(profile)){
|
||||
logger.warn("[Migrator - MiniMOTD] Cannot create new file minimotd_migrated.yml. One with the same name is already present.");
|
||||
logger.warn("[<white>Migrator - MiniMOTD</white>] Cannot create new file minimotd_migrated.yml. One with the same name is already present.");
|
||||
sender.sendErrorMsg(" -> <red>File</red> minimotd_migrated.yml <red>already present.");
|
||||
|
||||
return false;
|
||||
@@ -163,7 +163,7 @@ public class MiniMOTDConfigMigrator{
|
||||
try{
|
||||
Files.createFile(profile);
|
||||
}catch(IOException ex){
|
||||
logger.warn("[Migrator - MiniMOTD] Encountered an IOException while trying to create file minimotd_migrated.yml.", ex);
|
||||
logger.warn("[<white>Migrator - MiniMOTD</white>] Encountered an IOException while trying to create file minimotd_migrated.yml.", ex);
|
||||
sender.sendErrorMsg(" -> <red>File creation error.");
|
||||
|
||||
return false;
|
||||
@@ -180,14 +180,14 @@ public class MiniMOTDConfigMigrator{
|
||||
try{
|
||||
migrated = yamlLoader.load();
|
||||
}catch(IOException ex){
|
||||
logger.warn("[Migrator - MiniMOTD] Encountered an IOException while trying to load file minimotd_migrated.yml.", ex);
|
||||
logger.warn("[<white>Migrator - MiniMOTD</white>] Encountered an IOException while trying to load file minimotd_migrated.yml.", ex);
|
||||
sender.sendErrorMsg(" -> <red>File loading error.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if(migrated == null){
|
||||
logger.warn("[Migrator - MiniMOTD] Cannot migrate Configuration. ConfigurationNode was null.");
|
||||
logger.warn("[<white>Migrator - MiniMOTD</white>] Cannot migrate Configuration. ConfigurationNode was null.");
|
||||
sender.sendErrorMsg(" -> <red>File loading error.");
|
||||
|
||||
return false;
|
||||
@@ -202,7 +202,7 @@ public class MiniMOTDConfigMigrator{
|
||||
|
||||
migrated.set(entry);
|
||||
}catch(SerializationException ex){
|
||||
logger.warn("[Migrator - MiniMOTD] Encountered a SerializationException while setting values.", ex);
|
||||
logger.warn("[<white>Migrator - MiniMOTD</white>] Encountered a SerializationException while setting values.", ex);
|
||||
sender.sendErrorMsg(" -> <red>Error while updating file</red> minimotd_migrated.yml<red>.");
|
||||
|
||||
return false;
|
||||
@@ -214,7 +214,7 @@ public class MiniMOTDConfigMigrator{
|
||||
|
||||
return true;
|
||||
}catch(IOException ex){
|
||||
logger.warn("[Migrator - MiniMOTD] Encountered an IOException while trying to save new file minimotd_migrated.yml.", ex);
|
||||
logger.warn("[<white>Migrator - MiniMOTD</white>] Encountered an IOException while trying to save new file minimotd_migrated.yml.", ex);
|
||||
sender.sendErrorMsg(" -> <red>File saving error.");
|
||||
|
||||
return false;
|
||||
|
||||
+47
-41
@@ -59,26 +59,55 @@ public class SLPConfigMigrator{
|
||||
"(?<end>#[a-fA-F0-9]{6})" + // Ending color
|
||||
"%" +
|
||||
"(?<text>.+?)" + // Text
|
||||
"%gradient%");
|
||||
"%gradient%"
|
||||
);
|
||||
|
||||
private static final Map<String, String> STATIC_REPLACEMENTS = Map.of(
|
||||
"%player%", "${player name}",
|
||||
"%uuid%", "${player uuid}",
|
||||
"%_uuid_%", "${player uuid}",
|
||||
"%online%", "${server playersOnline}",
|
||||
"%max%", "${server playersMax}");
|
||||
"%max%", "${server playersMax}"
|
||||
);
|
||||
private static final Map<String, String> COLOR_TO_MINIMESSAGE_TAG = new HashMap<>(){{
|
||||
// Color Codes.
|
||||
put("a", "<green>");
|
||||
put("b", "<aqua>");
|
||||
put("c", "<red>");
|
||||
put("d", "<light_purple>");
|
||||
put("e", "<yellow>");
|
||||
put("f", "<white>");
|
||||
put("0", "<black>");
|
||||
put("1", "<dark_blue>");
|
||||
put("2", "<dark_green>");
|
||||
put("3", "<dark_aqua>");
|
||||
put("4", "<dark_red>");
|
||||
put("5", "<dark_purple>");
|
||||
put("6", "<gold>");
|
||||
put("7", "<grey>");
|
||||
put("8", "<dark_grey>");
|
||||
put("9", "<blue>");
|
||||
|
||||
// Formatting Codes
|
||||
put("k", "<obfuscated>");
|
||||
put("l", "<bold>");
|
||||
put("m", "<strikethrough>");
|
||||
put("n", "<underlined>");
|
||||
put("o", "<italic>");
|
||||
put("r", "<reset>");
|
||||
}};
|
||||
|
||||
public static int migrate(AdvancedServerList<?> core, CmdSender sender){
|
||||
ServerListPlusCore slpCore = ServerListPlusCore.getInstance();
|
||||
PluginLogger logger = core.getPlugin().getPluginLogger();
|
||||
if(slpCore == null){
|
||||
logger.warn("[Migrator - ServerListPlus] Cannot migrate ServerListPlus config. ServerListPlus is not active!");
|
||||
logger.warn("[<white>Migrator - ServerListPlus</white>] Cannot migrate ServerListPlus config. ServerListPlus is not active!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ServerStatusConf conf = slpCore.getConf(ServerStatusConf.class);
|
||||
if(conf == null){
|
||||
logger.warn("[Migrator - ServerListPlus] Cannot migrate ServerListPlus configuration. Received configuration was null.");
|
||||
logger.warn("[<white>Migrator - ServerListPlus</white>] Cannot migrate ServerListPlus configuration. Received configuration was null.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -101,7 +130,7 @@ public class SLPConfigMigrator{
|
||||
private static int parseConf(AdvancedServerList<?> core, CmdSender sender, PersonalizedStatusConf.StatusConf conf, Type type){
|
||||
PluginLogger logger = core.getPlugin().getPluginLogger();
|
||||
if(conf == null){
|
||||
logger.info("[Migrator - ServerListPlus] No StatusConf found for type %s. Skipping...", type.getName());
|
||||
logger.info("[<white>Migrator - ServerListPlus</white>] No StatusConf found for type <white>%s</white>. Skipping...", type.getName());
|
||||
sender.sendPrefixedMsg(" -> Not found! Skipping...");
|
||||
|
||||
return 0;
|
||||
@@ -141,7 +170,7 @@ public class SLPConfigMigrator{
|
||||
|
||||
boolean profilesInvalid = profileEntries.isEmpty() || profileEntries.stream().anyMatch(ProfileEntry::isInvalid);
|
||||
if(entry.isInvalid() && profilesInvalid){
|
||||
logger.warn("[Migrator - ServerListPlus] Unable to parse ServerListPlus configuration of type %s. Generated ProfileEntry was invalid.", type.getName());
|
||||
logger.warn("[<white>Migrator - ServerListPlus</white>] Unable to parse ServerListPlus configuration of type <white>%s</white>. Generated ProfileEntry was invalid.", type.getName());
|
||||
sender.sendErrorMsg(" -> <red>Received invalid Configuration.");
|
||||
|
||||
return 0;
|
||||
@@ -149,7 +178,7 @@ public class SLPConfigMigrator{
|
||||
|
||||
Path profile = core.getPlugin().getFolderPath().resolve("profiles").resolve(type.getFile());
|
||||
if(Files.exists(profile)){
|
||||
logger.warn("[Migrator - ServerListPlus] Cannot create new file %s. One with the same name is already present", type.getFile());
|
||||
logger.warn("[<white>Migrator - ServerListPlus</white>] Cannot create new file <white>%s</white>. One with the same name is already present", type.getFile());
|
||||
sender.sendErrorMsg(" -> <red>File</red> %s <red>already present.", type.getFile());
|
||||
|
||||
return 0;
|
||||
@@ -158,7 +187,7 @@ public class SLPConfigMigrator{
|
||||
try{
|
||||
Files.createFile(profile);
|
||||
}catch(IOException ex){
|
||||
logger.warn("[Migrator - ServerListPlus] Encountered an IOException while trying to create file %s.", ex, type.getFile());
|
||||
logger.warn("[<white>Migrator - ServerListPlus</white>] Encountered an IOException while trying to create file <white>%s</white>.", ex, type.getFile());
|
||||
sender.sendErrorMsg(" -> <red>File creation error.");
|
||||
|
||||
return 0;
|
||||
@@ -175,14 +204,14 @@ public class SLPConfigMigrator{
|
||||
try{
|
||||
node = loader.load();
|
||||
}catch(IOException ex){
|
||||
logger.warn("[Migrator - ServerListPlus] Encountered an IOException while trying to load file %s.", ex, type.getFile());
|
||||
logger.warn("[<white>Migrator - ServerListPlus</white>] Encountered an IOException while trying to load file <white>%s</white>.", ex, type.getFile());
|
||||
sender.sendErrorMsg(" -> <red>File loading error.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(node == null){
|
||||
logger.warn("[Migrator - ServerListPlus] Cannot migrate Configuration of type %s. ConfigurationNode was null.", type.getName());
|
||||
logger.warn("[<white>Migrator - ServerListPlus</white>] Cannot migrate Configuration of type <white>%s</white>. ConfigurationNode was null.", type.getName());
|
||||
sender.sendErrorMsg(" -> <red>File loading error.");
|
||||
|
||||
return 0;
|
||||
@@ -194,9 +223,7 @@ public class SLPConfigMigrator{
|
||||
|
||||
if(type == Type.PERSONALIZED){
|
||||
node.node("condition")
|
||||
.set("${player name} != \"" +
|
||||
core.getFileHandler().getString("Anonymous", "unknownPlayer", "name") +
|
||||
"\"");
|
||||
.set("${player name} != \"" + core.getFileHandler().getString("Anonymous", "unknownPlayer", "name") + "\"");
|
||||
}else
|
||||
if(type == Type.BANNED){
|
||||
node.node("condition")
|
||||
@@ -209,7 +236,7 @@ public class SLPConfigMigrator{
|
||||
|
||||
node.set(entry);
|
||||
}catch(SerializationException ex){
|
||||
logger.warn("[Migrator - ServerListPlus] Encountered a SerializationException while setting values.", ex);
|
||||
logger.warn("[<white>Migrator - ServerListPlus</white>] Encountered a SerializationException while setting values.", ex);
|
||||
sender.sendErrorMsg(" -> <red>Error while updating file</red> %s<red>.", type.getFile());
|
||||
|
||||
return 0;
|
||||
@@ -221,7 +248,7 @@ public class SLPConfigMigrator{
|
||||
|
||||
return 1;
|
||||
}catch(IOException ex){
|
||||
logger.warn("[Migrator - ServerListPlus] Encountered an IOException while trying to save new file %s.", ex, type.getFile());
|
||||
logger.warn("[<white>Migrator - ServerListPlus</white>] Encountered an IOException while trying to save new file <white>%s</white>.", ex, type.getFile());
|
||||
sender.sendErrorMsg(" -> <red>File saving error.");
|
||||
|
||||
return 0;
|
||||
@@ -372,32 +399,11 @@ public class SLPConfigMigrator{
|
||||
if(colorCodeMatcher.find()){
|
||||
builder.setLength(0);
|
||||
do{
|
||||
String color = colorCodeMatcher.group("color").toLowerCase(Locale.ROOT);
|
||||
switch(color){
|
||||
case "a" -> colorCodeMatcher.appendReplacement(builder, "<green>");
|
||||
case "b" -> colorCodeMatcher.appendReplacement(builder, "<aqua>");
|
||||
case "c" -> colorCodeMatcher.appendReplacement(builder, "<red>");
|
||||
case "d" -> colorCodeMatcher.appendReplacement(builder, "<light_purple>");
|
||||
case "e" -> colorCodeMatcher.appendReplacement(builder, "<yellow>");
|
||||
case "f" -> colorCodeMatcher.appendReplacement(builder, "<white>");
|
||||
case "k" -> colorCodeMatcher.appendReplacement(builder, "<obfuscated>");
|
||||
case "l" -> colorCodeMatcher.appendReplacement(builder, "<bold>");
|
||||
case "m" -> colorCodeMatcher.appendReplacement(builder, "<strikethrough>");
|
||||
case "n" -> colorCodeMatcher.appendReplacement(builder, "<underlined>");
|
||||
case "o" -> colorCodeMatcher.appendReplacement(builder, "<italic>");
|
||||
case "r" -> colorCodeMatcher.appendReplacement(builder, "<reset>");
|
||||
case "0" -> colorCodeMatcher.appendReplacement(builder, "<black>");
|
||||
case "1" -> colorCodeMatcher.appendReplacement(builder, "<dark_blue>");
|
||||
case "2" -> colorCodeMatcher.appendReplacement(builder, "<dark_green>");
|
||||
case "3" -> colorCodeMatcher.appendReplacement(builder, "<dark_aqua>");
|
||||
case "4" -> colorCodeMatcher.appendReplacement(builder, "<dark_red>");
|
||||
case "5" -> colorCodeMatcher.appendReplacement(builder, "<dark_purple>");
|
||||
case "6" -> colorCodeMatcher.appendReplacement(builder, "<gold>");
|
||||
case "7" -> colorCodeMatcher.appendReplacement(builder, "<grey>");
|
||||
case "8" -> colorCodeMatcher.appendReplacement(builder, "<dark_grey>");
|
||||
case "9" -> colorCodeMatcher.appendReplacement(builder, "<blue>");
|
||||
// This one shouldn't be reachable given the pattern, but you never know...
|
||||
default -> colorCodeMatcher.appendReplacement(builder, "&" + color);
|
||||
for(Map.Entry<String, String> colorCodes : COLOR_TO_MINIMESSAGE_TAG.entrySet()){
|
||||
if(!colorCodeMatcher.group("color").equalsIgnoreCase(colorCodes.getKey()))
|
||||
continue;
|
||||
|
||||
colorCodeMatcher.appendReplacement(builder, colorCodes.getValue());
|
||||
}
|
||||
}while(colorCodeMatcher.find());
|
||||
|
||||
|
||||
+42
-9
@@ -26,25 +26,58 @@
|
||||
package ch.andre601.advancedserverlist.core.objects;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ValueCache<T>{
|
||||
public abstract class CacheUtil{
|
||||
|
||||
private final Duration duration;
|
||||
private CacheValue<T> cache = null;
|
||||
protected final Duration duration;
|
||||
|
||||
public ValueCache(Duration duration){
|
||||
public CacheUtil(Duration duration){
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public T get(Supplier<T> supplier){
|
||||
if(cache == null || cache.expired())
|
||||
cache = new CacheValue<>(System.currentTimeMillis(), duration, supplier.get());
|
||||
public static class SingletonCache<T> extends CacheUtil{
|
||||
|
||||
return cache.value();
|
||||
private CacheValue<T> cacheValue = null;
|
||||
|
||||
public SingletonCache(Duration duration){
|
||||
super(duration);
|
||||
}
|
||||
|
||||
public T get(Supplier<T> supplier){
|
||||
if(cacheValue == null || cacheValue.expired())
|
||||
cacheValue = new CacheValue<>(System.currentTimeMillis(), duration, supplier.get());
|
||||
|
||||
return cacheValue.value();
|
||||
}
|
||||
}
|
||||
|
||||
private record CacheValue<T>(long timestamp, Duration duration, T value){
|
||||
public static class IdentifiableCache<K, V> extends CacheUtil{
|
||||
|
||||
private final Map<K, CacheValue<V>> cacheValues = new HashMap<>();
|
||||
|
||||
public IdentifiableCache(Duration duration){
|
||||
super(duration);
|
||||
}
|
||||
|
||||
public V get(K key, Supplier<V> supplier){
|
||||
CacheValue<V> value = cacheValues.get(key);
|
||||
if(value == null || value.expired()){
|
||||
value = new CacheValue<>(System.currentTimeMillis(), duration, supplier.get());
|
||||
cacheValues.put(key, value);
|
||||
}
|
||||
|
||||
return cacheValues.get(key).value();
|
||||
}
|
||||
|
||||
public void clear(){
|
||||
cacheValues.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public record CacheValue<T>(long timestamp, Duration duration, T value){
|
||||
public boolean expired(){
|
||||
return (timestamp < 0L) || System.currentTimeMillis() - timestamp >= duration.toMillis();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package ch.andre601.advancedserverlist.core.objects;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class PluginMessageUtil{
|
||||
|
||||
private static PluginMessageUtil instance;
|
||||
|
||||
private final List<String> knownServers = new CopyOnWriteArrayList<>();
|
||||
private final Map<String, ParsedText> text = new ConcurrentHashMap<>();
|
||||
|
||||
private PluginMessageUtil(){}
|
||||
|
||||
public static PluginMessageUtil get(){
|
||||
if(instance != null)
|
||||
return instance;
|
||||
|
||||
return (instance = new PluginMessageUtil());
|
||||
}
|
||||
|
||||
public void addServer(String name){
|
||||
this.knownServers.add(name);
|
||||
}
|
||||
|
||||
public List<String> getKnownServers(){
|
||||
return knownServers;
|
||||
}
|
||||
|
||||
public void clear(){
|
||||
knownServers.clear();
|
||||
}
|
||||
|
||||
public String getOrExecute(String key, String value, BiFunction<String, String, String> function){
|
||||
if(text.get(key) != null){
|
||||
return getOrDefault(key, value);
|
||||
}
|
||||
|
||||
return function.apply(key, value);
|
||||
}
|
||||
|
||||
public void queue(String key, String value, boolean parsed){
|
||||
text.put(key, new ParsedText(value, parsed));
|
||||
}
|
||||
|
||||
public String getOrDefault(String key, String def){
|
||||
if(text.get(key) == null)
|
||||
return def;
|
||||
|
||||
return text.get(key).parsed() ? text.remove(key).text() : text.get(key).text();
|
||||
}
|
||||
|
||||
private record ParsedText(String text, boolean parsed){}
|
||||
}
|
||||
@@ -29,7 +29,6 @@ import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ComponentParser{
|
||||
@@ -52,12 +51,13 @@ public class ComponentParser{
|
||||
return new ComponentParser(text);
|
||||
}
|
||||
|
||||
public static ComponentParser list(List<String> lines){
|
||||
return new ComponentParser(String.join("\n", lines));
|
||||
public static ComponentParser textFormatted(String text, Object... args){
|
||||
return text(String.format(text, args));
|
||||
}
|
||||
|
||||
public ComponentParser modifyText(Function<String, String> function){
|
||||
this.text = function.apply(text);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -73,4 +73,8 @@ public class ComponentParser{
|
||||
public String toString(){
|
||||
return legacy.serialize(toComponent());
|
||||
}
|
||||
|
||||
public String toStringStriped(){
|
||||
return mm.stripTags(text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package ch.andre601.advancedserverlist.core.parsing;
|
||||
|
||||
import ch.andre601.advancedserverlist.core.AdvancedServerList;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import net.kyori.adventure.text.flattener.ComponentFlattener;
|
||||
import net.kyori.adventure.text.flattener.FlattenerListener;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
public class TextCenterUtil{
|
||||
private final ComponentFlattener COMPONENT_FLATTENER = ComponentFlattener.textOnly();
|
||||
|
||||
private final double MAX_WIDTH = 270.0; // Max width of the MOTD
|
||||
private final int SPACE_WIDTH = 4;
|
||||
|
||||
private final Map<String, FontInfo> charWidths;
|
||||
|
||||
public TextCenterUtil(AdvancedServerList<?> plugin){
|
||||
charWidths = loadFontWidths(plugin);
|
||||
}
|
||||
|
||||
public String getCenteredText(String text){
|
||||
if(!text.toLowerCase(Locale.ROOT).startsWith("<center>"))
|
||||
return text;
|
||||
|
||||
if(getDefault() == null)
|
||||
return text;
|
||||
|
||||
text = text.substring("<center>".length());
|
||||
TextFlattenerListener listener = new TextFlattenerListener(this);
|
||||
|
||||
COMPONENT_FLATTENER.flatten(ComponentParser.text(text).toComponent(), listener);
|
||||
|
||||
double width = listener.getWidth();
|
||||
if(width >= MAX_WIDTH)
|
||||
return text;
|
||||
|
||||
return createSpaces(width) + text;
|
||||
}
|
||||
|
||||
private double getCharWidth(int codePoint, boolean isBold, String fontName){
|
||||
FontInfo font = charWidths.getOrDefault(fontName, getDefault());
|
||||
if(font == null)
|
||||
return -1.0;
|
||||
|
||||
int index = Arrays.binarySearch(font.codePoints, codePoint);
|
||||
if(index < 0){
|
||||
return isBold ? 7.0 : 6.0;
|
||||
}else{
|
||||
return isBold ? font.advanceBf[index] : font.advance[index];
|
||||
}
|
||||
}
|
||||
|
||||
private FontInfo getDefault(){
|
||||
return charWidths.get("minecraft:default");
|
||||
}
|
||||
|
||||
private String createSpaces(double length){
|
||||
double remainder = MAX_WIDTH - length;
|
||||
int padding = (int)Math.max(0, Math.floor(remainder / 2.0) - 1);
|
||||
|
||||
return " ".repeat(Math.floorDiv(padding, SPACE_WIDTH));
|
||||
}
|
||||
|
||||
private Map<String, FontInfo> loadFontWidths(AdvancedServerList<?> plugin){
|
||||
try(InputStream stream = plugin.getClass().getResourceAsStream("/char-widths.json.gz")){
|
||||
if(stream == null){
|
||||
plugin.getPlugin().getPluginLogger().warn("Internal char-widths.json.gz couldn't be retrieved.");
|
||||
plugin.getPlugin().getPluginLogger().warn("The <center> Placeholder won't work.");
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
GZIPInputStream gzipStream = new GZIPInputStream(stream);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(gzipStream));
|
||||
|
||||
return new Gson().fromJson(reader, new TypeToken<Map<String, FontInfo>>(){}.getType());
|
||||
}catch(IOException ex){
|
||||
plugin.getPlugin().getPluginLogger().warn("Encountered IOException while loading font widths.");
|
||||
plugin.getPlugin().getPluginLogger().warn("The <center> Placeholder won't work.");
|
||||
plugin.getPlugin().getPluginLogger().warn("Cause: <white>%s</white>", ex.getMessage());
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TextFlattenerListener implements FlattenerListener{
|
||||
private double width = 0.0;
|
||||
private boolean isBold = false;
|
||||
private String font = "minecraft:default";
|
||||
|
||||
private final TextCenterUtil instance;
|
||||
|
||||
public TextFlattenerListener(TextCenterUtil instance){
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushStyle(@NotNull Style style){
|
||||
if(style.hasDecoration(TextDecoration.BOLD))
|
||||
isBold = true;
|
||||
|
||||
if(style.font() != null)
|
||||
font = style.font().asString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void component(@NotNull String text){
|
||||
for(int i = 0; i < text.length(); i++){
|
||||
width += instance.getCharWidth(text.codePointAt(i), isBold, font);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popStyle(@NotNull Style style){
|
||||
if(style.hasDecoration(TextDecoration.BOLD))
|
||||
isBold = false;
|
||||
}
|
||||
|
||||
public double getWidth(){
|
||||
return width;
|
||||
}
|
||||
}
|
||||
|
||||
private record FontInfo(int[] codePoints, float[] advance, float[] advanceBf){}
|
||||
}
|
||||
+7
-7
@@ -51,13 +51,13 @@ public record ServerListProfile(int priority, String condition, ProfileEntry def
|
||||
boolean result = core.getParser().evaluate(condition, player, server, collector);
|
||||
|
||||
if(collector.hasWarnings()){
|
||||
logger.warn("Encountered %d Error(s) while parsing condition for file '%s':", collector.getWarnings().size(), file);
|
||||
logger.warn("Encountered <white>%d</white> Error(s) while parsing condition for file '<white>%s</white>':", collector.getWarnings().size(), file);
|
||||
|
||||
for(ParseWarnCollector.Context context : collector.getWarnings()){
|
||||
logger.warn(" - At position %s:", context.position() == -1 ? "Unknown" : context.position());
|
||||
logger.warn(" %s", condition);
|
||||
logger.warn(" ".repeat(context.position() + 4) + "^");
|
||||
logger.warn(" -> %s", context.message());
|
||||
logger.warn(" - At position <white>%s</white>:", context.position() == -1 ? "<italic>Unknown</italic>" : context.position());
|
||||
logger.warn(" <white>%s</white>", condition);
|
||||
logger.warn(" ".repeat(context.position() + 3) + "<red>^</red>");
|
||||
logger.warn(" <grey>-></grey> %s", context.message());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ public record ServerListProfile(int priority, String condition, ProfileEntry def
|
||||
try{
|
||||
this.profiles = node.node("profiles").getList(ProfileEntry.class);
|
||||
}catch(SerializationException ex){
|
||||
logger.warn("Encountered a SerializationException while resolving the profiles entry for %s", ex, fileName);
|
||||
logger.warn("Encountered a SerializationException while resolving the profiles entry for <white>%s</white>", ex, fileName);
|
||||
}
|
||||
|
||||
return this;
|
||||
@@ -159,7 +159,7 @@ public record ServerListProfile(int priority, String condition, ProfileEntry def
|
||||
try{
|
||||
this.defaultProfile = node.get(ProfileEntry.class);
|
||||
}catch(SerializationException ex){
|
||||
logger.warn("Encountered a SerializationException while resolving the global profile for %s", ex, fileName);
|
||||
logger.warn("Encountered a SerializationException while resolving the global profile for <white>%s</white>", ex, fileName);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
+8
-1
@@ -76,7 +76,14 @@ public class CustomExpressionTokenizer{
|
||||
}
|
||||
}
|
||||
|
||||
collector.appendWarningFormatted(position.getIndex(), "Illegal Token '%c'.", text.charAt(position.getIndex()));
|
||||
if(position.getIndex() >= text.length()){
|
||||
collector.appendWarningFormatted(
|
||||
"Current index position (%d) is larger or equal to Text length (%d) for condition '%s'",
|
||||
position.getIndex(), text.length(), text
|
||||
);
|
||||
}else{
|
||||
collector.appendWarningFormatted(position.getIndex(), "Illegal Token '%c'.", text.charAt(position.getIndex()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
+26
-31
@@ -27,8 +27,7 @@ package ch.andre601.advancedserverlist.core.profiles.handlers;
|
||||
|
||||
import ch.andre601.advancedserverlist.core.AdvancedServerList;
|
||||
import ch.andre601.advancedserverlist.core.interfaces.PluginLogger;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import ch.andre601.advancedserverlist.core.objects.CacheUtil;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
@@ -42,9 +41,13 @@ import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class FaviconHandler<F>{
|
||||
@@ -52,7 +55,7 @@ public class FaviconHandler<F>{
|
||||
private final AdvancedServerList<F> core;
|
||||
private final PluginLogger logger;
|
||||
private final ThreadPoolExecutor faviconThreadPool;
|
||||
private final Cache<String, CompletableFuture<F>> faviconCache;
|
||||
private final CacheUtil.IdentifiableCache<String, CompletableFuture<F>> faviconCache;
|
||||
|
||||
private final Map<String, F> localFavicons = new HashMap<>();
|
||||
private final HttpClient client = HttpClient.newHttpClient();
|
||||
@@ -62,42 +65,32 @@ public class FaviconHandler<F>{
|
||||
this.core = core;
|
||||
this.logger = core.getPlugin().getPluginLogger();
|
||||
this.faviconThreadPool = createFaviconThreadPool();
|
||||
this.faviconCache = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(core.getFileHandler().getLong(1, 1, "faviconCacheTime"), TimeUnit.MINUTES)
|
||||
.build();
|
||||
this.faviconCache = new CacheUtil.IdentifiableCache<>(Duration.ofMinutes(
|
||||
core.getFileHandler().getLong(1, 1, "faviconCacheTime")
|
||||
));
|
||||
|
||||
loadLocalFavicons();
|
||||
}
|
||||
|
||||
public F getFavicon(String input){
|
||||
if(localFavicons.size() > 0){
|
||||
if(!localFavicons.isEmpty()){
|
||||
logger.debug(FaviconHandler.class, "Current Local Favicons:");
|
||||
for(String key : localFavicons.keySet()){
|
||||
logger.debug(FaviconHandler.class, " - %s", key);
|
||||
}
|
||||
}
|
||||
|
||||
if(input.equalsIgnoreCase("random")){
|
||||
logger.debug(FaviconHandler.class, "Input matches 'random'. Returning random Favicon...");
|
||||
return getRandomized();
|
||||
}
|
||||
|
||||
if(localFavicons.containsKey(input.toLowerCase(Locale.ROOT))){
|
||||
logger.debug(FaviconHandler.class, "Input matches local favicon Image. Returning it...");
|
||||
return localFavicons.get(input.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
try{
|
||||
logger.debug(FaviconHandler.class, "Getting Favicon image from cache...");
|
||||
return faviconCache.get(input, () -> getFuture(input)).getNow(null);
|
||||
}catch(ExecutionException ex){
|
||||
logger.warn("Received ExecutionException while retrieving Favicon for '%s'.", ex, input);
|
||||
return null;
|
||||
}
|
||||
logger.debug(FaviconHandler.class, "Getting Favicon image from cache...");
|
||||
return faviconCache.get(input, () -> getFuture(input)).getNow(null);
|
||||
}
|
||||
|
||||
public void cleanCache(){
|
||||
faviconCache.invalidateAll();
|
||||
faviconCache.clear();
|
||||
loadLocalFavicons();
|
||||
}
|
||||
|
||||
@@ -113,7 +106,7 @@ public class FaviconHandler<F>{
|
||||
logger.debug(FaviconHandler.class, "Resolving image file '%s'...", input);
|
||||
return CompletableFuture.completedFuture(localFavicons.get(input.toLowerCase(Locale.ROOT)));
|
||||
}else{
|
||||
logger.debug(FaviconHandler.class, "Resolving Name/UUID as https://mc-heads.net/avatar/%s/64...", input);
|
||||
logger.debug(FaviconHandler.class, "Resolving '%s' as 'https://mc-heads.net/avatar/%s/64'...", input, input);
|
||||
return CompletableFuture.supplyAsync(() -> fromURL(core, "https://mc-heads.net/avatar/" + input + "/64"), this.faviconThreadPool);
|
||||
}
|
||||
}
|
||||
@@ -137,15 +130,15 @@ public class FaviconHandler<F>{
|
||||
private void loadLocalFavicons(){
|
||||
this.localFavicons.clear();
|
||||
|
||||
logger.info("Loading local image files as Favicons...");
|
||||
logger.info("[-] Loading local image files as Favicons...");
|
||||
|
||||
Path folder = core.getPlugin().getFolderPath().resolve("favicons");
|
||||
if(!Files.exists(folder)){
|
||||
try{
|
||||
Files.createDirectories(folder);
|
||||
logger.info("Created favicons folder.");
|
||||
logger.debugSuccess(FaviconHandler.class, "Created favicons folder.");
|
||||
}catch(IOException ex){
|
||||
logger.warn("Cannot create favicons folder. Encountered an IOException.", ex);
|
||||
logger.failure("Cannot create favicons folder. Encountered an IOException.", ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -154,8 +147,10 @@ public class FaviconHandler<F>{
|
||||
pathStream.filter(Files::isRegularFile)
|
||||
.filter(file -> file.getFileName().toString().endsWith(".png"))
|
||||
.forEach(this::loadFile);
|
||||
|
||||
logger.success("Loaded <white>%d</white> local Favicons.", localFavicons.size());
|
||||
}catch(IOException ex){
|
||||
logger.warn("Cannot load files from Favicons folder.", ex);
|
||||
logger.failure("Cannot load files from favicons folder.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,14 +158,14 @@ public class FaviconHandler<F>{
|
||||
try(InputStream stream = Files.newInputStream(path)){
|
||||
F favicon = createFavicon(stream);
|
||||
if(favicon == null){
|
||||
logger.warn("Cannot create Favicon from file '%s'. Received Favicon was null.", path.getFileName().toString());
|
||||
logger.failure("Cannot create Favicon from file '<white>%s</white>'. Received Favicon was null.", path.getFileName().toString());
|
||||
return;
|
||||
}
|
||||
|
||||
localFavicons.put(path.getFileName().toString().toLowerCase(Locale.ROOT), favicon);
|
||||
logger.info("Loaded file '%s' as Favicon.", path.getFileName().toString());
|
||||
logger.debugSuccess(FaviconHandler.class, "Loaded file '<white>%s</white>' as Favicon.", path.getFileName().toString());
|
||||
}catch(IOException ex){
|
||||
logger.warn("Cannot create Favicon from file '%s'. Encountered IOException.", ex);
|
||||
logger.failure("Cannot create Favicon from file '<white>%s</white>'. Encountered IOException.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,11 +180,11 @@ public class FaviconHandler<F>{
|
||||
try(InputStream stream = client.send(request, HttpResponse.BodyHandlers.ofInputStream()).body()){
|
||||
return createFavicon(stream);
|
||||
}catch(InterruptedException | IOException ex){
|
||||
logger.warn("Cannot create Favicon from URL '%s'. Encountered an Exception.", ex, url);
|
||||
logger.warn("Cannot create Favicon from URL '<white>%s</white>'. Encountered an Exception.", ex, url);
|
||||
return null;
|
||||
}
|
||||
}catch(URISyntaxException ex){
|
||||
logger.warn("Cannot create Favicon from URL '%s'. Encountered an IOException.", ex, url);
|
||||
logger.warn("Cannot create Favicon from URL '<white>%s</white>'. Encountered a URISyntaxException.", ex, url);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -79,40 +79,40 @@ public class PlayerHandler{
|
||||
try(BufferedReader reader = Files.newBufferedReader(cache)){
|
||||
cachedPlayers = gson.fromJson(reader, listType);
|
||||
}catch(IOException ex){
|
||||
logger.warn("Encountered IOException while reading the playercache.json file!", ex);
|
||||
logger.warn("Encountered IOException while reading the <white>playercache.json</white> file!", ex);
|
||||
return;
|
||||
}catch(JsonIOException | JsonSyntaxException ex){
|
||||
logger.warn("Encountered JsonSyntaxException while parsing playercache.json file!", ex);
|
||||
logger.warn("Encountered JsonSyntaxException while parsing <white>playercache.json</white> file!", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
// In case Gson messes up... I guess.
|
||||
if(cachedPlayers == null){
|
||||
logger.warn("Couldn't load players from playercache.json file. Is the JSON valid?");
|
||||
logger.warn("Couldn't load players from <white>playercache.json</white> file. Is the JSON valid?");
|
||||
// Create new ArrayList instance to avoid further issues.
|
||||
cachedPlayers = new ArrayList<>();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Loaded %d players into cache!", cachedPlayers.size());
|
||||
logger.success("Loaded <white>%d</white> players into cache!", cachedPlayers.size());
|
||||
}
|
||||
|
||||
public void save(){
|
||||
if(cachedPlayers.isEmpty()){
|
||||
logger.info("No players in cache to save. Skipping...");
|
||||
logger.debug(PlayerHandler.class, "No players in cache to save. Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
if(core.getFileHandler().getBoolean("disableCache")){
|
||||
logger.info("'disable_cache' is set to true. Skipping saving of cached players...");
|
||||
logger.debug(PlayerHandler.class, "[-] 'disable_cache' is set to true. Not saving cached players.");
|
||||
return;
|
||||
}
|
||||
|
||||
try(BufferedWriter writer = Files.newBufferedWriter(cache, StandardCharsets.UTF_8)){
|
||||
gson.toJson(cachedPlayers, listType, writer);
|
||||
logger.info("Successfully saved playercache.json file.");
|
||||
logger.success("Successfully saved <white>playercache.json</white> file.");
|
||||
}catch(IOException ex){
|
||||
logger.warn("Encountered IOException while saving cached players to playercache.json!", ex);
|
||||
logger.failure("Encountered IOException while saving cached players to <white>playercache.json</white>!", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -56,6 +56,7 @@ public class ProfileManager{
|
||||
NullBool extraPlayersEnabled = resolve(defEntry, entry, ProfileEntry::extraPlayersEnabled);
|
||||
NullBool maxPlayersEnabled = resolve(defEntry, entry, ProfileEntry::maxPlayersEnabled);
|
||||
NullBool onlinePlayersEnabled = resolve(defEntry, entry, ProfileEntry::onlinePlayersEnabled);
|
||||
NullBool hidePlayersHoverEnabled = resolve(defEntry, entry, ProfileEntry::hidePlayersHoverEnabled);
|
||||
String extraPlayersCount = resolve(defEntry, entry, ProfileEntry::extraPlayersCount);
|
||||
String maxPlayersCount = resolve(defEntry, entry, ProfileEntry::maxPlayersCount);
|
||||
String onlinePlayersCount = resolve(defEntry, entry, ProfileEntry::onlinePlayersCount);
|
||||
@@ -69,6 +70,7 @@ public class ProfileManager{
|
||||
.extraPlayersEnabled(extraPlayersEnabled)
|
||||
.maxPlayersEnabled(maxPlayersEnabled)
|
||||
.onlinePlayersEnabled(onlinePlayersEnabled)
|
||||
.hidePlayersHoverEnabled(hidePlayersHoverEnabled)
|
||||
.extraPlayersCount(extraPlayersCount)
|
||||
.maxPlayersCount(maxPlayersCount)
|
||||
.onlinePlayersCount(onlinePlayersCount)
|
||||
@@ -96,6 +98,7 @@ public class ProfileManager{
|
||||
NullBool extraPlayersEnabled = resolveNullBool(node, "playerCount", "extraPlayers", "enabled");
|
||||
NullBool maxPlayersEnabled = resolveNullBool(node, "playerCount", "maxPlayers", "enabled");
|
||||
NullBool onlinePlayersEnabled = resolveNullBool(node, "playerCount", "onlinePlayers", "enabled");
|
||||
NullBool hidePlayersHoverEnabled = resolveNullBool(node, "playerCount", "hidePlayersHover");
|
||||
String extraPlayers = resolveNullableString(node, "playerCount", "extraPlayers", "amount");
|
||||
String maxPlayers = resolveNullableString(node, "playerCount", "maxPlayers", "amount");
|
||||
String onlinePlayers = resolveNullableString(node, "playerCount", "onlinePlayers", "amount");
|
||||
@@ -109,6 +112,7 @@ public class ProfileManager{
|
||||
.extraPlayersEnabled(extraPlayersEnabled)
|
||||
.maxPlayersEnabled(maxPlayersEnabled)
|
||||
.onlinePlayersEnabled(onlinePlayersEnabled)
|
||||
.hidePlayersHoverEnabled(hidePlayersHoverEnabled)
|
||||
.extraPlayersCount(extraPlayers)
|
||||
.maxPlayersCount(maxPlayers)
|
||||
.onlinePlayersCount(onlinePlayers)
|
||||
|
||||
+1
@@ -53,6 +53,7 @@ public class ProfileSerializer implements TypeSerializer<ProfileEntry>{
|
||||
node.node("playerCount", "text").set(profile.playerCountText());
|
||||
node.node("favicon").set(profile.favicon());
|
||||
node.node("playerCount", "hidePlayers").set(profile.hidePlayersEnabled().getOrDefault(false));
|
||||
node.node("playerCount", "hidePlayersHover").set(profile.hidePlayersHoverEnabled().getOrDefault(false));
|
||||
node.node("playerCount", "extraPlayers", "enabled").set(profile.extraPlayersEnabled().getOrDefault(false));
|
||||
node.node("playerCount", "extraPlayers", "amount").set((profile.extraPlayersCount() == null) ? "0" : profile.extraPlayersCount());
|
||||
node.node("playerCount", "maxPlayers", "enabled").set(profile.maxPlayersEnabled().getOrDefault(false));
|
||||
|
||||
+7
-7
@@ -72,6 +72,9 @@ public class StringReplacer{
|
||||
boolean identified = false;
|
||||
boolean invalid = true;
|
||||
|
||||
int placeholderStart = position.getIndex() - 1;
|
||||
int valueStart = position.getIndex() - 1;
|
||||
|
||||
char[] chars = input.substring(index).toCharArray();
|
||||
for(final char c : chars){
|
||||
index++;
|
||||
@@ -83,6 +86,7 @@ public class StringReplacer{
|
||||
|
||||
if(c == ' ' && !identified){
|
||||
identified = true;
|
||||
valueStart = index + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -98,10 +102,6 @@ public class StringReplacer{
|
||||
|
||||
StringBuilder raw = new StringBuilder();
|
||||
|
||||
// Condition parsing needs one extra increase of index... for some reason
|
||||
if(collector != null)
|
||||
index++;
|
||||
|
||||
position.setIndex(index);
|
||||
|
||||
if(invalid){
|
||||
@@ -111,7 +111,7 @@ public class StringReplacer{
|
||||
raw.append(' ').append(valuesStr);
|
||||
|
||||
if(collector != null)
|
||||
collector.appendWarningFormatted(index, "Placeholder '%s' does not have a closing bracket (})", raw.toString());
|
||||
collector.appendWarningFormatted(index, "Placeholder '<white>%s</white>' does not have a closing bracket (})", raw.toString());
|
||||
|
||||
return raw.toString();
|
||||
}
|
||||
@@ -126,7 +126,7 @@ public class StringReplacer{
|
||||
raw.append('}');
|
||||
|
||||
if(collector != null)
|
||||
collector.appendWarningFormatted(index, "Placeholder '%s' does not have any available PlaceholderProvider", raw.toString());
|
||||
collector.appendWarningFormatted(placeholderStart, "Placeholder '<white>%s</white>' does not have any available PlaceholderProvider", raw.toString());
|
||||
|
||||
return raw.toString();
|
||||
}
|
||||
@@ -141,7 +141,7 @@ public class StringReplacer{
|
||||
raw.append('}');
|
||||
|
||||
if(collector != null)
|
||||
collector.appendWarningFormatted(index, "Placeholder '%s' has an invalid value String '%s'", raw.toString(), valuesStr);
|
||||
collector.appendWarningFormatted(valueStart, "Placeholder '<white>%s</white>' has an invalid value String '<white>%s</white>'", raw.toString(), valuesStr);
|
||||
|
||||
return raw.toString();
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -48,6 +48,14 @@ checkUpdates: true
|
||||
#
|
||||
debug: false
|
||||
|
||||
#
|
||||
# Should the "ASL" ASCI art be displayed on plugin startup?
|
||||
# Setting this to false will hide the banner.
|
||||
#
|
||||
# Default: true
|
||||
#
|
||||
printBanner: true
|
||||
|
||||
#
|
||||
# Sets how long Favicons should be cached. Value is in minutes with the lowest possible value being 1.
|
||||
#
|
||||
@@ -84,6 +92,12 @@ disableDuringMaintenance:
|
||||
#
|
||||
hidePlayers: true
|
||||
#
|
||||
# Whether hiding player list on hover should be disabled while Maintenance mode is active.
|
||||
#
|
||||
# Default: true
|
||||
#
|
||||
hidePlayersHover: true
|
||||
#
|
||||
# Whether player count text changing should be disabled while Maintenance mode is active.
|
||||
#
|
||||
# Default: true
|
||||
|
||||
@@ -95,6 +95,15 @@ playerCount:
|
||||
#
|
||||
hidePlayers: false
|
||||
#
|
||||
# Set whether the List of players is shown when hovering over the player count.
|
||||
# Setting this to true will also make the plugin ignore the hover option below.
|
||||
#
|
||||
# If not present, defaults to false.
|
||||
#
|
||||
# Read more: https://asl.andre601.ch/profiles/#hideplayershover
|
||||
#
|
||||
hidePlayersHover: false
|
||||
#
|
||||
# Set the lines shown when the player hovers over the player count.
|
||||
# Unlike the MOTD option does this one only support basic color and formatting codes. Any RGB colors will be
|
||||
# downsampled to the nearest matching color.
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package ch.andre601.advancedserverlist.test;
|
||||
package ch.andre601.advancedserverlist.core;
|
||||
|
||||
import ch.andre601.advancedserverlist.core.interfaces.PluginLogger;
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package ch.andre601.advancedserverlist.test;
|
||||
package ch.andre601.advancedserverlist.core;
|
||||
|
||||
import ch.andre601.advancedserverlist.core.profiles.conditions.ProfileConditionParser;
|
||||
import ch.andre601.expressionparser.ParseWarnCollector;
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package ch.andre601.advancedserverlist.test;
|
||||
package ch.andre601.advancedserverlist.core;
|
||||
|
||||
import com.unascribed.flexver.FlexVerComparator;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -17,7 +17,7 @@
|
||||
import re
|
||||
|
||||
from mkdocs.config.defaults import MkDocsConfig
|
||||
from mkdocs.structure.files import File, Files
|
||||
from mkdocs.structure.files import Files
|
||||
from mkdocs.structure.pages import Page
|
||||
|
||||
def on_page_markdown(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block announce %}
|
||||
<span class="twemoji">{% include ".icons/octicons/info-24.svg" %}</span>
|
||||
<b>NEW:</b> Added a <a href="{{ base_url | url }}/getting-started/tutorials">Tutorial Section</a> to the Wiki!
|
||||
<span class="twemoji">{% include ".icons/material/party-popper.svg" %}</span>
|
||||
Celebrating <a href="https://andre601.ch/blog/2024/12-13-history-of-asl/" target="_blank" rel="nofollow"><b>25,000 downloads!</b></a> Thank you!
|
||||
<span class="twemoji">{% include ".icons/material/party-popper.svg" %}</span>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,183 @@
|
||||
---
|
||||
icon: octicons/plus-circle-24
|
||||
---
|
||||
|
||||
AdvancedServerList allows you to add your own Placeholders to be useable through its `${identifier value}` format.
|
||||
The benefit of this method over alternatives such as using PlaceholderAPI placeholders is availability and the fact that these placeholders can be used within the [`condition` option](../profiles/index.md#condition) of the plugin.
|
||||
|
||||
Adding your own placeholder is relatively easy and only requires the main API module to be used in your dependencies, unless you need platform-specific values (Explained later on).
|
||||
|
||||
## Create your PlaceholderProvider class
|
||||
|
||||
AdvancedServerList's placeholder system uses a dedicated [`PlaceholderProvider`](reference/api/ch.andre601.advancedserverlist.api/placeholderprovider.md) class where the placeholder gets defined and its values parsed.
|
||||
In order to add your own, you'll need to first create a new class and make it extend the `PlaceholderProvider` class:
|
||||
|
||||
```java title="MyPlaceholder.java"
|
||||
public class MyPlaceholder extends PlaceholderProvider {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Your IDE should now report issues, namely a missing method to override and a missing Constructor to match the extended class' one. Your IDE should allow you to easily implement those, if not, then manually add the constructor with a `String` argument and the `public String parsePlaceholder` method with `String`, [`GenericPlayer`](reference/api/ch.andre601.advancedserverlist.api/objects/genericplayer.md) and [`GenericServer`](reference/api/ch.andre601.advancedserverlist.api/objects/genericserver.md) arguments:
|
||||
|
||||
```java { title="MyPlaceholder.java" .annotate }
|
||||
public class MyPlaceholder extends PlaceholderProvider {
|
||||
|
||||
public MyPlaceholder(String identifier) {
|
||||
super(identifier); // (1)
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parsePlaceholder(String placeholder, GenericPlayer player, GenericServer server) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. It is recommended to replace the Constructor with a No-Args Constructor and set the placeholder name directly in the `super` call:
|
||||
```java
|
||||
public MyPlaceholder() {
|
||||
super("myplaceholder");
|
||||
}
|
||||
```
|
||||
|
||||
## Parsing placeholders
|
||||
|
||||
To now parse your placeholders, all you need to do is edit the `parsePlaceholder` method.
|
||||
AdvancedServerList calls this method whenever it finds a matching identifier of a PlaceholderProvider, with the `String` argument being the actual value after the identifier and the `GenericPlayer` and `GenericServer` arguments holding the Player and server values respectively.
|
||||
|
||||
As an example, assuming you simply want to return `Hello` for the placeholder `${myplaceholder sayHello}`, this would be the code for it:
|
||||
|
||||
```java { title="MyPlaceholder.java" .annotate }
|
||||
public class MyPlaceholder extends PlaceholderProvider {
|
||||
|
||||
public MyPlaceholder() {
|
||||
super("myplaceholder");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parsePlaceholder(String placeholder, GenericPlayer player, GenericServer server) {
|
||||
if (placeholder.equals("sayHello")) {
|
||||
return "Hello";
|
||||
}
|
||||
|
||||
return null; // (1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. Returning `null` tells AdvancedServerList that the placeholder was invalid, making it return the placeholder unchanged, while also reporting an error, if used in `condition`.
|
||||
|
||||
## Registering the Placeholder
|
||||
|
||||
The final step now would be to register your placeholder for AdvancedServerList to use.
|
||||
To do this, obtain an instance of the [`AdvancedServerListAPI`](reference/api/ch.andre601.advancedserverlist.api/advancedserverlistapi.md) through its static `get()` method and then call `addPlaceholderProvider(PlaceholderProvider)` through it:
|
||||
|
||||
/// tab | :fontawesome-solid-paper-plane: Paper Example
|
||||
```java title="MyPlugin.java"
|
||||
public class MyPlugin extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
AdvancedServerListAPI api = AdvancedServerListAPI.get();
|
||||
api.addPlaceholderProvider(new MyPlaceholder());
|
||||
getLogger().info("Hooked into AdvancedServerList!");
|
||||
}
|
||||
}
|
||||
```
|
||||
///
|
||||
|
||||
/// tab | :octicons-git-merge-24: BungeeCord Example
|
||||
```java title="MyPlugin.java"
|
||||
public class MyPlugin extends Plugin {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
AdvancedServerListAPI api = AdvancedServerListAPI.get();
|
||||
api.addPlaceholderProvider(new MyPlaceholder());
|
||||
getLogger().info("Hooked into AdvancedServerList!");
|
||||
}
|
||||
}
|
||||
```
|
||||
///
|
||||
|
||||
/// tab | :fontawesome-solid-paper-plane: Velocity Example
|
||||
```java title="MyPlugin.java"
|
||||
@Plugin(
|
||||
id = "myplugin",
|
||||
version = "1.0.0",
|
||||
authors = {"You?"},
|
||||
dependencies = {
|
||||
@Dependency(id = "advancedserverlist")
|
||||
}
|
||||
)
|
||||
public class MyPlugin {
|
||||
|
||||
private final ProxyServer proxy;
|
||||
private final Logger logger;
|
||||
|
||||
@Inject
|
||||
public MyPlugin(ProxyServer proxy, Logger logger) {
|
||||
this.proxy = proxy;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onProxyInitialization(ProxyInitializeEvent event) {
|
||||
AdvancedServerListAPI api = AdvancedServerListAPI.get();
|
||||
api.addPlaceholderProvider(new MyPlaceholder());
|
||||
logger.info("Hooked into AdvancedServerList!");
|
||||
}
|
||||
}
|
||||
```
|
||||
///
|
||||
|
||||
## You're done
|
||||
|
||||
Your Placeholders should now be registered successfully if they check the following:
|
||||
|
||||
- The provided identifier is not `null` nor empty.
|
||||
- The provided identifier does not contain spaces.
|
||||
- The provided identifier is not already registered in the API.
|
||||
|
||||
## Note on `GenericPlayer` and `GenericServer`
|
||||
|
||||
The different platform APIs of AdvancedServerList provide extended versions of the `GenericPlayer` and `GenericServer` instances used, providing additional methods otherwise not available.
|
||||
Should you want to use these specific instances will you need to add the platform API as an additional dependency to your project and then cast the `GenericPlayer` and/or `GenericServer` instance to the platform specific version.
|
||||
|
||||
Below is an example using the `platform-bukkit` API (This assumes your plugin is one running on Paper):
|
||||
|
||||
```java { title="MyPlaceholder.java" .annotate }
|
||||
public class MyPlaceholder extends PlaceholderProvider {
|
||||
|
||||
public MyPlaceholder() {
|
||||
super("myplaceholder");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parsePlaceholder(String placeholder, GenericPlayer player, GenericServer server) {
|
||||
if (!(player instanceof BukkitPlayer bukkitPlayer)) {
|
||||
return null; // (1)
|
||||
}
|
||||
|
||||
return switch (placeholder) {
|
||||
case "playedBefore" -> String.valueOf(bukkitPlayer.hasPlayedBefore());
|
||||
case "whitelisted" -> String.valueOf(bukkitPlayer.isWhitelisted());
|
||||
case "banned" -> String.valueOf(bukkitPlayer.isBanned());
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. Depending on the Java version you use will you need to alter the code:
|
||||
```java
|
||||
if (!(player instanceof BukkitPlayer)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// You need to cast it separately.
|
||||
BukkitPlayer bukkitPlayer = (BukkitPlayer) player;
|
||||
|
||||
// ...use bukkitPlayer here.
|
||||
```
|
||||
+15
-9
@@ -7,6 +7,15 @@ icon: octicons/log-24
|
||||
This page lists the recent changes made towards the AdvancedServerListAPI.
|
||||
They are ordered newest to oldest.
|
||||
|
||||
## v4.1.0 { #v4.1.0 }
|
||||
|
||||
### :octicons-plus-circle-24:{ .changelog-added title="Added" } Added { #v-added }
|
||||
|
||||
- New setter method added to [`ProfileEntry.Builder`][profileentry.builder] class:
|
||||
- [`hidePlayersHoverEnabled(NullBool)`][hidePlayersHoverEnabled]
|
||||
|
||||
[hidePlayersHoverEnabled]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#hideplayershoverenabled(nullbool)
|
||||
|
||||
## v4.0.0 { #v4.0.0 }
|
||||
|
||||
### :octicons-alert-24:{ .changelog-breaking title="Breaking Changes" } Breaking Changes { #v4.0.0-breaking }
|
||||
@@ -21,7 +30,7 @@ They are ordered newest to oldest.
|
||||
- [`onlinePlayersCount(String)`][onlinePlayersCount]
|
||||
- [`onlinePlayersEnabled(NullBool)`][onlinePlayersEnabled]
|
||||
|
||||
[onlinePlayersCount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#onlineplayerscount(integer)
|
||||
[onlinePlayersCount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#onlineplayerscount(string)
|
||||
[onlinePlayersEnabled]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#onlineplayersenabled(nullbool)
|
||||
|
||||
## v3.3.0 { #v3.3.0 }
|
||||
@@ -53,11 +62,11 @@ They are ordered newest to oldest.
|
||||
- [`playerCountText(String)`][playercounttext]
|
||||
- [`players(List<String>)`][players]
|
||||
|
||||
[extraplayerscount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#extraplayerscount(integer)
|
||||
[extraplayerscount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#extraplayerscount(string)
|
||||
[extraplayersenabled]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#extraplayersenabled(nullbool)
|
||||
[favicon]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#favicon(string)
|
||||
[hideplayersenabled]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#hideplayersenabled(nullbool)
|
||||
[maxplayerscount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#maxplayerscount(integer)
|
||||
[maxplayerscount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#maxplayerscount(string)
|
||||
[maxplayersenabled]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#maxplayersenabled(nullbool)
|
||||
[motd]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#motd(list<string>)
|
||||
[playercounttext]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#playercounttext(string)
|
||||
@@ -119,9 +128,9 @@ They are ordered newest to oldest.
|
||||
- [`maxPlayersEnabled()`][maxplayersenabled] - Returns the value for `playerCount -> maxPlayers -> enabled` as `NullBool`
|
||||
- [`maxPlayersCount()`][maxplayerscount] - Returns the value of `playerCount -> maxPlayers -> amount` as nullable `Integer`
|
||||
- New setters in [`ProfileEntry.Builder`][profileentry.builder]:
|
||||
- [`setMaxPlayersEnabled(NullBool)`][setmaxplayersenabled] - Sets whether the max players feature is enabled or not.
|
||||
- [`setMaxPlayersCount(Integer)`][setmaxplayerscount] - Sets the number to use for the max players count.
|
||||
- [`setExtraPlayersCount(Integer)`][setextraplayersecount] - Replacement for [deprecated `setExtraPlayerCount(Integer)` method](#v3.1.0-deprecated).
|
||||
- `setMaxPlayersEnabled(NullBool)`- Sets whether the max players feature is enabled or not.
|
||||
- `setMaxPlayersCount(Integer)` - Sets the number to use for the max players count.
|
||||
- `setExtraPlayersCount(Integer)` - Replacement for [deprecated `setExtraPlayerCount(Integer)` method](#v3.1.0-deprecated).
|
||||
|
||||
### :octicons-zap-24:{ .changelog-breaking title="Deprecated" } Deprecated { #v3.1.0-deprecated }
|
||||
|
||||
@@ -132,9 +141,6 @@ They are ordered newest to oldest.
|
||||
[maxplayerscount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/index.md#maxplayerscount()
|
||||
|
||||
[profileentry.builder]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md
|
||||
[setmaxplayersenabled]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#setmaxplayersenabled(nullbool)
|
||||
[setmaxplayerscount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#setmaxplayerscount(integer)
|
||||
[setextraplayersecount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#setextraplayerscount(integer)
|
||||
|
||||
----
|
||||
|
||||
|
||||
+7
-111
@@ -84,127 +84,23 @@ dependencies {
|
||||
```
|
||||
///
|
||||
|
||||
## Adding your own Placeholders
|
||||
## Tutorials
|
||||
|
||||
<!-- admo:info This requires the main api module -->
|
||||
Dedicated pages to explain aspects of the API are available:
|
||||
|
||||
The API allows you to add your own placeholders which would be available through the `${<identifier> <values>}` placeholder Pattern in a Server List profile.
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
To add your own placeholders, follow these steps:
|
||||
- [:octicons-plus-circle-24: Adding Placeholders](adding-placeholders.md)
|
||||
- [:octicons-broadcast-24: Listening for Events](listening-for-events.md)
|
||||
|
||||
### 1. Create a Placeholder class
|
||||
</div>
|
||||
|
||||
You should first create a new class and make it extend the abstract [`PlaceholderProvider`][placeholderprovider]. This class includes a constructor and a method you need to add, so do that:
|
||||
|
||||
```java { .annotate title="Creating PlaceholderProvider class" }
|
||||
public class MyPlaceholders extends PlaceholderProvider {
|
||||
|
||||
public MyPlaceholders(String identifier) {
|
||||
super(identifier); // (1)
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parsePlaceholder(String placeholder, GenericPlayer player, GenericServer server) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. It is recommended to replace this constructor with a no-args Constructor and have the placeholder name set in the `super` directly:
|
||||
```java
|
||||
public MyPlaceholders() {
|
||||
super("myplaceholders");
|
||||
}
|
||||
```
|
||||
|
||||
The [`parsePlaceholder(String, GenericPlayer, GenericServer)`][parseplaceholder] method is used by AdvancedServerList to replace a matching placeholder with a value.
|
||||
What you return is completely up to you. Just keep in mind that returning `null` will be treated as an invalid placeholder by AdvancedServerList, resulting in the placeholder being returned as-is without any changes.
|
||||
|
||||
[placeholderprovider]: reference/api/ch.andre601.advancedserverlist.api/placeholderprovider.md
|
||||
[parseplaceholder]: reference/api/ch.andre601.advancedserverlist.api/placeholderprovider.md#parseplaceholder(string,-genericplayer,-genericserver)
|
||||
|
||||
/// details | Example of final class
|
||||
type: example
|
||||
|
||||
```java title="MyPlaceholders.java"
|
||||
public class MyPlaceholders extends PlaceholderProvider {
|
||||
|
||||
public MyPlaceholders() {
|
||||
super("myplaceholders");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parsePlaceholder(String placeholder, GenericPlayer player, GenericServer server) {
|
||||
if(placeholder.equalsIgnoreCase("hello"))
|
||||
return "Hello " + player.getName();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
///
|
||||
|
||||
### 2. Register the Placeholders
|
||||
|
||||
Next step should be to register the placeholder. To achieve this, first obtain an instance of the [`AdvancedServerListAPI`][advancedserverlistapi] by using the static [`get()`][get] method. After that call [`addPlaceholderProvider(PlaceholderProvider placeholderProvider)`][addplaceholderprovider].
|
||||
Your code may look similar to this:
|
||||
|
||||
```java title="Registering PlaceholderProvider class"
|
||||
public class MyPlugin extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
AdvancedServerListAPI api = AdvancedServerListAPI.get();
|
||||
|
||||
api.addPlaceholderProvider(new MyPlaceholders());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
[advancedserverlistapi]: reference/api/ch.andre601.advancedserverlist.api/advancedserverlistapi.md
|
||||
[get]: reference/api/ch.andre601.advancedserverlist.api/advancedserverlistapi.md#get()
|
||||
[addplaceholderprovider]: reference/api/ch.andre601.advancedserverlist.api/advancedserverlistapi.md#addplaceholderprovider(placeholderprovider)
|
||||
|
||||
This should register your PlaceholderExpansion as long as it is valid, meaning that the identifier...
|
||||
|
||||
- ...is not `null`
|
||||
- ...is not containing spaces
|
||||
- ...is not using a name already registered in the API
|
||||
|
||||
### 3. Declare AdvancedServerList as (soft)depend
|
||||
## Declare AdvancedServerList as (soft)depend
|
||||
|
||||
The final thing you should make sure is to define AdvancedServerList as a depend or soft-depend for your plugin, to make sure it loads after AdvancedServerList.
|
||||
|
||||
Below are example setups for Spigot, Paper, BungeeCord and Velocity:
|
||||
|
||||
/// tab | :simple-spigotmc: Spigot
|
||||
//// tab | Softdepend
|
||||
```yaml title="plugin.yml"
|
||||
name: "MyPlugin"
|
||||
author: "author"
|
||||
version: "1.0.0"
|
||||
|
||||
main: "com.example.plugin.ExamplePlugin"
|
||||
|
||||
softdepend:
|
||||
- AdvancedServerList
|
||||
```
|
||||
////
|
||||
|
||||
//// tab | Depend
|
||||
```yaml title="plugin.yml"
|
||||
name: "MyPlugin"
|
||||
author: "author"
|
||||
version: "1.0.0"
|
||||
|
||||
main: "com.example.plugin.ExamplePlugin"
|
||||
|
||||
depend:
|
||||
- AdvancedServerList
|
||||
```
|
||||
////
|
||||
///
|
||||
|
||||
/// tab | :fontawesome-solid-paper-plane: Paper
|
||||
//// tab | Softdepend
|
||||
```yaml title="paper-plugin.yml"
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
icon: octicons/broadcast-24
|
||||
---
|
||||
|
||||
AdvancedServerList provides some events that your plugin can listen to.
|
||||
These events are structurally the same across all platforms, yet still require the usage of Platform-specific APIs due to how some events are registered and handled on some platforms.
|
||||
|
||||
The plugin currently offers the following events:
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- [PreServerListSetEvent](#preserverlistsetevent)
|
||||
- [PostServerListSetEvent](#postserverlistsetevent)
|
||||
|
||||
</div>
|
||||
|
||||
## PreServerListSetEvent
|
||||
|
||||
This event is executed before AdvancedServerList tries to modify the player list.
|
||||
|
||||
### Methods { #preserverlistsetevent-methods }
|
||||
|
||||
The following methods are available to use:
|
||||
|
||||
| Method | Description |
|
||||
|--------------------------|--------------------------------------------------------------------------------|
|
||||
| `getEntry()` | Returns the [`ProfileEntry`][profileentry] that AdvancedServerList should use. |
|
||||
| `setEntry(ProfileEntry)` | Sets the [`ProfileEntry`][profileentry] that should be used. |
|
||||
| `isCancelled()` | Returns wether this event was cancelled or not. |
|
||||
| `setCancelled(boolean)` | Sets the event's cancelled state. |
|
||||
|
||||
### Notes { #preserverlistsetevent-notes }
|
||||
|
||||
- Providing `null` as the `ProfileEntry` for `setEntry(ProfileEntry)` is not allowed. Also, a copy of the provided `ProfileEntry` is made and used.
|
||||
|
||||
## PostServerListSetEvent
|
||||
|
||||
This event is executed after AdvancedServerList completed the server list modifications.
|
||||
|
||||
### Methods { #postserverlistsetevent-methods }
|
||||
|
||||
| Method | Description |
|
||||
|--------------------------|--------------------------------------------------------------------------|
|
||||
| `getEntry()` | Returns the [`ProfileEntry`][profileentry] that AdvancedServerList used. |
|
||||
|
||||
### Notes { #postserverlistsetevent-notes }
|
||||
|
||||
- The returned `ProfileEntry` is not an actual representation of what is being displayed to the player and rather an instance of values the plugin received.
|
||||
The returned value may also be `null` should the Ping Event be cancelled due to things such as an invalid Protocol or the [`PreServerListSetEvent`](#preserverlistsetevent) being cancelled.
|
||||
|
||||
## Registering the Events
|
||||
|
||||
To register an event, please refer to our platforms documentation on how to do this.
|
||||
|
||||
## What is a ProfileEntry?
|
||||
|
||||
Both events provide and use a so called [`ProfileEntry`][profileentry].
|
||||
The `ProfileEntry` is a record, representing the values of a [Server List Profile](../profiles/index.md), meaning all values you can set in the profile configuration (Except condition and priority) will be available as a `ProfileEntry`.
|
||||
|
||||
It is important to note, that the `ProfileEntry` can be a representation of the [`profiles` option](../profiles/index.md#profiles), the global options or a mixture of both.
|
||||
If you want to create your own instance, you'll need to use the [`ProfileEntry.Builder` class](reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md) as the `ProfileEntry` is unmodifiable.
|
||||
|
||||
A convenience method in the form of `builder()` exists to create a Builder instance of the ProfileEntry with its values already pre-set.
|
||||
|
||||
[profileentry]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/index.md
|
||||
+28
-5
@@ -13,14 +13,16 @@ constructors:
|
||||
methods:
|
||||
- name: 'extraPlayersCount'
|
||||
description: |
|
||||
Sets the number of players to add to the online players to use as the new max players value.<br>
|
||||
Sets the value to resolve into a number by AdvancedServerList.<br>
|
||||
Strings not resolving into valid numbers will be treated as this option not being set.<br>
|
||||
<br>
|
||||
This option has no effect when <a href="../#extraplayersenabled()"><code>extraPlayersEnabled</code></a> is set to <a href="../../../objects/nullbool/#false"><code>NullBool.FALSE</code></a> or <a href="../../objects/nullbool/#not_set"><code>NullBool.NOT_SET</code></a>.<br>
|
||||
<br>
|
||||
Set this to <code>null</code> to not alter the max player count. Alternatively, set <a href="#setextraplayersenabled(nullbool)"><code>setExtraPlayersEnabled(NullBool)</code></a> to <a href="../../../objects/nullbool/#false"><code>NullBool.FALSE</code></a>.
|
||||
Set this to <code>null</code> to not alter the max player count. Alternatively <a href="#setextraplayersenabled(nullbool)"><code>disable extra Players</code></a>.
|
||||
parameters:
|
||||
- name: 'extraPlayersCount'
|
||||
description: 'The number of extra players to add.'
|
||||
type: 'Integer'
|
||||
type: 'String'
|
||||
attribute:
|
||||
- 'nullable'
|
||||
returns: 'This Builder after the extra player count has been set. Useful for chaining.'
|
||||
@@ -71,9 +73,26 @@ methods:
|
||||
throws:
|
||||
- name: 'IllegalArgumentException'
|
||||
description: 'Thrown by the <code>CheckUtil</code> in case <code>null</code> has been provided as parameter.'
|
||||
- name: 'hidePlayersHoverEnabled'
|
||||
description: |
|
||||
Sets ehther the Hover List of online Players should be hidden or not.<br>
|
||||
<br>
|
||||
Set to <a href="../../../objects/nullbool/#not_set"><code>NullBool.NOT_SET</code></a> to not set this.
|
||||
parameters:
|
||||
- name: 'hidePlayersHoverEnabled'
|
||||
description: 'Whether the online players feature should be enabled or not.'
|
||||
type: 'NullBool'
|
||||
attribute:
|
||||
- 'notnull'
|
||||
returns: 'This Builder after the NullBool has been set. Useful for chaining.'
|
||||
throws:
|
||||
- name: 'IllegalArgumentException'
|
||||
description: 'Thrown by the <code>CheckUtil</code> in case <code>null</code> has been provided as parameter.'
|
||||
- name: 'maxPlayersCount'
|
||||
description: |
|
||||
Sets the number of max players allowed to join this server.<br>
|
||||
Sets the value to resolve into a number by AdvancedServerList.<br>
|
||||
Strings not resolving into valid numbers will be treated as this option not being set.<br>
|
||||
<br>
|
||||
This option has no effect when <a href="#maxplayersenabled(nullbool)">maxPlayersEnabled</a> is set to <a href="../../../objects/nullbool/#false"><code>NullBool.FALSE</code></a> or <a href="../../../objects/nullbool/#not_set"><code>NullBool.NOT_SET</code></a>.<br>
|
||||
<br>
|
||||
Set this to <code>null</code> to not alter the max player count. Alternatively <a href="#maxplayersenabled(nullbool)">disable max Players</a>.
|
||||
@@ -122,7 +141,11 @@ methods:
|
||||
- name: 'onlinePlayersCount'
|
||||
description: |
|
||||
Sets the value to resolve into a number by AdvancedServerList.<br>
|
||||
Strings not resolving into valid numbers will be treated as this option not being set.
|
||||
Strings not resolving into valid numbers will be treated as this option not being set.<br>
|
||||
<br>
|
||||
This option has no effect when <a href="#onlineplayersenabled(nullbool)">maxPlayersEnabled</a> is set to <a href="../../../objects/nullbool/#false"><code>NullBool.FALSE</code></a> or <a href="../../../objects/nullbool/#not_set"><code>NullBool.NOT_SET</code></a>.<br>
|
||||
<br>
|
||||
Set this to <code>null</code> to not alter the online player count. Alternatively <a href="#onlineplayersenabled(nullbool)">disable online Players</a>.
|
||||
parameters:
|
||||
- name: 'onlinePlayersCount'
|
||||
description: 'The number of online players to set.'
|
||||
|
||||
+52
-11
@@ -42,14 +42,24 @@ constructors:
|
||||
type: NullBool
|
||||
attributes:
|
||||
- notnull
|
||||
- name: 'onlinePlayersCount'
|
||||
description: 'The number to set for the Online Players count.'
|
||||
type: String
|
||||
attributes:
|
||||
- nullable
|
||||
- name: 'hidePlayersHoverEnabled'
|
||||
description: 'Whether the online Players Hover should beenabled.'
|
||||
type: NullBool
|
||||
attributes:
|
||||
- notnull
|
||||
- name: 'extraPlayersCount'
|
||||
description: 'The number to add to the online players for the max players number.'
|
||||
type: Integer
|
||||
description: 'The number to add to the online Players for the extra Players.'
|
||||
type: String
|
||||
attributes:
|
||||
- nullable
|
||||
- name: 'maxPlayersCount'
|
||||
description: 'The number to use for the max players count.'
|
||||
type: Integer
|
||||
description: 'The number to set for the max Players count.'
|
||||
type: String
|
||||
attributes:
|
||||
- nullable
|
||||
seealso:
|
||||
@@ -74,8 +84,11 @@ methods:
|
||||
<li><a href="#hideplayersenabled()"><code>hidePlayersEnabled</code></a>: <a href="../../objects/nullbool/#not_set"><code>NullBool.NOT_SET</code></a></li>
|
||||
<li><a href="#extraplayersenabled()"><code>extraPlayersEnabled</code></a>: <a href="../../objects/nullbool/#not_set"><code>NullBool.NOT_SET</code></a></li>
|
||||
<li><a href="#maxplayersenabled()"><code>maxPlayersEnabled</code></a>: <a href="../../objects/nullbool/#not_set"><code>NullBoo.NOT_SET</code></a></li>
|
||||
<li><a href="#onlineplayersenabled()"><code>onlinePlayersEnabled</code></a>: <a href="../../objects/nullbool/#not_set"><code>NullBoo.NOT_SET</code></a></li>
|
||||
<li><a href="#hideplayershoverenabled()"><code>hidePlayersHoverEnabled</code></a>: <a href="../../objects/nullbool/#not_set"><code>NullBoo.NOT_SET</code></a></li>
|
||||
<li><a href="#extraplayerscount()"><code>extraPlayersCount</code></a>: <code>null</code></li>
|
||||
<li><a href="#maxplayerscount()"><code>maxPlayersCount</code></a>: <code>null</code></li>
|
||||
<li><a href="#onlineplayerscount()"><code>onlinePlayersCount</code></a>: <code>null</code></li>
|
||||
</ul>
|
||||
returns: 'New ProfileEntry instance with empty/null values defined'
|
||||
attributes:
|
||||
@@ -139,7 +152,7 @@ methods:
|
||||
- name: 'hidePlayersEnabled'
|
||||
description: |
|
||||
Whether the player count should be hidden or not in this ProfileEntry.<br>
|
||||
To get the actual boolean value, append <a href="../../objects/nullbool/#getordefault()"><code>getOrDefault(boolean)</code></a>.
|
||||
To get the actual boolean value, append <a href="../../objects/nullbool/#getordefault(boolean)"><code>getOrDefault(boolean)</code></a>.
|
||||
returns: 'Whether the player count should be hidden or not in this ProfileEntry.'
|
||||
type:
|
||||
name: 'NullBool'
|
||||
@@ -148,7 +161,7 @@ methods:
|
||||
- name: 'extraPlayersEnabled'
|
||||
description: |
|
||||
Whether the extra players feature should be used or not in this ProfileEntry.<br>
|
||||
To get the actual boolean value, append <a href="../../objects/nullbool/#getordefault()"><code>getOrDefault(boolean)</code></a>.
|
||||
To get the actual boolean value, append <a href="../../objects/nullbool/#getordefault(boolean)"><code>getOrDefault(boolean)</code></a>.
|
||||
returns: 'Whether the extra players feature should be used or not in this ProfileEntry.'
|
||||
type:
|
||||
name: 'NullBool'
|
||||
@@ -157,19 +170,39 @@ methods:
|
||||
- name: 'maxPlayersEnabled'
|
||||
description: |
|
||||
Whether the max players feature should be used or not.<br>
|
||||
To get the actual boolean value, append <a href="../../objects/nullbool/#getordefault()"><code>getOrDefault(boolean)</code></a>.
|
||||
To get the actual boolean value, append <a href="../../objects/nullbool/#getordefault(boolean)"><code>getOrDefault(boolean)</code></a>.
|
||||
returns: 'Whether the max players feature should be used or not.'
|
||||
type:
|
||||
name: 'NullBool'
|
||||
type: 'object'
|
||||
link: '../../objects/nullbool/'
|
||||
- name: 'onlinePlayersEnabled'
|
||||
description: |
|
||||
Whether the online players feature should be used or not.<br>
|
||||
To get the actual boolean value, append <a href="../../objects/nullbool/#getordefault(boolean)"><code>getOrDefault(boolean)</code></a>.
|
||||
returns: 'Whether the online players feature should be used or not.'
|
||||
type:
|
||||
name: 'NullBool'
|
||||
type: 'object'
|
||||
link: '../../objects/nullbool/'
|
||||
- name: 'hidePlayersHoverEnabled'
|
||||
description: |
|
||||
Whether the Hover List of online players should be hidden or not.<br>
|
||||
To get the actual boolean value, use <a href="../../objects/nullbool/#getordefault()"><code>getOrDefault(boolean)</code></a> of the returned <a href="../../objects/nullbool"><code>NullBool</code></a>.<br>
|
||||
<br>
|
||||
When true, this option will force the <a href="#players()"><code>players()</code></a> list to be ignored.
|
||||
returns: 'Whether the Hover List of online players should be hidden or not.'
|
||||
type:
|
||||
name: 'NullBool'
|
||||
type: 'object'
|
||||
link: '../../objects/nullbool/'
|
||||
- name: 'extraPlayersCount'
|
||||
description: 'Gets the currently set number of extra players to use by this ProfileEntry. May be <code>null</code>.'
|
||||
returns: 'Possibly-null integer number of extra players used by this ProfileEntry.'
|
||||
description: 'Gets the currently set number of extra players of this ProfileEntry.'
|
||||
returns: 'The current number of extra players used by this ProfileEntry.'
|
||||
attributes:
|
||||
- nullable
|
||||
type:
|
||||
name: 'Integer'
|
||||
name: 'String'
|
||||
type: 'object'
|
||||
- name: 'maxPlayersCount'
|
||||
description: 'Gets the currently set number of max players of this ProfileEntry.'
|
||||
@@ -177,7 +210,15 @@ methods:
|
||||
attributes:
|
||||
- nullable
|
||||
type:
|
||||
name: 'Integer'
|
||||
name: 'String'
|
||||
type: 'object'
|
||||
- name: 'onlinePlayersCount'
|
||||
description: 'Gets the currently set number of online players of this ProfileEntry.'
|
||||
returns: 'The current number of online players used by this ProfileEntry.'
|
||||
attributes:
|
||||
- nullable
|
||||
type:
|
||||
name: 'String'
|
||||
type: 'object'
|
||||
- name: 'isInvalid'
|
||||
description: |
|
||||
|
||||
@@ -44,4 +44,15 @@
|
||||
|
||||
.md-source__repository .md-source__facts {
|
||||
animation: facts .25s ease-in;
|
||||
}
|
||||
|
||||
.md-nav--secondary li.md-nav__item > .md-nav__link {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.md-banner__inner.md-grid.md-typeset {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -41,10 +41,7 @@ AdvancedServerList is available on the following places (Please note the current
|
||||
|
||||
</div>
|
||||
|
||||
[modrinth]: https://modrinth.com/plugin/advancedserverlist
|
||||
[codeberg]: https://codeberg.org/Andre601/AdvancedServerList
|
||||
[spigot]: https://www.spigotmc.org/resources/102910/
|
||||
[hangar]: https://hangar.papermc.io/Andre_601/AdvancedServerList
|
||||
|
||||
The plugin is available for Paper, BungeeCord, Waterfall and Velocity, and has been tested on these platforms. It may support additional forks, but this is not guaranteed.
|
||||
|
||||
@@ -247,6 +244,15 @@ playerCount:
|
||||
#
|
||||
hidePlayers: false
|
||||
#
|
||||
# Set whether the List of players is shown when hovering over the player count.
|
||||
# Setting this to true will also make the plugin ignore the hover option below.
|
||||
#
|
||||
# If not present, defaults to false.
|
||||
#
|
||||
# Read more: https://asl.andre601.ch/profiles/#hideplayershover
|
||||
#
|
||||
hidePlayersHover: false
|
||||
#
|
||||
# Set the lines shown when the player hovers over the player count.
|
||||
# Unlike the MOTD option does this one only support basic color and formatting codes. Any RGB colors will be
|
||||
# downsampled to the nearest matching color.
|
||||
@@ -424,6 +430,14 @@ checkUpdates: true
|
||||
#
|
||||
debug: false
|
||||
|
||||
#
|
||||
# Should the "ASL" ASCI art be displayed on plugin startup?
|
||||
# Setting this to false will hide the banner.
|
||||
#
|
||||
# Default: true
|
||||
#
|
||||
printBanner: true
|
||||
|
||||
#
|
||||
# Sets how long Favicons should be cached. Value is in minutes with the lowest possible value being 1.
|
||||
#
|
||||
@@ -460,6 +474,12 @@ disableDuringMaintenance:
|
||||
#
|
||||
hidePlayers: true
|
||||
#
|
||||
# Whether hiding player list on hover should be disabled while Maintenance mode is active.
|
||||
#
|
||||
# Default: true
|
||||
#
|
||||
hidePlayersHover: true
|
||||
#
|
||||
# Whether player count text changing should be disabled while Maintenance mode is active.
|
||||
#
|
||||
# Default: true
|
||||
@@ -483,6 +503,12 @@ disableDuringMaintenance:
|
||||
# Default: true
|
||||
#
|
||||
maxPlayers: true
|
||||
#
|
||||
# Whether the online players feature should be disabled while Maintenance mode is active.
|
||||
#
|
||||
# Default: true
|
||||
#
|
||||
onlinePlayers: true
|
||||
|
||||
#
|
||||
# DO NOT EDIT!
|
||||
@@ -490,7 +516,7 @@ disableDuringMaintenance:
|
||||
# This is used internally to determine if the config needs to be migrated.
|
||||
# Changing or even removing this option could result in your config being broken.
|
||||
#
|
||||
config-version: 5
|
||||
configVersion: 7
|
||||
```
|
||||
///
|
||||
|
||||
|
||||
+42
-3
@@ -50,6 +50,12 @@ AdvancedServerList supports the following platforms:
|
||||
----
|
||||
|
||||
A simple guide on how to get started with AdvancedServerList.
|
||||
|
||||
----
|
||||
|
||||
**Other Pages:**
|
||||
|
||||
- [:octicons-project-roadmap-24: Tutorials](getting-started/tutorials.md)
|
||||
|
||||
- [:octicons-rel-file-path-24: **Commands**](commands/index.md)
|
||||
|
||||
@@ -62,6 +68,14 @@ AdvancedServerList supports the following platforms:
|
||||
----
|
||||
|
||||
Pages containing information about the Server List Profile System and all its available options.
|
||||
|
||||
----
|
||||
|
||||
**Other Pages:**
|
||||
|
||||
- [:octicons-file-code-24: Expressions](profiles/expressions.md)
|
||||
- [:octicons-pencil-24: Formatting](profiles/formatting.md)
|
||||
- [:material-percent: Placeholders](profiles/placeholders.md)
|
||||
|
||||
- [:octicons-list-unordered-24: **Examples**](examples/index.md)
|
||||
|
||||
@@ -74,12 +88,37 @@ AdvancedServerList supports the following platforms:
|
||||
----
|
||||
|
||||
Pages explaining how to migrate from one version of the plugin to another.
|
||||
|
||||
----
|
||||
|
||||
**Other Pages:**
|
||||
|
||||
- Plugins
|
||||
- [:octicons-arrow-right-24: MiniMOTD](migration/plugins/minimotd.md)
|
||||
- [:octicons-arrow-right-24: ServerListPlus](migration/plugins/serverlistplus.md)
|
||||
- Versions
|
||||
- [:octicons-arrow-right-24: v3.8.0 to v3.9.0](migration/versions/v3.8.0-to-v3.9.0.md)
|
||||
- [:octicons-arrow-right-24: v3.7.0 to v3.8.0](migration/versions/v3.7.0-to-v3.8.0.md)
|
||||
- [:octicons-arrow-right-24: < v3.7.0 to v3.7.0](migration/versions/pre-v3.7.0-to-v3.7.0.md)
|
||||
- [:octicons-arrow-right-24: v1.9.0 to v1.10.0](migration/versions/v1.9.0-to-v1.10.0.md)
|
||||
- [:octicons-arrow-right-24: v1.5.3 to v1.6.0](migration/versions/v1.5.3-to-v1.6.0.md)
|
||||
|
||||
- [:octicons-command-palette-24: **API**](api/index.md)
|
||||
|
||||
----
|
||||
|
||||
Pages all about the API of AdvancedServerList.
|
||||
|
||||
----
|
||||
|
||||
**Other Pages:**
|
||||
|
||||
- [:octicons-plus-circle-24: Adding Placeholders](api/adding-placeholders.md)
|
||||
- [:octicons-broadcast-24: Listening for Events](api/listening-for-events.md)
|
||||
- [:octicons-log-24: Changelog](api/changelog.md)
|
||||
- [:octicons-link-external-24: Javadocs](api/javadocs.md)
|
||||
- [:simple-codeberg: Source Code](api/source.md)
|
||||
- [:octicons-file-code-24: Reference](api/reference/index.md)
|
||||
|
||||
- [:octicons-plus-circle-24: **Addons**](addons/index.md)
|
||||
|
||||
@@ -87,10 +126,10 @@ AdvancedServerList supports the following platforms:
|
||||
|
||||
Pages listing 1st-party and 3rd-party add-ons for AdvancedServerList.
|
||||
|
||||
- [:material-percent: **PlaceholderAPI**](placeholderapi/index.md)
|
||||
|
||||
----
|
||||
|
||||
Information about available placeholders to use in PlaceholderAPI and how to use placeholders from other plugins in AdvancedServerList using PlaceholderAPI.
|
||||
**Other Pages:**
|
||||
|
||||
- [:octicons-stop-24: BanPlugins Addon](addons/banplugins.md)
|
||||
|
||||
</div>
|
||||
@@ -11,13 +11,13 @@ These pages may include important information about the migration process and wh
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- [**MiniMOTD**](minimotd.md)
|
||||
- [**MiniMOTD**](plugins/minimotd.md)
|
||||
|
||||
----
|
||||
|
||||
Migrate from the Plugin [MiniMOTD](https://github.com/jpenilla/MiniMOTD){ target="_blank" rel="nofollow" }
|
||||
|
||||
- [**ServerListPlus**](serverlistplus.md)
|
||||
- [**ServerListPlus**](plugins/serverlistplus.md)
|
||||
|
||||
----
|
||||
|
||||
@@ -32,31 +32,31 @@ These guides are only for the Server List Profile files, as the config.yml gets
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- [**v3.8.0 to v3.9.0**](v3_8_0-to-v3_9_0.md)
|
||||
- [**v3.8.0 to v3.9.0**](versions/v3.8.0-to-v3.9.0.md)
|
||||
|
||||
----
|
||||
|
||||
Migrate from version 3.8.0 to 3.9.0.
|
||||
|
||||
- [**v3.7.0 to v3.8.0**](v3_7_0-to-v3_8_0.md)
|
||||
- [**v3.7.0 to v3.8.0**](versions/v3.7.0-to-v3.8.0.md)
|
||||
|
||||
----
|
||||
|
||||
Migrating from version 3.7.0 to 3.8.0.
|
||||
|
||||
- [**< v3.7.0 to v3.7.0**](pre-v3_7_0-to-v3_7_0.md)
|
||||
- [**< v3.7.0 to v3.7.0**](versions/pre-v3.7.0-to-v3.7.0.md)
|
||||
|
||||
----
|
||||
|
||||
Migrate from any version prior to 3.7.0 to 3.7.0 or newer.
|
||||
|
||||
- [**v1.9.0 to v1.10.0**](v1_9_0-to-v1_10_0.md)
|
||||
- [**v1.9.0 to v1.10.0**](versions/v1.9.0-to-v1.10.0.md)
|
||||
|
||||
----
|
||||
|
||||
Migrate from version 1.9.0 to 1.10.0.
|
||||
|
||||
- [**v1.5.3 to v1.6.0**](v1_5_3-to-v1_6_0.md)
|
||||
- [**v1.5.3 to v1.6.0**](versions/v1.5.3-to-v1.6.0.md)
|
||||
|
||||
----
|
||||
|
||||
|
||||
@@ -11,14 +11,14 @@ Version `4.10.0` added support for migrating your existing `main.conf` configura
|
||||
|
||||
## How to Migrate
|
||||
|
||||
Execute the command [`/asl migrate minimotd`](../commands/index.md#migrate) to start the migration process. Make sure the `main.conf` file is present in `/plugins/MiniMOTD/` (`minimotd-velocity` on Velocity).
|
||||
Execute the command [`/asl migrate minimotd`](../../commands/index.md#migrate) to start the migration process. Make sure the `main.conf` file is present in `/plugins/MiniMOTD/` (`minimotd-velocity` on Velocity).
|
||||
|
||||
## Example
|
||||
|
||||
Below can you find a full example of a `main.conf` file and the corresponding migrated files from AdvancedServerList:
|
||||
|
||||
/// tab | :octicons-file-code-24: main.conf
|
||||
```lighty
|
||||
```ruby
|
||||
icon-enabled=true
|
||||
motd-enabled=true
|
||||
motds=[
|
||||
@@ -48,7 +48,7 @@ The following Favicon options get converted into an equivalent one:
|
||||
|
||||
## How to Migrate
|
||||
|
||||
Execute the command [`/asl migrate serverlistplus`](../commands/index.md#migrate) to start the migration process. Make sure that ServerListPlus is present and enabled.
|
||||
Execute the command [`/asl migrate serverlistplus`](../../commands/index.md#migrate) to start the migration process. Make sure that ServerListPlus is present and enabled.
|
||||
|
||||
## Example
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ icon: octicons/arrow-right-24
|
||||
# < v3.7.0 to v3.7.0
|
||||
|
||||
Version 3.7.0 of AdvancedServerList brought significant changes to its conditions system.
|
||||
Namely, the `conditions` option is now deprecated and planned for removal in a future release of the Plugin, and the new [`condition`](../profiles/index.md#condition) option should now be used instead.
|
||||
Namely, the `conditions` option is now deprecated and planned for removal in a future release of the Plugin, and the new [`condition`](../../profiles/index.md#condition) option should now be used instead.
|
||||
|
||||
## Migration
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+88
-37
@@ -4,57 +4,108 @@ icon: octicons/pencil-24
|
||||
|
||||
# Formatting
|
||||
|
||||
All text options, with exception of [`Conditions`](index.md#condition), allow the usage of formatting options using [MiniMessage].
|
||||
This page explains specific points of formatting in AdvancedServerList, be it [text formatting](#text-formatting) or special quirks of [YAML formatting](#yaml-formatting).
|
||||
If you have questions, don't hesitate to join the [M.O.S.S. Discord][discord] and ask in the `#asl-support` channel.
|
||||
|
||||
/// info | Online Tool
|
||||
The devs of the Adventure library provide a handy online tool to create the right formatting codes to display the text properly.
|
||||
You can find the tool at https://webui.advntr.dev/
|
||||
## Text Formatting
|
||||
|
||||
AdvancedServerList uses the [MiniMessage Text Formatting][minimessage] for all formatting of text options ([`motd`](index.md#motd), [`playerCount -> hover`](index.md#hover) and [`playerCount -> text`](index.md#text)).
|
||||
It supports all available [formatting tags][tags] with a [few exceptions and limitations](#exceptions-and-limitations).
|
||||
|
||||
/// note
|
||||
Please note that the available tags in MiniMessage depend heavily on the version used on the Server or Proxy.
|
||||
While AdvancedServerList downloads MiniMessage for BungeeCord, is it using the built-in version on Paper and Velocity. Due to this could the version used be older than the one the plugin is compiled against.
|
||||
///
|
||||
|
||||
## Before starting
|
||||
[minimessage]: https://docs.advntr.dev/minimessage/index.html
|
||||
[tags]: https://docs.advntr.dev/minimessage/format.html
|
||||
|
||||
Please make sure to surround your text with either single quotes (`'`) or double quotes (`"`).
|
||||
This avoids possible issues where the YAML parser would treat lines starting with specific characters as special options (i.e. `<` would be treated as a scolar value).
|
||||
### Why MiniMessage and not `&`/`§`? { #why-minimessage }
|
||||
|
||||
Also, should your text contain single quotes (for example when using `you're`), either surround the text with double quotes, or change the single quote in your text into two single quotes (`you're -> you''re`).
|
||||
The reason to only support MiniMessage and not the so-called legacy color and formatting codes can be boiled down to 3 specific reasons:
|
||||
|
||||
```yaml title="Wrong formatting"
|
||||
motd:
|
||||
- <red>This will cause
|
||||
- <red>Errors
|
||||
```
|
||||
1. **MiniMessage tags are more clear on what they do.**
|
||||
`<aqua>` makes it clear that it colors the text while `&b` is not as clear on that matter.
|
||||
2. **MiniMessage allows easier combination of color and formatting codes.**
|
||||
MiniMessage "remembers" what colors and formatting you applied, meaning if you close a tag and a previous one was set before it, that will continue.
|
||||
|
||||
/// details | Example
|
||||
type: example
|
||||
|
||||
Below is a comparison of the same text made in MiniMessage and Color codes.
|
||||
|
||||
//// tab | MiniMessage
|
||||
```
|
||||
<aqua>Hello <green><bold>World! <red>How</bold> are</red> you?
|
||||
```
|
||||
////
|
||||
|
||||
//// tab | Color Codes
|
||||
```
|
||||
# We add &c after "How" to avoid a "bold space"
|
||||
|
||||
&bHello &a&lWorld! &c&lHow&c are &ayou?
|
||||
```
|
||||
////
|
||||
///
|
||||
|
||||
3. **MiniMessage adds easier support for advanced features (RGB colors, gradients, etc.)**
|
||||
With MiniMessage is it significantly more easy to use more advanced features such as gradients, rgb colors and similar.
|
||||
While legacy colors have a form of RGB support is the format weird to look at and gradients become a mess. This is even worse when including bold for specific characters.
|
||||
|
||||
/// details | Example
|
||||
type: example
|
||||
|
||||
Below is an example of a Gradient starting at `#084CFB` (:octicons-dot-fill-24:{ style="color: #084CFB;" }) and ending at `#ADF3FD` (:octicons-dot-fill-24:{ style="color: #ADF3FD;" }).
|
||||
|
||||
//// tab | MiniMessage
|
||||
```
|
||||
<gradient:#084CFB:#ADF3FD>Hello World!</gradient>
|
||||
```
|
||||
////
|
||||
|
||||
//// tab | Color Codes
|
||||
```
|
||||
TCFBHÃDFBeĨDFBlA7EFClA8FFCo BB0FCWCC1FCoCD2FDr	DE2FDl&#ADF3FDd
|
||||
```
|
||||
////
|
||||
///
|
||||
|
||||
```yaml title="Right formatting"
|
||||
motd:
|
||||
- "<green>This will be formatted"
|
||||
- "<green>properly."
|
||||
```
|
||||
In general is MiniMessage a lot easier to manage and read as legacy color codes, as their appearance is more distinct from normal text.
|
||||
|
||||
## Unsupported options
|
||||
### Exceptions and Limitations
|
||||
|
||||
The following options are **not** supported, no matter what option they are used in:
|
||||
Due to the situation MiniMessage is used in are not all of its features available to use.
|
||||
Below is a list of all features that cannot be used by AdvancedServerList, or can only be used by it under specific conditions.
|
||||
|
||||
- Hover Actions (Show text, Show Advancement, etc)
|
||||
- Click Actions (Run command, Suggest command, etc.)
|
||||
- Custom Fonts (May work if player already has the resource pack loaded)
|
||||
| Feature | Tag | Supported? |
|
||||
|---------------|------------------------------|------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| RGB Colors | `<#rrggbb>` | Only works properly on `motd` and will be downsampled to the closest named color for the other options. |
|
||||
| RGB Gradients | `<gradient:#rrggbb:#rrggbb>` | Only works properly on `motd` and will be downsampled to the closest named color for the other options. |
|
||||
| Click Actions | `<click:_type_:_value_>` | Tag will be rendered, but clicking the text won't do anything. |
|
||||
| Hover Actions | `<hover:_type_:_value_>` | Tag will be rendered, but hovering the text won't show anything. |
|
||||
| Insertions | `<insertion:_value_>` | Tag will be rendered, but clicking the text won't do anything. |
|
||||
| Font | `<font:_font_>` | May work with the default fonts (`default`, `alt` and `uniform`) and with custom ones should the client already have it. |
|
||||
| Selector | `<selector:_sel_>` | Tag will be rendered, but clicking the text won't do anything. |
|
||||
| Score | `<score:_name_:_objective_>` | Will not render due to requiring the player to be on the server. |
|
||||
| Pride | `<pride[:_flag_]>` | Only works if the server (Paper) or Proxy (Velocity) use `v4.18.0` of MiniMessage. BungeeCord is unaffected due to downloading it. |
|
||||
|
||||
## Colors
|
||||
## Yaml Formatting
|
||||
|
||||
### Default colors
|
||||
YAML has some special quirks that require attention when editing the file.
|
||||
Such quirks include features such as...
|
||||
|
||||
Default colors such as `<red>`, `<green>`, `<aqua>`, etc. may be used in all text options.
|
||||
- Reading `~` and `null` as literal null values.
|
||||
- Reading `>` and `|` as multi-line indicators.
|
||||
|
||||
### Hex Colors
|
||||
To avoid this should you always surround text values with single (`'`) or double quotes (`"`) to force the YAML parser to see those as Strings.
|
||||
|
||||
Hexadecimal colors may be used in the [`motd`](index.md#motd) option using the `<#RRGGBB>` format.
|
||||
/// note | Note on Single quotes inside text
|
||||
Using single quotes, the YAML parser may get confused when the text itself also contains a single quote.
|
||||
As an example `'You're banned'` would mess up the value because the `'` in `You're` is understood as the end of the String.
|
||||
|
||||
### Gradients
|
||||
To avoid this issue, do one of the following:
|
||||
|
||||
Gradients can be created by using `<gradient:<color1>:<color2>>`, replacing `<color1>` with a starting color name or hex color value and `<color2>` with an ending color name or hex color value.
|
||||
Only the [`motd`](index.md#motd) option may support hex color gradients and in all other options will it be downsampled.
|
||||
|
||||
## Formatting
|
||||
|
||||
Formatting options (`<bold>`, `<italic>`, etc.) are available for all text options.
|
||||
|
||||
[MiniMessage]: https://docs.adventure.kyori.net/minimessage/index.html
|
||||
- Surround the text with Double quotes instead of Single quotes (`"You're banned"`)
|
||||
- Use two single quotes (`''`) inside the text every time you want to display a single quote character (`'You''re banned'`)
|
||||
///
|
||||
@@ -91,6 +91,8 @@ motd:
|
||||
|
||||
## Motd
|
||||
|
||||
<!-- admo:tip Start a line with <code><center></code> to have it centered. -->
|
||||
|
||||
The `motd` option allows you to alter the "Message of the day" that is being displayed on a server in the multiplayer screen.
|
||||
Please see the [Formatting](formatting.md) for all supported formatting options.
|
||||
|
||||
@@ -196,6 +198,13 @@ playerCount:
|
||||
</div>
|
||||
///
|
||||
|
||||
### HidePlayersHover
|
||||
|
||||
The `playerCount -> hidePlayersHover` option takes a boolean (`true` or `false`) to set whether the hover displayed on the player count should be hidden or not.
|
||||
|
||||
When set to true will hovering over the player count show nothing.
|
||||
In addition will [`playerCount -> hover`](#hover) not be used.
|
||||
|
||||
### Hover
|
||||
|
||||
The `playerCount -> hover` option allows you to define lines of text to show when a player hovers over the player count with their cursor.
|
||||
|
||||
@@ -15,43 +15,39 @@ The following placeholders are available in AdvancedServerList itself. Please no
|
||||
|
||||
These placeholders use the player who pinged the server, to return values. They may require the player to be cached in order to work.
|
||||
|
||||
| Placeholder | Description | Platforms | Cached Player required?[^1] |
|
||||
|-----------------------------|-----------------------------------------------------------------|--------------|-----------------------------|
|
||||
| `${player name}` | The name of the player. | All | Yes[^2] |
|
||||
| `${player protocol}` | The protocol version of the player. | All | No |
|
||||
| `${player uuid}` | The UUID of the player. | All | Yes[^3] |
|
||||
| `${player version}` | The protocol version of the player as readable MC version. | Velocity | No |
|
||||
| `${player hasPlayedBefore}` | Boolean for whether the player has played on the server before. | Paper | Yes |
|
||||
| `${player isBanned}` | Boolean for whether the player has been banned from the server. | Paper | Yes |
|
||||
| `${player isWhitelisted}` | Boolean for whether the player is whitelisted on the server. | Paper | Yes |
|
||||
| Placeholder | Description | Platforms | Cached Player required?^[1](#player-n1)^ |
|
||||
|-----------------------------|-----------------------------------------------------------------|--------------|------------------------------------------|
|
||||
| `${player name}` | The name of the player. | All | Yes^[2](#player-n2)^ |
|
||||
| `${player protocol}` | The protocol version of the player. | All | No |
|
||||
| `${player uuid}` | The UUID of the player. | All | Yes^[3](#player-n3)^ |
|
||||
| `${player version}` | The protocol version of the player as readable MC version. | Velocity | No |
|
||||
| `${player hasPlayedBefore}` | Boolean for whether the player has played on the server before. | Paper | Yes |
|
||||
| `${player isBanned}` | Boolean for whether the player has been banned from the server. | Paper | Yes |
|
||||
| `${player isWhitelisted}` | Boolean for whether the player is whitelisted on the server. | Paper | Yes |
|
||||
|
||||
[^1]:
|
||||
Whether this placeholder requires the player to have joined before while AdvancedServerList was running.
|
||||
When `disableCache` is enabled will these placeholders not work.
|
||||
[^2]: Will default to whatever name has been provided in the `unknownPlayer -> name` option of the config.yml, when the player isn't cached yet.
|
||||
[^3]: Will default to whatever name has been provided in the `unknownPlayer -> uuid` option of the config.yml, when the player isn't cached yet.
|
||||
<small>^1^{ #player-n1 } When `Yes` is set, requires the player to have joined before while AdvancedServerList was running. Placeholder will not work when `disableCache` is true.</small>
|
||||
|
||||
<small>^2^{ #player-n2 } Will default to the value provided in `unknownPlayer -> name` of the config.yml, should the player not be cached yet.</small>
|
||||
|
||||
<small>^3^{ #player-n3 } Will default to the value provided in `unknownPlayer -> uuid` of the config.yml, should the player not be cached yet.</small>
|
||||
|
||||
### Server
|
||||
|
||||
These placeholders use values given by the server/proxy AdvancedServerList runs on.
|
||||
|
||||
| Placeholder | Description | Platforms |
|
||||
|------------------------------|------------------------------------------------------------|-----------|
|
||||
| `${server playersOnline}` | The number of players online on this proxy/server.[^4] | All |
|
||||
| `${server playersMax}` | The total number of players that can join this server.[^5] | All |
|
||||
| `${server host}` | The domain/IP the player pinged.[^6] | All |
|
||||
| `${server whitelistEnabled}` | Whether the whitelist is enabled or not. | Paper |
|
||||
| Placeholder | Description | Platforms |
|
||||
|------------------------------|-------------------------------------------------------------------------|-----------|
|
||||
| `${server playersOnline}` | The number of players online on this proxy/server.^[1](#server-n1)^ | All |
|
||||
| `${server playersMax}` | The total number of players that can join this server.^[2](#server-n2)^ | All |
|
||||
| `${server host}` | The domain/IP the player pinged.^[3](#server-n3)^ | All |
|
||||
| `${server whitelistEnabled}` | Whether the whitelist is enabled or not. | Paper |
|
||||
|
||||
[^4]:
|
||||
A comma-separated list of world (On Paper) or Server (On BungeeCord/Velocity) names can be provided to display the added numbers of players in these worlds/servers.
|
||||
**Example:** `${server playersOnline lobby1,lobby2}` will display the collective numbers of players in the servers `lobby1` and `lobby2`.
|
||||
Using the [onlinePlayers' `amount`](index.md#onlineplayers-amount) option will modify the output of this placeholder, except when used in [`condition`](index.md#condition).
|
||||
[^5]:
|
||||
Using either the [extraPlayers' `amount`](index.md#extraplayers-amount) or [maxPlayers' `amount`](index.md#maxplayers-amount) option will make this placeholder return the modified max players count.
|
||||
Only exception to this rule is when the placeholder is used in the [`condition`](index.md#condition) option in which case it returns the actual max player count of the proxy/server.
|
||||
[^6]:
|
||||
An optional server name can be provided to display the IP/Domain associated with that server. This only works on BungeeCord or Velocity.
|
||||
**Example:** `${server host survival}` would display the IP/Domain associated with the `survival` server.
|
||||
<small>^1^{ #server-n1 } You can provide a comma-separated list of world or server names (based on platform) to return the collective count of players on these worlds/servers.
|
||||
Additionally, when using [`playerCount -> onlinePlayers`](index.md#onlineplayers) will this placeholder return the modified online players count, except when used within the [`condition`](index.md#condition) option.</small>
|
||||
|
||||
<small>^2^{ #server-n2 } When using either [`playerCount -> extraPlayers`](index.md#extraplayers) or [`playerCount -> maxPlayers`](index.md#maxplayers) will this placeholder return the modified max players count, except when used within the [`condition`](index.md#condition) option.</small>
|
||||
|
||||
<small>^3^{ #server-n3 } An optional server name may be provided to display the IP/Domain associated with said server. This only works for the BungeeCord and Velocity version of the plugin.</small>
|
||||
|
||||
### Proxy
|
||||
|
||||
@@ -61,14 +57,14 @@ These placeholders are only available on the BungeeCord and Velocity versions of
|
||||
Servers are pinged every 10 seconds by the plugin and cached for that time period.
|
||||
///
|
||||
|
||||
| Placeholders | Description | Default[^7] |
|
||||
|------------------------------|-------------------------------------------------------------------|-------------|
|
||||
| `${proxy status <name>}` | Returns `online` or `offline` based on the Server's availability. | `offline` |
|
||||
| `${proxy motd <name>}` | Returns the Server's MOTD as MiniMessage String. | `none` |
|
||||
| `${proxy players <name>}` | Returns the number of online Players on the Server. | `0` |
|
||||
| `${proxy maxPlayers <name>}` | Returns the max number of Players allowed to join the Server. | `0` |
|
||||
| Placeholders | Description | Default^[1](#proxy-n1)^ |
|
||||
|------------------------------|-------------------------------------------------------------------|-------------------------|
|
||||
| `${proxy status <name>}` | Returns `online` or `offline` based on the Server's availability. | `offline` |
|
||||
| `${proxy motd <name>}` | Returns the Server's MOTD as MiniMessage String. | `none` |
|
||||
| `${proxy players <name>}` | Returns the number of online Players on the Server. | `0` |
|
||||
| `${proxy maxPlayers <name>}` | Returns the max number of Players allowed to join the Server. | `0` |
|
||||
|
||||
[^7]: The default value returned, should the Server or parts of it not be available.
|
||||
<small>^1^{ #proxy-n1 } The default value returned, should the Server or parts of it not be available.</small>
|
||||
|
||||
### Maintenance
|
||||
|
||||
@@ -78,13 +74,42 @@ These placeholders require the [Maintenance](https://hangar.papermc.io/kennytv/M
|
||||
|-------------------------------------|----------------------------------------------------------------|
|
||||
| `${maintenance maintenanceEnabled}` | Returns whether the Global maintenance mode is enabled or not. |
|
||||
|
||||
## Adding your own Placeholders
|
||||
|
||||
Thanks to the API of AdvancedServerList are you able to add your own Placeholders that can be used using the `${identifier value}` pattern.
|
||||
To learn how to do this, head over to the [API page](../api/index.md) to learn more.
|
||||
|
||||
|
||||
## PlaceholderAPI
|
||||
|
||||
Please visit the [PlaceholderAPI page](../placeholderapi/index.md) for how AdvancedServerList works with PlaceholderAPI.
|
||||
### Own Placeholders
|
||||
|
||||
## Custom Placeholders
|
||||
The Paper version of AdvancedServerList provides the following placeholders to use through PlaceholderAPI.
|
||||
|
||||
Since version 2 of AdvancedServerList are developers able to provide their own placeholders to be used in a server list profile.
|
||||
The format follows the same one as the [built-in placeholders](#built-in-placeholders), meaning it is `${<identifier> <placeholder>}`.
|
||||
/// info
|
||||
AdvancedServerList uses the same logic to determine the value to return for a player as it does when selecting a Server List Profile.
|
||||
///
|
||||
|
||||
If you're a developer and want to integrate your own placeholders into AdvancedServerList, read about it [here](../api/index.md).
|
||||
| Placeholder | Description | Default^[1](#papi-n1)^ |
|
||||
|--------------------------------------|-----------------------------------------------------------------------------------|------------------------|
|
||||
| `%asl_favicon%` | Value of `favicon` when present. | `(empty string)` |
|
||||
| `%asl_motd%` | Value of `motd` when present. | `(empty string)` |
|
||||
| `%asl_playercount_extraplayers%` | Value of `playerCount -> extraPlayers -> amount`. | `(empty string)` |
|
||||
| `%asl_playercount_hideplayers%` | Returns wether `playerCount -> hidePlayers` is enabled or not. | `false` |
|
||||
| `%asl_playercount_hideplayershover%` | Returns wether `playerCount -> hidePlayersHover` is enabled or not. | `false` |
|
||||
| `%asl_playercount_hover%` | Value of `playerCount -> hover`. | `(empty string)` |
|
||||
| `%asl_playercount_maxplayers%` | Value of `playerCount -> maxPlayers -> amount` | `(empty string)` |
|
||||
| `%asl_playercount_onlineplayers%` | Value of `playerCount -> onlinePlayers -> amount` | `(empty string)` |
|
||||
| `%asl_playercount_text%` | Value of `playerCount -> text` | `(empty string)` |
|
||||
| `%asl_playersmax%` | Returns the max players based on currently set maxPlayers or extraPlayers values. | `(empty string)` |
|
||||
|
||||
<small>^1^{ #papi-n1 } The default value is returned when the option in the chosen profile is either not set, or `null`.</small>
|
||||
|
||||
### Other Placeholders
|
||||
|
||||
Placeholders from PlaceholderAPI Expansions can be used in AdvancedServerList using the default `%identifier_value%` format.
|
||||
|
||||
PlaceholderAPI placeholders can be used both on the Server (Paper) and on the proxy (BungeeCord, Velocity) version of AdvancedServerList.
|
||||
However, the Proxy version requires the usage of [PAPIProxyBridge] to function properly.
|
||||
|
||||
Placeholders can also not be used within the [`condition`](index.md#condition) option of a Server List Profile.
|
||||
+26
-9
@@ -68,6 +68,21 @@ extra:
|
||||
plugins:
|
||||
- search
|
||||
- glightbox
|
||||
- redirects:
|
||||
redirect_maps:
|
||||
# Plugin Migration Pages
|
||||
'migration/minimotd.md': 'migration/plugins/minimotd.md'
|
||||
'migration/serverlistplus.md': 'migration/plugins/serverlistplus.md'
|
||||
#
|
||||
# Version Migration Pages
|
||||
'migration/v3_8_0-to-v3_9_0.md': 'migration/versions/v3.8.0-to-v3.9.0.md'
|
||||
'migration/v3_7_0-to-v3_8_0.md': 'migration/versions/v3.7.0-to-v3.8.0.md'
|
||||
'migration/pre-v3_7_0-to-v3_7_0.md': 'migration/versions/pre-v3.7.0-to-v3.7.0.md'
|
||||
'migration/v1_9_0-to-v1_10_0.md': 'migration/versions/v1.9.0-to-v1.10.0.md'
|
||||
'migration/v1_5_3-to-v1_6_0.md': 'migration/versions/v1.5.3-to-v1.6.0.md'
|
||||
#
|
||||
# PlaceholderAPI page
|
||||
'placeholderapi/index.md': 'profiles/placeholders.md#own-placeholders'
|
||||
- pagenav-generator
|
||||
|
||||
markdown_extensions:
|
||||
@@ -103,6 +118,7 @@ markdown_extensions:
|
||||
- pymdownx.blocks.tab:
|
||||
alternate_style: true
|
||||
- pymdownx.blocks.details:
|
||||
- pymdownx.caret:
|
||||
|
||||
hooks:
|
||||
- docs/.hooks/badges.py
|
||||
@@ -123,16 +139,18 @@ nav:
|
||||
- Migration:
|
||||
- migration/index.md
|
||||
- Other Plugins:
|
||||
- MiniMOTD: migration/minimotd.md
|
||||
- ServerListPlus: migration/serverlistplus.md
|
||||
- MiniMOTD: migration/plugins/minimotd.md
|
||||
- ServerListPlus: migration/plugins/serverlistplus.md
|
||||
- AdvancedServerList:
|
||||
- v3.8.0 to v3.9.0: migration/v3_8_0-to-v3_9_0.md
|
||||
- v3.7.0 to v3.8.0: migration/v3_7_0-to-v3_8_0.md
|
||||
- '< v3.7.0 to v3.7.0': migration/pre-v3_7_0-to-v3_7_0.md
|
||||
- v1.9.0 to v1.10.0: migration/v1_9_0-to-v1_10_0.md
|
||||
- v1.5.3 to v1.6.0: migration/v1_5_3-to-v1_6_0.md
|
||||
- v3.8.0 to v3.9.0: migration/versions/v3.8.0-to-v3.9.0.md
|
||||
- v3.7.0 to v3.8.0: migration/versions/v3.7.0-to-v3.8.0.md
|
||||
- '< v3.7.0 to v3.7.0': migration/versions/pre-v3.7.0-to-v3.7.0.md
|
||||
- v1.9.0 to v1.10.0: migration/versions/v1.9.0-to-v1.10.0.md
|
||||
- v1.5.3 to v1.6.0: migration/versions/v1.5.3-to-v1.6.0.md
|
||||
- API:
|
||||
- api/index.md
|
||||
- Adding Placeholders: api/adding-placeholders.md
|
||||
- Listening for Events: api/listening-for-events.md
|
||||
- Changelog: api/changelog.md
|
||||
- Javadocs: api/javadocs.md
|
||||
- Source Code: api/source.md
|
||||
@@ -184,5 +202,4 @@ nav:
|
||||
- api/reference/platform-velocity/ch.andre601.advancedserverlist.api.velocity/objects/velocityproxy.md
|
||||
- Addons:
|
||||
- addons/index.md
|
||||
- BanPlugins: addons/banplugins.md
|
||||
- PlaceholderAPI: placeholderapi/index.md
|
||||
- BanPlugins: addons/banplugins.md
|
||||
+11
-1
@@ -99,7 +99,7 @@
|
||||
<dependency>
|
||||
<groupId>com.viaversion</groupId>
|
||||
<artifactId>viaversion-api</artifactId>
|
||||
<version>5.2.0</version>
|
||||
<version>5.2.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -118,6 +118,16 @@
|
||||
<resource>
|
||||
<filtering>true</filtering>
|
||||
<directory>${project.basedir}/src/main/resources</directory>
|
||||
<excludes>
|
||||
<exclude>char-widths.json.gz</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
<resource>
|
||||
<filtering>false</filtering>
|
||||
<directory>${project.basedir}/src/main/resources</directory>
|
||||
<includes>
|
||||
<include>char-widths.json.gz</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
|
||||
@@ -67,18 +67,6 @@ public class PaperCore extends JavaPlugin implements PluginCore<CachedServerIcon
|
||||
|
||||
@Override
|
||||
public void onEnable(){
|
||||
try{
|
||||
Class.forName("io.papermc.paper.configuration.ConfigurationLoaders");
|
||||
}catch(ClassNotFoundException ignored){
|
||||
try{
|
||||
// Above class only exists since 1.19+. This is a fallback to see if an older version is used.
|
||||
Class.forName("com.destroystokyo.paper.PaperConfig");
|
||||
}catch(ClassNotFoundException ex){
|
||||
printNoPaperWarning();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.core = AdvancedServerList.init(this, new PaperPlayerPlaceholders(), new PaperServerPlaceholders(this));
|
||||
|
||||
if(getServer().getPluginManager().isPluginEnabled("PlaceholderAPI"))
|
||||
@@ -101,9 +89,9 @@ public class PaperCore extends JavaPlugin implements PluginCore<CachedServerIcon
|
||||
@Override
|
||||
public void loadCommands(){
|
||||
if(getServer().getCommandMap().register("asl", new CmdAdvancedServerList(this))){
|
||||
getPluginLogger().info("Registered /advancedserverlist:advancedserverlist");
|
||||
getPluginLogger().success("Registered <white>/advancedserverlist:advancedserverlist</white>!");
|
||||
}else{
|
||||
getPluginLogger().info("Registered /asl:advancedserverlist");
|
||||
getPluginLogger().success("Registered <white>/asl:advancedserverlist</white>!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,27 +213,6 @@ public class PaperCore extends JavaPlugin implements PluginCore<CachedServerIcon
|
||||
return players.size();
|
||||
}
|
||||
|
||||
private void printNoPaperWarning(){
|
||||
getPluginLogger().warn("======================================================================================");
|
||||
getPluginLogger().warn("THIS SERVER DOES NOT USE PAPER OR A COMPATIBLE FORK!");
|
||||
getPluginLogger().warn("");
|
||||
getPluginLogger().warn("AdvancedServerList 3.6.0 has dropped support for Spigot-based servers.");
|
||||
getPluginLogger().warn("This means that only servers using Paper or a Paper-compatible fork may work.");
|
||||
getPluginLogger().warn("");
|
||||
getPluginLogger().warn("Reasons for this removal:");
|
||||
getPluginLogger().warn(" - Reliance on a different plugin for core functionalities.");
|
||||
getPluginLogger().warn(" - Bad dependency loading limited to MavenCentral only.");
|
||||
getPluginLogger().warn("");
|
||||
getPluginLogger().warn("Please consider switching to Paper or one of the Paper-compatible forks to benefit");
|
||||
getPluginLogger().warn("from overall performance improvements amongst other things.");
|
||||
getPluginLogger().warn("");
|
||||
getPluginLogger().warn("Disabling AdvancedServerList. If you believe this is an error, report it on");
|
||||
getPluginLogger().warn("Codeberg: https://codeberg.org/Andre601/AdvancedServerList");
|
||||
getPluginLogger().warn("======================================================================================");
|
||||
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
}
|
||||
|
||||
private String getNewVersion(){
|
||||
ServerBuildInfo info = ServerBuildInfo.buildInfo();
|
||||
|
||||
@@ -255,7 +222,7 @@ public class PaperCore extends JavaPlugin implements PluginCore<CachedServerIcon
|
||||
|
||||
OptionalInt build = info.buildNumber();
|
||||
if(build.isEmpty())
|
||||
return String.format("%s (%s, Version: %s)", name, id, version);
|
||||
return String.format("%s (ID: %s, Version: %s)", name, id, version);
|
||||
|
||||
return String.format("%s (ID: %s, Version: %s, Build: %d)", name, id, version, build.getAsInt());
|
||||
}
|
||||
|
||||
@@ -48,5 +48,20 @@ public class LoadEvent implements Listener{
|
||||
|
||||
// Load currently loaded worlds into the WorldCache
|
||||
Bukkit.getWorlds().forEach(world -> plugin.getWorldCache().addWorld(world));
|
||||
|
||||
// Print a warning if server load was after a reload.
|
||||
if(event.getType() != ServerLoadEvent.LoadType.RELOAD)
|
||||
return;
|
||||
|
||||
plugin.getPluginLogger().warn("======================================================================================");
|
||||
plugin.getPluginLogger().warn("SERVER RELOAD DETECTED!");
|
||||
plugin.getPluginLogger().warn("");
|
||||
plugin.getPluginLogger().warn("A server reload has been detected by AdvancedServerList, meaning either the /reload");
|
||||
plugin.getPluginLogger().warn("command or Bukkit.reload() was executed.");
|
||||
plugin.getPluginLogger().warn("");
|
||||
plugin.getPluginLogger().warn("YOU WILL GET NO SUPPORT FOR THE PLUGIN FOR ANY ISSUES YOU ENCOUNTER AFTER A SERVER");
|
||||
plugin.getPluginLogger().warn("RELOAD!");
|
||||
plugin.getPluginLogger().warn("ALWAYS RESTART YOUR SERVER. NEVER RELOAD IT!");
|
||||
plugin.getPluginLogger().warn("======================================================================================");
|
||||
}
|
||||
}
|
||||
+23
-4
@@ -98,14 +98,24 @@ public class PaperEventWrapper implements GenericEventWrapper<CachedServerIcon,
|
||||
@Override
|
||||
public void setPlayers(List<String> lines, PaperPlayerImpl player, GenericServer server){
|
||||
try{
|
||||
event.getListedPlayers();
|
||||
Class.forName("com.destroystokyo.paper.event.server.PaperServerListPingEvent$ListedPlayerInfo");
|
||||
setListedPlayers(lines, player, server);
|
||||
}catch(NoSuchMethodError ex){
|
||||
}catch(ClassNotFoundException ex){
|
||||
// Version before latest 1.20.6 builds(?)
|
||||
setPlayerSample(lines, player, server);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayersHidden(){
|
||||
try{
|
||||
Class.forName("com.destroystokyo.paper.event.server.PaperServerListPingEvent$ListedPlayerInfo");
|
||||
clearListedPlayers();
|
||||
}catch(ClassNotFoundException ex){
|
||||
clearPlayerSample();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFavicon(CachedServerIcon favicon){
|
||||
event.setServerIcon(favicon);
|
||||
@@ -184,8 +194,12 @@ public class PaperEventWrapper implements GenericEventWrapper<CachedServerIcon,
|
||||
return new PaperServerImpl(plugin.getWorldCache().worlds(), playersOnline, playersMax, host);
|
||||
}
|
||||
|
||||
private void setListedPlayers(List<String> lines, PaperPlayerImpl player, GenericServer server){
|
||||
private void clearListedPlayers(){
|
||||
event.getListedPlayers().clear();
|
||||
}
|
||||
|
||||
private void setListedPlayers(List<String> lines, PaperPlayerImpl player, GenericServer server){
|
||||
clearListedPlayers();
|
||||
List<PaperServerListPingEvent.ListedPlayerInfo> playerList = getPlayerList(lines, player, server,
|
||||
text -> new PaperServerListPingEvent.ListedPlayerInfo(text, UUID.randomUUID()));
|
||||
|
||||
@@ -193,8 +207,13 @@ public class PaperEventWrapper implements GenericEventWrapper<CachedServerIcon,
|
||||
}
|
||||
|
||||
@SuppressWarnings({"Deprecation", "removal"})
|
||||
private void setPlayerSample(List<String> lines, PaperPlayerImpl player, GenericServer server){
|
||||
private void clearPlayerSample(){
|
||||
event.getPlayerSample().clear();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"Deprecation", "removal"})
|
||||
private void setPlayerSample(List<String> lines, PaperPlayerImpl player, GenericServer server){
|
||||
clearPlayerSample();
|
||||
List<PlayerProfile> playerProfiles = getPlayerList(lines, player, server, FakePlayerProfile::create);
|
||||
|
||||
event.getPlayerSample().addAll(playerProfiles);
|
||||
|
||||
@@ -26,19 +26,18 @@
|
||||
package ch.andre601.advancedserverlist.paper.logging;
|
||||
|
||||
import ch.andre601.advancedserverlist.core.interfaces.PluginLogger;
|
||||
import ch.andre601.advancedserverlist.core.parsing.ComponentParser;
|
||||
import ch.andre601.advancedserverlist.paper.PaperCore;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
|
||||
|
||||
public class PaperLogger implements PluginLogger{
|
||||
|
||||
private final PaperCore plugin;
|
||||
private final Logger logger;
|
||||
private final ComponentLogger logger;
|
||||
|
||||
public PaperLogger(PaperCore plugin){
|
||||
this.plugin = plugin;
|
||||
this.logger = plugin.getLogger();
|
||||
this.logger = plugin.getComponentLogger();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,21 +54,21 @@ public class PaperLogger implements PluginLogger{
|
||||
|
||||
@Override
|
||||
public void info(String msg, Object... args){
|
||||
logger.info(String.format(msg, args));
|
||||
logger.info(ComponentParser.textFormatted("<grey>" + msg, args).toComponent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String msg, Object... args){
|
||||
logger.warning(String.format(msg, args));
|
||||
logger.warn(ComponentParser.textFormatted("<yellow>" + msg, args).toComponent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String msg, Throwable throwable){
|
||||
logger.log(Level.WARNING, msg, throwable);
|
||||
logger.warn(ComponentParser.text("<yellow>" + msg).toComponent(), throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String msg, Throwable throwable, Object... args){
|
||||
logger.log(Level.WARNING, String.format(msg, args), throwable);
|
||||
logger.warn(ComponentParser.textFormatted("<yellow>" + msg, args).toComponent(), throwable);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -36,7 +36,7 @@ public class PaperPlayerImpl implements BukkitPlayer{
|
||||
private final OfflinePlayer player;
|
||||
|
||||
private final String name;
|
||||
private int protocol;
|
||||
private final int protocol;
|
||||
private final UUID uuid;
|
||||
|
||||
private boolean playedBefore = false;
|
||||
|
||||
+29
-11
@@ -46,6 +46,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class PAPIPlaceholders extends PlaceholderExpansion{
|
||||
|
||||
@@ -114,13 +115,15 @@ public class PAPIPlaceholders extends PlaceholderExpansion{
|
||||
|
||||
BukkitServer finalServer = new PaperServerImpl(plugin.getWorldCache().worlds(), online, max, host);
|
||||
return switch(identifier.toLowerCase(Locale.ROOT)){
|
||||
case "motd" -> getOption(entry.motd(), pl, player, finalServer);
|
||||
case "favicon" -> getOption(entry.favicon(), pl, player, finalServer);
|
||||
case "playercount_hover" -> getOption(entry.players(), pl, player, finalServer);
|
||||
case "motd" -> getOption(entry.motd(), pl, player, finalServer, true);
|
||||
case "playercount_extraplayers" -> extraPlayers == null ? "" : String.valueOf(extraPlayers);
|
||||
case "playercount_hideplayers" -> String.valueOf(entry.hidePlayersEnabled().getOrDefault(false));
|
||||
case "playercount_hideplayershover" -> String.valueOf(entry.hidePlayersHoverEnabled().getOrDefault(false));
|
||||
case "playercount_hover" -> getOption(entry.players(), pl, player, finalServer, false);
|
||||
case "playercount_maxplayers" -> maxPlayers == null ? "" : String.valueOf(maxPlayers);
|
||||
case "playercount_onlineplayers" -> onlinePlayers == null ? "" : String.valueOf(onlinePlayers);
|
||||
case "playercount_text" -> getOption(entry.playerCountText(), pl, player, finalServer);
|
||||
case "playercount_extraplayers" -> extraPlayers == null ? "null" : String.valueOf(extraPlayers);
|
||||
case "playercount_maxplayers" -> maxPlayers == null ? "null" : String.valueOf(maxPlayers);
|
||||
case "playercount_onlineplayers" -> onlinePlayers == null ? "null" : String.valueOf(onlinePlayers);
|
||||
case "server_playersmax" -> String.valueOf(max);
|
||||
default -> null;
|
||||
};
|
||||
@@ -136,14 +139,29 @@ public class PAPIPlaceholders extends PlaceholderExpansion{
|
||||
}
|
||||
|
||||
private String getOption(String str, Player pl, BukkitPlayer player, BukkitServer server){
|
||||
return getOption(Collections.singletonList(str), pl, player, server);
|
||||
if(str == null)
|
||||
return "";
|
||||
|
||||
return getOption(Collections.singletonList(str), pl, player, server, false);
|
||||
}
|
||||
|
||||
private String getOption(List<String> list, Player pl, BukkitPlayer player, BukkitServer server){
|
||||
return ComponentParser.list(list)
|
||||
.modifyText(text -> StringReplacer.replace(text, player, server))
|
||||
.modifyText(text -> PlaceholderAPI.setPlaceholders(pl, text))
|
||||
.toString();
|
||||
private String getOption(List<String> list, Player pl, BukkitPlayer player, BukkitServer server, boolean isMotd){
|
||||
if(list.isEmpty())
|
||||
return "";
|
||||
|
||||
return list.stream()
|
||||
.map(line -> ComponentParser.text(line)
|
||||
.modifyText(text -> StringReplacer.replace(text, player, server))
|
||||
.modifyText(text -> PlaceholderAPI.setPlaceholders(pl, text))
|
||||
.modifyText(text -> {
|
||||
if(isMotd)
|
||||
return plugin.getCore().getTextCenterUtil().getCenteredText(text);
|
||||
|
||||
return text;
|
||||
})
|
||||
.toString()
|
||||
)
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
private Integer parseNumberOption(String option, Player pl, BukkitPlayer player, BukkitServer server){
|
||||
|
||||
@@ -5,7 +5,7 @@ description: '${plugin.description}'
|
||||
|
||||
main: 'ch.andre601.advancedserverlist.paper.PaperCore'
|
||||
|
||||
api-version: '1.19'
|
||||
api-version: '1.20'
|
||||
|
||||
folia-supported: true
|
||||
|
||||
@@ -24,15 +24,6 @@ dependencies:
|
||||
load: BEFORE
|
||||
required: false
|
||||
|
||||
# The below stuff is NOT used by Paper plugin.
|
||||
commands:
|
||||
advancedserverlist:
|
||||
description: 'Main command of AdvancedServerList'
|
||||
usage: '/<command> [help|reload]'
|
||||
aliases:
|
||||
- 'asl'
|
||||
permission: 'advancedserverlist.admin'
|
||||
|
||||
# This is actually supported(?)
|
||||
permissions:
|
||||
advancedserverlist.*:
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
#
|
||||
# This file is kept for Folia support.
|
||||
#
|
||||
|
||||
name: 'AdvancedServerList'
|
||||
author: 'Andre_601'
|
||||
version: '${plugin.version}'
|
||||
description: '${plugin.description}'
|
||||
|
||||
main: 'ch.andre601.advancedserverlist.paper.PaperCore'
|
||||
|
||||
api-version: '1.19'
|
||||
|
||||
folia-supported: true
|
||||
|
||||
libraries:
|
||||
- 'org.spongepowered:configurate-yaml:4.1.2'
|
||||
- 'net.kyori:adventure-text-serializer-legacy:4.12.0'
|
||||
- 'com.squareup.okhttp3:okhttp:4.10.0'
|
||||
|
||||
softdepend:
|
||||
- 'PlaceholderAPI'
|
||||
- 'Maintenance'
|
||||
|
||||
commands:
|
||||
advancedserverlist:
|
||||
description: 'Main command of AdvancedServerList'
|
||||
usage: '/<command> [help|reload|clearCache]'
|
||||
aliases:
|
||||
- 'asl'
|
||||
permission: 'advancedserverlist.admin'
|
||||
|
||||
permissions:
|
||||
advancedserverlist.*:
|
||||
description: 'Grants access to all permissions'
|
||||
default: op
|
||||
children:
|
||||
- 'advancedserverlist.admin'
|
||||
advancedserverlist.admin:
|
||||
description: 'Grants access to all permissions'
|
||||
default: op
|
||||
children:
|
||||
- 'advancedserverlist.command.*'
|
||||
advancedserverlist.command.*:
|
||||
description: 'Grants access to all commands'
|
||||
default: op
|
||||
children:
|
||||
- 'advancedserverlist.command.help'
|
||||
- 'advancedserverlist.command.reload'
|
||||
advancedserverlist.command.help:
|
||||
description: 'Grants access to /asl help'
|
||||
default: op
|
||||
advancedserverlist.command.reload:
|
||||
description: 'Grants access to /asl reload'
|
||||
default: op
|
||||
advancedserverlist.command.clearcache:
|
||||
description: 'Clears the Player and Favicon cache'
|
||||
default: op
|
||||
@@ -37,14 +37,14 @@
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<plugin.version>5.1.1</plugin.version>
|
||||
<plugin.version>5.4.1</plugin.version>
|
||||
<plugin.description>Create multiple Server lists based on conditions.</plugin.description>
|
||||
|
||||
<api.version>4.0.0</api.version>
|
||||
<api.version>4.1.0</api.version>
|
||||
|
||||
<dependency.adventure-api>4.17.0</dependency.adventure-api>
|
||||
<dependency.adventure-text-minimessage>4.17.0</dependency.adventure-text-minimessage>
|
||||
<dependency.adventure-text-serializer-legacy>4.17.0</dependency.adventure-text-serializer-legacy>
|
||||
<dependency.adventure-api>4.18.0</dependency.adventure-api>
|
||||
<dependency.adventure-text-minimessage>4.18.0</dependency.adventure-text-minimessage>
|
||||
<dependency.adventure-text-serializer-legacy>4.18.0</dependency.adventure-text-serializer-legacy>
|
||||
<dependency.adventure-platform-bungeecord>4.3.4</dependency.adventure-platform-bungeecord>
|
||||
<dependency.configurate-yaml>4.1.2</dependency.configurate-yaml>
|
||||
|
||||
@@ -74,5 +74,6 @@
|
||||
<module>velocity</module>
|
||||
<module>version-uploader</module>
|
||||
<module>addons/banplugins</module>
|
||||
<module>addons/placeholderapi</module>
|
||||
</modules>
|
||||
</project>
|
||||
+3
-1
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:base", "group:allNonMajor"],
|
||||
"dependencyDashboard": false,
|
||||
"labels": ["PR Type/Dependency Update"],
|
||||
"ignoreDeps": [
|
||||
"ch.andre601.advancedserverlist:parent",
|
||||
@@ -37,7 +39,7 @@
|
||||
"io.papermc.paper:paper-api"
|
||||
],
|
||||
"registryUrls": [
|
||||
"https://papermc.io/repo/repository/maven-public"
|
||||
"https://repo.papermc.io/repository/maven-public"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
mkdocs-material==9.5.48
|
||||
mkdocs-material==9.5.49
|
||||
mkdocs-glightbox==0.4.0
|
||||
mkdocs-redirects==1.2.2
|
||||
git+https://github.com/Andre601/mkdocs-pagenav-generator
|
||||
+17
-1
@@ -48,6 +48,10 @@
|
||||
<id>velocity-repo</id>
|
||||
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>repo-sayandevelopment</id>
|
||||
<url>https://repo.sayandev.org/snapshots</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
@@ -68,7 +72,7 @@
|
||||
<dependency>
|
||||
<groupId>com.velocitypowered</groupId>
|
||||
<artifactId>velocity-api</artifactId>
|
||||
<version>3.1.2-SNAPSHOT</version>
|
||||
<version>3.4.0-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -95,6 +99,18 @@
|
||||
<version>2.9.0-4</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.sayandev</groupId>
|
||||
<artifactId>sayanvanish-api</artifactId>
|
||||
<version>1.6.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.sayandev</groupId>
|
||||
<artifactId>sayanvanish-proxy-velocity</artifactId>
|
||||
<version>1.6.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Other -->
|
||||
<dependency>
|
||||
|
||||
@@ -30,14 +30,17 @@ import ch.andre601.advancedserverlist.core.interfaces.PluginLogger;
|
||||
import ch.andre601.advancedserverlist.core.interfaces.core.PluginCore;
|
||||
import ch.andre601.advancedserverlist.core.profiles.handlers.FaviconHandler;
|
||||
import ch.andre601.advancedserverlist.velocity.commands.CmdAdvancedServerList;
|
||||
import ch.andre601.advancedserverlist.velocity.listeners.JoinEvent;
|
||||
import ch.andre601.advancedserverlist.velocity.listeners.PingEvent;
|
||||
import ch.andre601.advancedserverlist.velocity.listeners.JoinListener;
|
||||
import ch.andre601.advancedserverlist.velocity.listeners.PingListener;
|
||||
import ch.andre601.advancedserverlist.velocity.listeners.PluginMessageListener;
|
||||
import ch.andre601.advancedserverlist.velocity.logging.VelocityLogger;
|
||||
import ch.andre601.advancedserverlist.velocity.objects.placeholders.VelocityPlayerPlaceholders;
|
||||
import ch.andre601.advancedserverlist.velocity.objects.placeholders.VelocityProxyPlaceholders;
|
||||
import ch.andre601.advancedserverlist.velocity.objects.placeholders.VelocityServerPlaceholders;
|
||||
import com.alessiodp.libby.Library;
|
||||
import com.alessiodp.libby.VelocityLibraryManager;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.inject.Inject;
|
||||
import com.velocitypowered.api.command.CommandMeta;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
@@ -46,15 +49,16 @@ import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
||||
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
import com.velocitypowered.api.scheduler.ScheduledTask;
|
||||
import com.velocitypowered.api.util.Favicon;
|
||||
import de.myzelyam.api.vanish.VelocityVanishAPI;
|
||||
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bstats.velocity.Metrics;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.sayandev.sayanvanish.velocity.api.SayanVanishVelocityAPI;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.nio.file.Path;
|
||||
@@ -67,10 +71,12 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class VelocityCore implements PluginCore<Favicon>{
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger("AdvancedServerList");
|
||||
public static final MinecraftChannelIdentifier ASL_IDENTIFIER = MinecraftChannelIdentifier.from("advancedserverlist:action");
|
||||
private final Map<String, ServerPing> fetchedServers = new ConcurrentHashMap<>();
|
||||
|
||||
private final PluginLogger pluginLogger;
|
||||
private final ComponentLogger logger;
|
||||
|
||||
private final ProxyServer proxy;
|
||||
private final Path path;
|
||||
private final Metrics.Factory metrics;
|
||||
@@ -84,7 +90,10 @@ public class VelocityCore implements PluginCore<Favicon>{
|
||||
|
||||
@Inject
|
||||
public VelocityCore(ProxyServer proxy, @DataDirectory Path path, Metrics.Factory metrics){
|
||||
this.pluginLogger = new VelocityLogger(this, this.logger);
|
||||
proxy.getChannelRegistrar().register(ASL_IDENTIFIER);
|
||||
ComponentLogger logger = ComponentLogger.logger("AdvancedServerList");
|
||||
this.pluginLogger = new VelocityLogger(this, logger);
|
||||
this.logger = logger;
|
||||
|
||||
this.proxy = proxy;
|
||||
this.path = path;
|
||||
@@ -112,12 +121,14 @@ public class VelocityCore implements PluginCore<Favicon>{
|
||||
.build();
|
||||
|
||||
getProxy().getCommandManager().register(command, new CmdAdvancedServerList(this));
|
||||
getPluginLogger().success("Registered <white>/advancedserverlist</white>!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadEvents(){
|
||||
new JoinEvent(this);
|
||||
new PingEvent(this);
|
||||
new JoinListener(this);
|
||||
new PingListener(this);
|
||||
new PluginMessageListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -223,6 +234,9 @@ public class VelocityCore implements PluginCore<Favicon>{
|
||||
// Exclude players when PremiumVanish is enabled and player is hidden.
|
||||
if(getProxy().getPluginManager().isLoaded("premiumvanish")){
|
||||
players.removeIf(VelocityVanishAPI::isInvisible);
|
||||
}else // Do the same if SayanVanish is enabled.
|
||||
if(getProxy().getPluginManager().isLoaded("sayanvanish")){
|
||||
players.removeIf(player -> SayanVanishVelocityAPI.getInstance().isVanished(player.getUniqueId()));
|
||||
}
|
||||
|
||||
return players.size();
|
||||
@@ -239,6 +253,11 @@ public class VelocityCore implements PluginCore<Favicon>{
|
||||
return;
|
||||
|
||||
fetchedServers.put(server.getServerInfo().getName(), ping);
|
||||
|
||||
ByteArrayDataOutput out = ByteStreams.newDataOutput();
|
||||
out.writeUTF("findPlugins");
|
||||
|
||||
server.sendPluginMessage(ASL_IDENTIFIER, out.toByteArray());
|
||||
}));
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -33,11 +33,11 @@ import com.velocitypowered.api.proxy.Player;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public class JoinEvent{
|
||||
public class JoinListener{
|
||||
|
||||
private final VelocityCore plugin;
|
||||
|
||||
public JoinEvent(VelocityCore plugin){
|
||||
public JoinListener(VelocityCore plugin){
|
||||
this.plugin = plugin;
|
||||
plugin.getProxy().getEventManager().register(plugin, this);
|
||||
}
|
||||
+2
-2
@@ -33,11 +33,11 @@ import com.velocitypowered.api.event.PostOrder;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
|
||||
|
||||
public class PingEvent{
|
||||
public class PingListener{
|
||||
|
||||
private final VelocityCore plugin;
|
||||
|
||||
public PingEvent(VelocityCore plugin){
|
||||
public PingListener(VelocityCore plugin){
|
||||
this.plugin = plugin;
|
||||
plugin.getProxy().getEventManager().register(plugin, this);
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package ch.andre601.advancedserverlist.velocity.listeners;
|
||||
|
||||
import ch.andre601.advancedserverlist.core.objects.PluginMessageUtil;
|
||||
import ch.andre601.advancedserverlist.velocity.VelocityCore;
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
|
||||
public class PluginMessageListener{
|
||||
|
||||
public PluginMessageListener(VelocityCore plugin){
|
||||
plugin.getProxy().getEventManager().register(plugin, this);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPluginMessage(PluginMessageEvent event){
|
||||
if(!(event.getSource() instanceof ServerConnection))
|
||||
return;
|
||||
|
||||
if(event.getIdentifier() != VelocityCore.ASL_IDENTIFIER)
|
||||
return;
|
||||
|
||||
ByteArrayDataInput input = ByteStreams.newDataInput(event.getData());
|
||||
String subChannel = input.readUTF();
|
||||
switch(subChannel){
|
||||
case "hasPlugin" -> {
|
||||
String serverName = input.readUTF();
|
||||
PluginMessageUtil.get().addServer(serverName);
|
||||
}
|
||||
case "parsed" -> {
|
||||
String uuid = input.readUTF();
|
||||
String parsed = input.readUTF();
|
||||
|
||||
PluginMessageUtil.get().queue(uuid, parsed, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
-21
@@ -29,17 +29,18 @@ import ch.andre601.advancedserverlist.api.events.GenericServerListEvent;
|
||||
import ch.andre601.advancedserverlist.api.objects.GenericServer;
|
||||
import ch.andre601.advancedserverlist.api.profiles.ProfileEntry;
|
||||
import ch.andre601.advancedserverlist.core.compat.maintenance.MaintenanceUtil;
|
||||
import ch.andre601.advancedserverlist.core.compat.papi.PAPIUtil;
|
||||
import ch.andre601.advancedserverlist.core.interfaces.core.PluginCore;
|
||||
import ch.andre601.advancedserverlist.core.interfaces.events.GenericEventWrapper;
|
||||
import ch.andre601.advancedserverlist.core.objects.CachedPlayer;
|
||||
import ch.andre601.advancedserverlist.core.objects.PluginMessageUtil;
|
||||
import ch.andre601.advancedserverlist.core.parsing.ComponentParser;
|
||||
import ch.andre601.advancedserverlist.core.profiles.replacer.StringReplacer;
|
||||
import ch.andre601.advancedserverlist.velocity.VelocityCore;
|
||||
import ch.andre601.advancedserverlist.velocity.objects.impl.VelocityPlayerImpl;
|
||||
import ch.andre601.advancedserverlist.velocity.objects.impl.VelocityProxyImpl;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
import com.velocitypowered.api.util.Favicon;
|
||||
@@ -119,6 +120,11 @@ public class VelocityEventWrapper implements GenericEventWrapper<Favicon, Veloci
|
||||
builder.clearSamplePlayers().samplePlayers(players);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayersHidden(){
|
||||
builder.clearSamplePlayers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFavicon(Favicon favicon){
|
||||
builder.favicon(favicon);
|
||||
@@ -172,25 +178,25 @@ public class VelocityEventWrapper implements GenericEventWrapper<Favicon, Veloci
|
||||
|
||||
@Override
|
||||
public String parsePAPIPlaceholders(String text, VelocityPlayerImpl player){
|
||||
if(!plugin.getProxy().getPluginManager().isLoaded("papiproxybridge"))
|
||||
return text;
|
||||
|
||||
if(!PAPIUtil.get().isCompatible())
|
||||
return text;
|
||||
|
||||
String server = PAPIUtil.get().getServer();
|
||||
if(server == null || server.isEmpty())
|
||||
return text;
|
||||
|
||||
RegisteredServer registeredServer = plugin.getProxy().getServer(server).orElse(null);
|
||||
if(registeredServer == null || registeredServer.getPlayersConnected().isEmpty())
|
||||
return text;
|
||||
|
||||
Player carrier = PAPIUtil.get().getPlayer(registeredServer.getPlayersConnected());
|
||||
if(carrier == null)
|
||||
return text;
|
||||
|
||||
return PAPIUtil.get().parse(text, carrier.getUniqueId(), player.getUUID());
|
||||
return PluginMessageUtil.get().getOrExecute(player.getUUID().toString(), text, (uuid, textValue) -> {
|
||||
List<String> servers = PluginMessageUtil.get().getKnownServers();
|
||||
if(servers.isEmpty())
|
||||
return textValue;
|
||||
|
||||
RegisteredServer registeredServer = plugin.getProxy().getServer(servers.get(0)).orElse(null);
|
||||
if(registeredServer == null)
|
||||
return textValue;
|
||||
|
||||
ByteArrayDataOutput out = ByteStreams.newDataOutput();
|
||||
out.writeUTF("parse");
|
||||
out.writeUTF(uuid);
|
||||
out.writeUTF(textValue);
|
||||
|
||||
registeredServer.sendPluginMessage(VelocityCore.ASL_IDENTIFIER, out.toByteArray());
|
||||
PluginMessageUtil.get().queue(uuid, textValue, false);
|
||||
|
||||
return PluginMessageUtil.get().getOrDefault(uuid, textValue);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+8
-7
@@ -26,15 +26,16 @@
|
||||
package ch.andre601.advancedserverlist.velocity.logging;
|
||||
|
||||
import ch.andre601.advancedserverlist.core.interfaces.PluginLogger;
|
||||
import ch.andre601.advancedserverlist.core.parsing.ComponentParser;
|
||||
import ch.andre601.advancedserverlist.velocity.VelocityCore;
|
||||
import org.slf4j.Logger;
|
||||
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
|
||||
|
||||
public class VelocityLogger implements PluginLogger{
|
||||
|
||||
private final VelocityCore plugin;
|
||||
private final Logger logger;
|
||||
private final ComponentLogger logger;
|
||||
|
||||
public VelocityLogger(VelocityCore plugin, Logger logger){
|
||||
public VelocityLogger(VelocityCore plugin, ComponentLogger logger){
|
||||
this.plugin = plugin;
|
||||
this.logger = logger;
|
||||
}
|
||||
@@ -53,21 +54,21 @@ public class VelocityLogger implements PluginLogger{
|
||||
|
||||
@Override
|
||||
public void info(String msg, Object... args){
|
||||
logger.info(String.format(msg, args));
|
||||
logger.info(ComponentParser.textFormatted("<grey>" + msg, args).toComponent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String msg, Object... args){
|
||||
logger.warn(String.format(msg, args));
|
||||
logger.warn(ComponentParser.textFormatted(msg, args).toComponent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String msg, Throwable throwable){
|
||||
logger.warn(msg, throwable);
|
||||
logger.warn(ComponentParser.text(msg).toComponent(), throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String msg, Throwable throwable, Object... args){
|
||||
logger.warn(String.format(msg, args), throwable);
|
||||
logger.warn(ComponentParser.textFormatted(msg, args).toComponent(), throwable);
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.5.12</version>
|
||||
<version>1.5.16</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
+1
-1
@@ -130,7 +130,7 @@ public class HangarVersionUploader{
|
||||
Platform.VELOCITY, velocityDependencies
|
||||
),
|
||||
Map.of(
|
||||
Platform.PAPER, List.of("1.20.x", "1.21"),
|
||||
Platform.PAPER, List.of("1.20.x", "1.21.x"),
|
||||
Platform.WATERFALL, List.of("1.19.x", "1.20.x", "1.21"),
|
||||
Platform.VELOCITY, Collections.singletonList("3.2")
|
||||
),
|
||||
|
||||
+2
-1
@@ -55,7 +55,8 @@ public class ModrinthVersionUploader{
|
||||
PlatformInfo.VELOCITY
|
||||
);
|
||||
private final List<String> versions = List.of(
|
||||
"1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4", "1.20.5", "1.20.6", "1.21"
|
||||
"1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4", "1.20.5", "1.20.6",
|
||||
"1.21", "1.21.1", "1.21.2", "1.21.3", "1.21.4"
|
||||
);
|
||||
|
||||
private final ModrinthAPI api;
|
||||
|
||||
Reference in new issue
Block a user