mirror of
https://github.com/WiIIiam278/Velocitab.git
synced 2025-03-13 13:30:01 +01:00
feat: Add configuration for server links (#201)
* feat: add server URLs * refactor: cleanup imports * fix: only send server links to 1.21 clients * feat: update server links on reload * refactor: minor cleanup * docs: add docs for server links * fix: protocol version check issue * Improved ServerUrl#resolve --------- Co-authored-by: AlexDev_ <56083016+alexdev03@users.noreply.github.com>
This commit is contained in:
parent
6f909fbec1
commit
84ae7a9437
18
build.gradle
18
build.gradle
@ -24,8 +24,8 @@ ext {
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url = 'https://repo.william278.net/velocity/' }
|
||||
maven { url = 'https://repo.papermc.io/repository/maven-public/' }
|
||||
maven { url = 'https://repo.william278.net/velocity/' }
|
||||
maven { url = 'https://repo.william278.net/releases/' }
|
||||
maven { url = 'https://jitpack.io/' }
|
||||
maven { url = 'https://repo.minebench.de/' }
|
||||
@ -35,20 +35,20 @@ dependencies {
|
||||
compileOnly "com.velocitypowered:velocity-api:${velocity_api_version}-SNAPSHOT"
|
||||
compileOnly "com.velocitypowered:velocity-proxy:${velocity_api_version}-SNAPSHOT"
|
||||
|
||||
compileOnly 'io.netty:netty-codec-http:4.1.111.Final'
|
||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
||||
compileOnly 'net.luckperms:api:5.4'
|
||||
compileOnly 'io.github.miniplaceholders:miniplaceholders-api:2.0.0'
|
||||
compileOnly 'net.william278:PAPIProxyBridge:1.5'
|
||||
compileOnly 'it.unimi.dsi:fastutil:8.5.13'
|
||||
compileOnly 'net.kyori:adventure-nbt:4.17.0'
|
||||
|
||||
implementation 'org.apache.commons:commons-text:1.12.0'
|
||||
implementation 'net.william278:DesertWell:2.0.4'
|
||||
implementation 'net.william278:minedown:1.8.2'
|
||||
implementation 'org.bstats:bstats-velocity:3.0.2'
|
||||
implementation 'de.exlll:configlib-yaml:4.5.0'
|
||||
|
||||
compileOnly 'io.netty:netty-codec-http:4.1.111.Final'
|
||||
compileOnly 'net.luckperms:api:5.4'
|
||||
compileOnly 'io.github.miniplaceholders:miniplaceholders-api:2.0.0'
|
||||
compileOnly 'net.william278:PAPIProxyBridge:1.5'
|
||||
compileOnly 'it.unimi.dsi:fastutil:8.5.13'
|
||||
compileOnly 'net.kyori:adventure-nbt:4.17.0'
|
||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
||||
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.32'
|
||||
}
|
||||
|
||||
|
@ -50,6 +50,16 @@ sort_players: true
|
||||
remove_spectator_effect: false
|
||||
# Whether to enable the Plugin Message API (allows backend plugins to perform certain operations)
|
||||
enable_plugin_message_api: true
|
||||
# A list of URLs that will be sent to display on player pause menus (Minecraft 1.21+ clients only).
|
||||
# • Labels can be fully custom or built-in (one of 'bug_report', 'community_guidelines', 'support', 'status',
|
||||
# 'feedback', 'community', 'website', 'forums', 'news', or 'announcements').
|
||||
# • If you supply a url with a 'bug_report' label, it will be shown if the player is disconnected.
|
||||
# • Specify a set of server groups each URL should be sent on. Use '*' to show a URL to all groups.
|
||||
server_links:
|
||||
- label: '�fb9a&About Velocitab'
|
||||
url: 'https://william278.net/project/velocitab'
|
||||
groups:
|
||||
- '*'
|
||||
```
|
||||
|
||||
</details>
|
||||
@ -110,4 +120,7 @@ As well as updating the text in the TAB menu, Velocitab supports updating player
|
||||
Velocitab supports basic header and footer animations by adding multiple frames of animation and setting the update rate to a value greater than 0.
|
||||
|
||||
### Placeholders
|
||||
You can use various placeholders that will be replaced with values (for example, `%username%`) in your config. Support for PlaceholderAPI is also available through [a bridge library plugin](https://modrinth.com/plugin/papiproxybridge), as is the component-based MiniPlaceholders for users of that plugin with the MiniMessage formatter. See [[Placeholders]] for more information.
|
||||
You can use various placeholders that will be replaced with values (for example, `%username%`) in your config. Support for PlaceholderAPI is also available through [a bridge library plugin](https://modrinth.com/plugin/papiproxybridge), as is the component-based MiniPlaceholders for users of that plugin with the MiniMessage formatter. See [[Placeholders]] for more information.
|
||||
|
||||
### Server Links
|
||||
For Minecraft 1.21+ clients, Velocitab supports specifying a list of URLs that will be sent to display in the player pause menu. See [[Server Links]] for more information.
|
37
docs/Server-Links.md
Normal file
37
docs/Server-Links.md
Normal file
@ -0,0 +1,37 @@
|
||||
> **Note:** This feature will only apply for users connecting with **Minecraft 1.21+** clients
|
||||
|
||||
Velocitab supports sending _Server Links_ to players, which will be displayed in the player pause menu by 1.21+ game clients. This can be useful for linking to your server's website, Discord, or other resources.
|
||||
|
||||
## Configuring
|
||||
Server links are configured with the `server_links` section in your `config.yml` file. A link must have:
|
||||
|
||||
* A `url` field; a valid web URL to link to
|
||||
* A `label` field, which is the text to display for the link. Labels can be::
|
||||
* Fully formatted custom text. You may include placeholders and formatting valid for your chosen formatter.
|
||||
* One of the following built-in label strings, which will be localized into the user's client language:
|
||||
* `bug_report` - Will also be shown on the disconnection error screen.
|
||||
* `community_guidelines`
|
||||
* `support`
|
||||
* `status`
|
||||
* `feedback`
|
||||
* `community`
|
||||
* `website`
|
||||
* `forums`
|
||||
* `news`
|
||||
* `announcements`
|
||||
* A `groups` field, which is a list of server groups the link should be sent to connecting players on.
|
||||
* Use `'*'` to show the link to all groups.
|
||||
|
||||
### Example section
|
||||
```yaml
|
||||
server_links:
|
||||
- url: 'https://william278.net/project/velocitab'
|
||||
label: 'website'
|
||||
groups: ['*']
|
||||
- url: 'https://william278.net/docs/velocitab'
|
||||
label: 'Documentation'
|
||||
groups: ['*']
|
||||
- url: 'https://github.com/William278/Velocitab/issues'
|
||||
label: 'bug_report' # This will use the bug report built-in label and also be shown on the player disconnect screen
|
||||
groups: ['*']
|
||||
```
|
@ -3,9 +3,9 @@ javaVersion=17
|
||||
org.gradle.jvmargs='-Dfile.encoding=UTF-8'
|
||||
org.gradle.daemon=true
|
||||
|
||||
plugin_version=1.6.6
|
||||
plugin_version=1.7
|
||||
plugin_archive=velocitab
|
||||
plugin_description=A beautiful and versatile TAB list plugin for Velocity proxies
|
||||
|
||||
velocity_api_version=3.3.0
|
||||
velocity_minimum_build=398
|
||||
velocity_minimum_build=400
|
BIN
images/server-links.png
Normal file
BIN
images/server-links.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 MiB |
@ -77,7 +77,7 @@ public enum Placeholder {
|
||||
private final TriFunction<String, Velocitab, TabPlayer, String> replacer;
|
||||
private final boolean parameterised;
|
||||
private final Pattern pattern;
|
||||
private final static Pattern checkPlaceholders = Pattern.compile("%.*?%");
|
||||
private final static Pattern CHECK_PLACEHOLDERS = Pattern.compile("%.*?%");
|
||||
private final static String DELIMITER = ":::";
|
||||
|
||||
Placeholder(@NotNull BiFunction<Velocitab, TabPlayer, String> replacer) {
|
||||
@ -109,7 +109,6 @@ public enum Placeholder {
|
||||
|
||||
public static CompletableFuture<String> replace(@NotNull String format, @NotNull Velocitab plugin,
|
||||
@NotNull TabPlayer player) {
|
||||
|
||||
if (format.equals(DELIMITER)) {
|
||||
return CompletableFuture.completedFuture("");
|
||||
}
|
||||
@ -119,19 +118,20 @@ public enum Placeholder {
|
||||
if (placeholder.parameterised) {
|
||||
// Replace the placeholder with the result of the replacer function with the parameter
|
||||
format = matcher.replaceAll(matchResult ->
|
||||
Matcher.quoteReplacement(
|
||||
placeholder.replacer.apply(StringUtils.chop(matchResult.group().replace("%" + placeholder.name().toLowerCase(), ""))
|
||||
, plugin, player)
|
||||
));
|
||||
Matcher.quoteReplacement(placeholder.replacer.apply(
|
||||
StringUtils.chop(matchResult.group().replace(
|
||||
"%" + placeholder.name().toLowerCase(), ""
|
||||
)), plugin, player
|
||||
)));
|
||||
} else {
|
||||
// Replace the placeholder with the result of the replacer function
|
||||
format = matcher.replaceAll(matchResult -> Matcher.quoteReplacement(placeholder.replacer.apply(null, plugin, player)));
|
||||
}
|
||||
|
||||
}
|
||||
final String replaced = format;
|
||||
|
||||
if (!checkPlaceholders.matcher(replaced).find()) {
|
||||
final String replaced = format;
|
||||
if (!CHECK_PLACEHOLDERS.matcher(replaced).find()) {
|
||||
return CompletableFuture.completedFuture(replaced);
|
||||
}
|
||||
|
||||
|
90
src/main/java/net/william278/velocitab/config/ServerUrl.java
Normal file
90
src/main/java/net/william278/velocitab/config/ServerUrl.java
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* This file is part of Velocitab, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.velocitab.config;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.velocitypowered.api.util.ServerLink;
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import net.william278.velocitab.player.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public record ServerUrl(
|
||||
@NotNull String label,
|
||||
@NotNull String url,
|
||||
@NotNull Set<String> groups
|
||||
) {
|
||||
|
||||
public ServerUrl(@NotNull String label, @NotNull String url) {
|
||||
this(label, url, Set.of("*"));
|
||||
}
|
||||
|
||||
// Resolve the built-in label or format the custom label, then wrap as a Velocity ServerLink
|
||||
@NotNull
|
||||
CompletableFuture<ServerLink> getServerLink(@NotNull Velocitab plugin, @NotNull TabPlayer player) {
|
||||
return getBuiltInLabel().map(
|
||||
(type) -> CompletableFuture.completedFuture(ServerLink.serverLink(type, url()))
|
||||
).orElseGet(
|
||||
() -> Placeholder.replace(label(), plugin, player)
|
||||
.thenApply(replaced -> plugin.getFormatter().format(replaced, player, plugin))
|
||||
.thenApply(formatted -> ServerLink.serverLink(formatted, url()))
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static CompletableFuture<List<ServerLink>> resolve(@NotNull Velocitab plugin, @NotNull TabPlayer player,
|
||||
@NotNull List<ServerUrl> urls) {
|
||||
final List<CompletableFuture<ServerLink>> futures = new ArrayList<>();
|
||||
for (ServerUrl url : urls) {
|
||||
futures.add(url.getServerLink(plugin, player));
|
||||
}
|
||||
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
|
||||
.thenApply(v -> futures.stream()
|
||||
.map(CompletableFuture::join).toList());
|
||||
}
|
||||
|
||||
private Optional<ServerLink.Type> getBuiltInLabel() {
|
||||
final String label = label().replaceAll(" ", "_").toUpperCase(Locale.ENGLISH);
|
||||
return Arrays.stream(ServerLink.Type.values()).filter(type -> type.name().equals(label)).findFirst();
|
||||
}
|
||||
|
||||
// Validate a ServerUrl
|
||||
void validate() throws IllegalStateException {
|
||||
if (label().isEmpty()) {
|
||||
throw new IllegalStateException("Server URL label cannot be empty");
|
||||
}
|
||||
if (url().isEmpty()) {
|
||||
throw new IllegalStateException("Server URL cannot be empty");
|
||||
}
|
||||
if (groups().isEmpty()) {
|
||||
throw new IllegalStateException("Server URL must have at least one group, or '*' to show on all groups");
|
||||
}
|
||||
try {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
URI.create(url());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalStateException("Server URL is not a valid URI");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -27,6 +27,7 @@ import lombok.NoArgsConstructor;
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@ -96,6 +97,18 @@ public class Settings implements ConfigValidator {
|
||||
@Comment("Whether to enable the Plugin Message API (allows backend plugins to perform certain operations)")
|
||||
private boolean enablePluginMessageApi = true;
|
||||
|
||||
@Comment({"A list of links that will be sent to display on player pause menus (Minecraft 1.21+ clients only).",
|
||||
"• Labels can be fully custom or built-in (one of 'bug_report', 'community_guidelines', 'support', 'status',",
|
||||
" 'feedback', 'community', 'website', 'forums', 'news', or 'announcements').",
|
||||
"• If you supply a url with a 'bug_report' label, it will be shown if the player is disconnected.",
|
||||
"• Specify a set of server groups each URL should be sent on. Use '*' to show a URL to all groups."})
|
||||
private List<ServerUrl> serverLinks = List.of(
|
||||
new ServerUrl(
|
||||
"�fb9a&About Velocitab",
|
||||
"https://william278.net/project/velocitab"
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Get display name for the server
|
||||
*
|
||||
@ -107,10 +120,19 @@ public class Settings implements ConfigValidator {
|
||||
return serverDisplayNames.getOrDefault(serverName, serverName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<ServerUrl> getUrlsForGroup(@NotNull Group group) {
|
||||
return serverLinks.stream()
|
||||
.filter(link -> link.groups().contains("*") || link.groups().contains(group.name()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateConfig(@NotNull Velocitab plugin) {
|
||||
if (papiCacheTime < 0) {
|
||||
throw new IllegalStateException("PAPI cache time must be greater than or equal to 0");
|
||||
}
|
||||
serverLinks.forEach(ServerUrl::validate);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
package net.william278.velocitab.tab;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.player.TabList;
|
||||
@ -33,6 +34,7 @@ import net.william278.velocitab.Velocitab;
|
||||
import net.william278.velocitab.api.PlayerAddedToTabEvent;
|
||||
import net.william278.velocitab.config.Group;
|
||||
import net.william278.velocitab.config.Placeholder;
|
||||
import net.william278.velocitab.config.ServerUrl;
|
||||
import net.william278.velocitab.player.Role;
|
||||
import net.william278.velocitab.player.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -142,18 +144,19 @@ public class PlayerTabList {
|
||||
protected void joinPlayer(@NotNull Player joined, @NotNull Group group) {
|
||||
// Add the player to the tracking list if they are not already listed
|
||||
final TabPlayer tabPlayer = getTabPlayer(joined).orElseGet(() -> createTabPlayer(joined, group));
|
||||
final boolean isVanished = plugin.getVanishManager().isVanished(joined.getUsername());
|
||||
tabPlayer.setGroup(group);
|
||||
players.putIfAbsent(joined.getUniqueId(), tabPlayer);
|
||||
|
||||
// Store the player's last server, so it's possible to have the last server on disconnect
|
||||
final String serverName = getServerName(joined);
|
||||
|
||||
//store last server, so it's possible to have the last server on disconnect
|
||||
tabPlayer.setLastServer(serverName);
|
||||
|
||||
final boolean isVanished = plugin.getVanishManager().isVanished(joined.getUsername());
|
||||
// Send server URLs (1.21 clients)
|
||||
sendPlayerServerLinks(tabPlayer);
|
||||
|
||||
// Determine display name, update TAB for player
|
||||
tabPlayer.getDisplayName(plugin).thenAccept(d -> {
|
||||
|
||||
joined.getTabList().getEntry(joined.getUniqueId())
|
||||
.ifPresentOrElse(e -> e.setDisplayName(d),
|
||||
() -> joined.getTabList().addEntry(createEntry(tabPlayer, joined.getTabList(), d)));
|
||||
@ -169,7 +172,6 @@ public class PlayerTabList {
|
||||
final Set<String> serversInGroup = group.registeredServers(plugin).stream()
|
||||
.map(server -> server.getServerInfo().getName())
|
||||
.collect(HashSet::new, HashSet::add, HashSet::addAll);
|
||||
|
||||
serversInGroup.remove(serverName);
|
||||
|
||||
// Update lists
|
||||
@ -342,6 +344,14 @@ public class PlayerTabList {
|
||||
});
|
||||
}
|
||||
|
||||
public void sendPlayerServerLinks(@NotNull TabPlayer player) {
|
||||
if (player.getPlayer().getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_21)) {
|
||||
return;
|
||||
}
|
||||
final List<ServerUrl> urls = plugin.getSettings().getUrlsForGroup(player.getGroup());
|
||||
ServerUrl.resolve(plugin, player, urls).thenAccept(player.getPlayer()::setServerLinks);
|
||||
}
|
||||
|
||||
public void updatePlayerDisplayName(@NotNull TabPlayer tabPlayer) {
|
||||
final Component lastDisplayName = tabPlayer.getLastDisplayName();
|
||||
tabPlayer.getDisplayName(plugin).thenAccept(displayName -> {
|
||||
@ -475,10 +485,10 @@ public class PlayerTabList {
|
||||
*/
|
||||
public void reloadUpdate() {
|
||||
plugin.getTabGroups().getGroups().forEach(this::updatePeriodically);
|
||||
|
||||
if (players.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the update time is set to 0 do not schedule the updater
|
||||
players.values().forEach(player -> {
|
||||
final Optional<ServerConnection> server = player.getPlayer().getCurrentServer();
|
||||
@ -488,6 +498,7 @@ public class PlayerTabList {
|
||||
final String serverName = server.get().getServerInfo().getName();
|
||||
final Group group = getGroup(serverName);
|
||||
player.setGroup(group);
|
||||
this.sendPlayerServerLinks(player);
|
||||
this.updatePlayer(player, true);
|
||||
player.sendHeaderAndFooter(this);
|
||||
});
|
||||
|
@ -82,11 +82,12 @@ public class TabListListener {
|
||||
@Subscribe
|
||||
public void onPlayerJoin(@NotNull ServerPostConnectEvent event) {
|
||||
final Player joined = event.getPlayer();
|
||||
|
||||
final String serverName = joined.getCurrentServer()
|
||||
.map(ServerConnection::getServerInfo)
|
||||
.map(ServerInfo::getName)
|
||||
.orElse("");
|
||||
|
||||
// Get the group the player should now be in
|
||||
final Group group = tabList.getGroup(serverName);
|
||||
plugin.getScoreboardManager().ifPresent(manager -> manager.resetCache(joined, group));
|
||||
final boolean isDefault = group.registeredServers(plugin).stream()
|
||||
|
Loading…
Reference in New Issue
Block a user