Implement update notifications for Geyser

Geyser installations will now get notified when a new Bedrock release is out and Geyser must be updated. The system works similarly to ViaVersion where OPs get a notification of an update when they join. The permission node for players to see update notifications is `geyser.update` and the backing JSON that controls this can be found at https://github.com/GeyserMC/GeyserSite/blob/gh-pages/versions.json. There is also a config option to disable update checking.

This update also fixes modern Paper installations not being able to see colored text logged from Geyser in the console.
This commit is contained in:
Camotoy 2022-08-21 21:22:15 -04:00
parent a3b1cf61ad
commit 67a65c45d3
No known key found for this signature in database
GPG key ID: 7EEFB66FE798081F
26 changed files with 558 additions and 34 deletions

View file

@ -37,6 +37,9 @@ public final class Constants {
public static final String FLOODGATE_DOWNLOAD_LOCATION = "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/";
public static final String GEYSER_DOWNLOAD_LOCATION = "https://ci.geysermc.org";
public static final String UPDATE_PERMISSION = "geyser.update";
static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json";
static {

View file

@ -41,6 +41,8 @@ import io.netty.util.internal.SystemPropertyUtil;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.api.Geyser;
@ -66,7 +68,6 @@ import org.geysermc.geyser.session.SessionManager;
import org.geysermc.geyser.session.auth.AuthType;
import org.geysermc.geyser.skin.FloodgateSkinUploader;
import org.geysermc.geyser.skin.SkinProvider;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.text.MinecraftLocale;
import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
@ -303,8 +304,8 @@ public class GeyserImpl implements GeyserApi {
int port = config.getBedrock().getPort();
logger.severe(GeyserLocale.getLocaleStringLog("geyser.core.fail", address, String.valueOf(port)));
if (!"0.0.0.0".equals(address)) {
logger.info(ChatColor.GREEN + "Suggestion: try setting `address` under `bedrock` in the Geyser config back to 0.0.0.0");
logger.info(ChatColor.GREEN + "Then, restart this server.");
logger.info(Component.text("Suggestion: try setting `address` under `bedrock` in the Geyser config back to 0.0.0.0", NamedTextColor.GREEN));
logger.info(Component.text("Then, restart this server.", NamedTextColor.GREEN));
}
}
}).join();
@ -454,6 +455,9 @@ public class GeyserImpl implements GeyserApi {
}
newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED);
if (config.isNotifyOnNewBedrockUpdate()) {
VersionCheckUtils.checkForGeyserUpdate(this::getLogger);
}
}
@Override

View file

@ -25,9 +25,12 @@
package org.geysermc.geyser;
import net.kyori.adventure.text.Component;
import org.geysermc.geyser.command.CommandSender;
import javax.annotation.Nullable;
public interface GeyserLogger {
public interface GeyserLogger extends CommandSender {
/**
* Logs a severe message to console
@ -73,6 +76,15 @@ public interface GeyserLogger {
*/
void info(String message);
/**
* Logs an info component to console
*
* @param message the message to log
*/
default void info(Component message) {
sendMessage(message);
}
/**
* Logs a debug message to console
*
@ -100,4 +112,24 @@ public interface GeyserLogger {
* If debug is enabled for this logger
*/
boolean isDebug();
@Override
default String name() {
return "CONSOLE";
}
@Override
default void sendMessage(String message) {
info(message);
}
@Override
default boolean isConsole() {
return true;
}
@Override
default boolean hasPermission(String permission) {
return true;
}
}

View file

@ -25,6 +25,8 @@
package org.geysermc.geyser.command;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.geysermc.geyser.text.GeyserLocale;
/**
@ -43,6 +45,10 @@ public interface CommandSender {
void sendMessage(String message);
default void sendMessage(Component message) {
sendMessage(LegacyComponentSerializer.legacySection().serialize(message));
}
/**
* @return true if the specified sender is from the console.
*/

View file

@ -105,6 +105,8 @@ public interface GeyserConfiguration {
int getCustomSkullRenderDistance();
boolean isNotifyOnNewBedrockUpdate();
IMetricsInfo getMetrics();
int getPendingAuthenticationTimeout();

View file

@ -148,6 +148,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
@JsonProperty("xbox-achievements-enabled")
private boolean xboxAchievementsEnabled = false;
@JsonProperty("notify-on-new-bedrock-update")
private boolean notifyOnNewBedrockUpdate = true;
private MetricsInfo metrics = new MetricsInfo();
@JsonProperty("pending-authentication-timeout")

View file

@ -31,6 +31,7 @@ import com.nukkitx.protocol.bedrock.packet.SetTitlePacket;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.scoreboard.Scoreboard;
import org.geysermc.geyser.scoreboard.ScoreboardUpdater.ScoreboardSession;
import org.geysermc.geyser.session.GeyserSession;
@ -172,6 +173,7 @@ public final class WorldCache {
if (serverVerifiedState.sequence <= sequence) {
// This block may be out of sync with the server
// In 1.19.0 Java, you can verify this by trying to mine in spawn protection
System.out.println("Resetting " + entry.getKey() + " to " + BlockRegistries.JAVA_BLOCKS.get(serverVerifiedState.blockState).getJavaIdentifier());
ChunkUtils.updateBlockClientSide(session, serverVerifiedState.blockState, entry.getKey());
it.remove();
}

View file

@ -25,10 +25,22 @@
package org.geysermc.geyser.util;
import com.fasterxml.jackson.databind.JsonNode;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextReplacementConfig;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.command.CommandSender;
import org.geysermc.geyser.network.MinecraftProtocol;
import org.geysermc.geyser.text.GeyserLocale;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
public final class VersionCheckUtils {
public static void checkForOutdatedFloodgate(GeyserLogger logger) {
@ -42,6 +54,41 @@ public final class VersionCheckUtils {
}
}
public static void checkForGeyserUpdate(Supplier<CommandSender> recipient) {
CompletableFuture.runAsync(() -> {
try {
JsonNode json = WebUtils.getJson("https://api.geysermc.org/v2/versions/geyser");
JsonNode bedrock = json.get("bedrock").get("protocol");
int protocolVersion = bedrock.get("id").asInt();
if (MinecraftProtocol.getBedrockCodec(protocolVersion) != null) {
// We support the latest version! No need to print a message.
return;
}
final String newBedrockVersion = bedrock.get("name").asText();
// Delayed for two reasons: save unnecessary processing, and wait to load locale if this is on join.
CommandSender sender = recipient.get();
// Overarching component is green - geyser.version.new component cannot be green or else the link blue is overshadowed
Component message = Component.text().color(NamedTextColor.GREEN)
.append(Component.text(GeyserLocale.getPlayerLocaleString("geyser.version.new", sender.getLocale(), newBedrockVersion))
.replaceText(TextReplacementConfig.builder()
.match("\\{1\\}") // Replace "Download here: {1}" so we can use fancy text component yesyes
.replacement(Component.text()
.content(Constants.GEYSER_DOWNLOAD_LOCATION)
.color(NamedTextColor.BLUE)
.decoration(TextDecoration.UNDERLINED, TextDecoration.State.TRUE)
.clickEvent(ClickEvent.openUrl(Constants.GEYSER_DOWNLOAD_LOCATION)))
.build()))
.build();
sender.sendMessage(message);
} catch (Exception e) {
GeyserImpl.getInstance().getLogger().error("Error whilst checking for Geyser update!", e);
}
});
}
private VersionCheckUtils() {
}
}

View file

@ -73,6 +73,8 @@ public class WebUtils {
public static JsonNode getJson(String reqURL) throws IOException {
HttpURLConnection con = (HttpURLConnection) new URL(reqURL).openConnection();
con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION);
con.setConnectTimeout(10000);
con.setReadTimeout(10000);
return GeyserImpl.JSON_MAPPER.readTree(con.getInputStream());
}

View file

@ -175,6 +175,11 @@ force-resource-packs: true
# THIS DISABLES ALL COMMANDS FROM SUCCESSFULLY RUNNING FOR BEDROCK IN-GAME, as otherwise Bedrock thinks you are cheating.
xbox-achievements-enabled: false
# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version
# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms
# auto-update.
notify-on-new-bedrock-update: true
# bStats is a stat tracker that is entirely anonymous and tracks only basic information
# about Geyser, such as how many people are online, how many servers are using Geyser,
# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/.