mirror of
https://github.com/DiscordSRV/Ascension.git
synced 2024-12-25 17:08:27 +01:00
Update checker
This commit is contained in:
parent
bab1d4a765
commit
29f48944d3
@ -31,6 +31,7 @@ import com.discordsrv.api.discord.entity.channel.DiscordTextChannel;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuild;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.List;
|
||||
@ -43,10 +44,11 @@ import java.util.concurrent.CompletableFuture;
|
||||
public interface ReceivedDiscordMessage extends Snowflake {
|
||||
|
||||
/**
|
||||
* Gets the content of this message.
|
||||
* @return the message content
|
||||
* Gets the content of this message. This will return {@code null} if the bot doesn't have the MESSAGE_CONTENT intent,
|
||||
* and this message was not from the bot, did not mention the bot and was not a direct message.
|
||||
* @return the message content or {@code null}
|
||||
*/
|
||||
@NotNull
|
||||
@Nullable
|
||||
String getContent();
|
||||
|
||||
/**
|
||||
|
@ -33,6 +33,7 @@ import com.discordsrv.common.channel.GlobalChannelLookupModule;
|
||||
import com.discordsrv.common.command.game.GameCommandModule;
|
||||
import com.discordsrv.common.component.ComponentFactory;
|
||||
import com.discordsrv.common.config.connection.ConnectionConfig;
|
||||
import com.discordsrv.common.config.connection.UpdateConfig;
|
||||
import com.discordsrv.common.config.main.LinkedAccountConfig;
|
||||
import com.discordsrv.common.config.main.MainConfig;
|
||||
import com.discordsrv.common.config.manager.ConnectionConfigManager;
|
||||
@ -71,6 +72,8 @@ import com.discordsrv.common.placeholder.result.ComponentResultStringifier;
|
||||
import com.discordsrv.common.profile.ProfileManager;
|
||||
import com.discordsrv.common.storage.Storage;
|
||||
import com.discordsrv.common.storage.StorageType;
|
||||
import com.discordsrv.common.update.UpdateChecker;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.JDAInfo;
|
||||
@ -133,12 +136,15 @@ public abstract class AbstractDiscordSRV<B extends IBootstrap, C extends MainCon
|
||||
private LinkProvider linkProvider;
|
||||
|
||||
// Version
|
||||
private UpdateChecker updateChecker;
|
||||
private String version;
|
||||
private String gitRevision;
|
||||
private String gitBranch;
|
||||
|
||||
private OkHttpClient httpClient;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final ObjectMapper objectMapper = new ObjectMapper()
|
||||
.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
|
||||
// Internal
|
||||
private final ReentrantLock lifecycleLock = new ReentrantLock();
|
||||
@ -165,6 +171,7 @@ public abstract class AbstractDiscordSRV<B extends IBootstrap, C extends MainCon
|
||||
this.discordConnectionDetails = new DiscordConnectionDetailsImpl(this);
|
||||
this.discordConnectionManager = new JDAConnectionManager(this);
|
||||
this.channelConfig = new ChannelConfigHelper(this);
|
||||
this.updateChecker = new UpdateChecker(this);
|
||||
readManifest();
|
||||
|
||||
Dispatcher dispatcher = new Dispatcher();
|
||||
@ -587,6 +594,27 @@ public abstract class AbstractDiscordSRV<B extends IBootstrap, C extends MainCon
|
||||
channelConfig().reload();
|
||||
}
|
||||
|
||||
// Update check
|
||||
UpdateConfig updateConfig = connectionConfig().update;
|
||||
if (updateConfig.security.enabled) {
|
||||
if (updateChecker.isSecurityFailed()) {
|
||||
// Security has already failed
|
||||
return;
|
||||
}
|
||||
|
||||
if (initial && !updateChecker.check(true)) {
|
||||
// Security failed cancel startup & shutdown
|
||||
invokeDisable();
|
||||
return;
|
||||
}
|
||||
} else if (initial) {
|
||||
// Not using security, run update check off thread
|
||||
scheduler().run(() -> updateChecker.check(true));
|
||||
}
|
||||
if (initial) {
|
||||
scheduler().runAtFixedRate(() -> updateChecker.check(false), 6L, TimeUnit.HOURS);
|
||||
}
|
||||
|
||||
if (flags.contains(ReloadFlag.LINKED_ACCOUNT_PROVIDER)) {
|
||||
LinkedAccountConfig linkedAccountConfig = config().linkedAccounts;
|
||||
if (linkedAccountConfig != null && linkedAccountConfig.enabled) {
|
||||
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.config.connection;
|
||||
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
@ConfigSerializable
|
||||
public class BotConfig {
|
||||
|
||||
@Comment("The Discord bot token from https://discord.com/developers/applications\n"
|
||||
+ "Requires a connection to: discord.com, gateway.discord.gg, cdn.discordapp.com")
|
||||
public String token = "Token here";
|
||||
|
||||
}
|
@ -20,7 +20,6 @@ package com.discordsrv.common.config.connection;
|
||||
|
||||
import com.discordsrv.common.config.Config;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
@ConfigSerializable
|
||||
public class ConnectionConfig implements Config {
|
||||
@ -32,15 +31,9 @@ public class ConnectionConfig implements Config {
|
||||
return FILE_NAME;
|
||||
}
|
||||
|
||||
public Bot bot = new Bot();
|
||||
public BotConfig bot = new BotConfig();
|
||||
|
||||
public StorageConfig storage = new StorageConfig();
|
||||
|
||||
@ConfigSerializable
|
||||
public static class Bot {
|
||||
|
||||
@Comment("Don't know what this is? Neither do I")
|
||||
public String token = "Token here";
|
||||
|
||||
}
|
||||
public UpdateConfig update = new UpdateConfig();
|
||||
}
|
||||
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.config.connection;
|
||||
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Setting;
|
||||
|
||||
@ConfigSerializable
|
||||
public class UpdateConfig {
|
||||
|
||||
@Setting(value = "notification-enabled")
|
||||
@Comment("On/off for notifications when a new version of DiscordSRV is available")
|
||||
public boolean notificationEnabled = true;
|
||||
|
||||
@Setting(value = "notification-ingame")
|
||||
@Comment("If players with the discordsrv.updatenotification permission should receive\n"
|
||||
+ "a update notification upon joining if there is a update available")
|
||||
public boolean notificationInGame = true;
|
||||
|
||||
@Setting(value = "enable-first-party-api-for-notifications")
|
||||
@Comment("Weather the DiscordSRV download API should be used for update checks\n"
|
||||
+ "Requires a connection to: download.discordsrv.com")
|
||||
public boolean firstPartyNotification = true;
|
||||
|
||||
@Setting(value = "github")
|
||||
public GitHub github = new GitHub();
|
||||
|
||||
@Setting(value = "security")
|
||||
public Security security = new Security();
|
||||
|
||||
@ConfigSerializable
|
||||
public static class GitHub {
|
||||
|
||||
@Setting(value = "enabled")
|
||||
@Comment("Weather the GitHub API should be used for update checks\n"
|
||||
+ "This will be the secondary API if both first party and GitHub sources are enabled\n"
|
||||
+ "Requires a connection to: api.github.com")
|
||||
public boolean enabled = true;
|
||||
|
||||
@Setting(value = "api-token")
|
||||
@Comment("The GitHub API token used for authenticating to the GitHub api,\n"
|
||||
+ "if this isn't specified the API will be used 'anonymously'\n"
|
||||
+ "The token only requires read permission to DiscordSRV/DiscordSRV releases, workflows and commits")
|
||||
public String apiToken = "";
|
||||
|
||||
}
|
||||
|
||||
@ConfigSerializable
|
||||
public static class Security {
|
||||
|
||||
@Setting(value = "enabled")
|
||||
@Comment("Uses the DiscordSRV download API to check if the version of DiscordSRV\n"
|
||||
+ "being used is vulnerable to known vulnerabilities, disabling the plugin if it is.\n"
|
||||
+ "Requires a connection to: download.discordsrv.com\n"
|
||||
+ "\n"
|
||||
+ "WARNING! DO NOT TURN THIS OFF UNLESS YOU KNOW WHAT YOU'RE DOING AND STAY UP-TO-DATE")
|
||||
public boolean enabled = true;
|
||||
|
||||
@Setting(value = "force")
|
||||
@Comment("If the security check needs to be completed for DiscordSRV to enable,\n"
|
||||
+ "if the security check fails, DiscordSRV will be disabled if this option is set to true")
|
||||
public boolean force = false;
|
||||
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.connection.ConnectionConfig;
|
||||
import com.discordsrv.common.config.manager.loader.YamlConfigLoaderProvider;
|
||||
import com.discordsrv.common.config.manager.manager.TranslatedConfigManager;
|
||||
import org.spongepowered.configurate.ConfigurationOptions;
|
||||
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
||||
|
||||
public abstract class ConnectionConfigManager<C extends ConnectionConfig>
|
||||
@ -32,6 +33,18 @@ public abstract class ConnectionConfigManager<C extends ConnectionConfig>
|
||||
super(discordSRV);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurationOptions defaultOptions() {
|
||||
return super.defaultOptions()
|
||||
.header("DiscordSRV's configuration file for connections to different external services.\n"
|
||||
+ "This file is intended to contain connection details to services in order to keep them out of the config.yml\n"
|
||||
+ "and to serve as a easy way to identify and control what external connections are being used.\n"
|
||||
+ "\n"
|
||||
+ "All domains listed as \"Requires a connection to\" require port 443 (https/wss) unless otherwise specified\n"
|
||||
+ "\n"
|
||||
+ " ABSOLUTELY DO NOT SEND THIS FILE TO ANYONE - IT ONLY CONTAINS SECRETS\n");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String fileName() {
|
||||
return ConnectionConfig.FILE_NAME;
|
||||
|
@ -25,6 +25,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.configurate.CommentedConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurationOptions;
|
||||
import org.spongepowered.configurate.loader.AbstractConfigurationLoader;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
||||
@ -37,6 +38,8 @@ import java.util.List;
|
||||
public abstract class TranslatedConfigManager<T extends Config, LT extends AbstractConfigurationLoader<CommentedConfigurationNode>>
|
||||
extends ConfigurateConfigManager<T, LT> {
|
||||
|
||||
private String header;
|
||||
|
||||
public TranslatedConfigManager(DiscordSRV discordSRV) {
|
||||
super(discordSRV);
|
||||
}
|
||||
@ -48,6 +51,15 @@ public abstract class TranslatedConfigManager<T extends Config, LT extends Abstr
|
||||
super.save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurationOptions defaultOptions() {
|
||||
ConfigurationOptions options = super.defaultOptions();
|
||||
if (header != null) {
|
||||
options = options.header(header);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ConfigurationNode getTranslation() throws ConfigurateException {
|
||||
ConfigurationNode translation = getTranslationRoot();
|
||||
@ -77,6 +89,8 @@ public abstract class TranslatedConfigManager<T extends Config, LT extends Abstr
|
||||
ConfigurationNode comments = translationRoot.node(fileIdentifier + "_comments");
|
||||
|
||||
CommentedConfigurationNode node = loader().createNode();
|
||||
this.header = comments.node("$header").getString();
|
||||
|
||||
save(config, (Class<T>) config.getClass(), node);
|
||||
translateNode(node, translation, comments);
|
||||
} catch (ConfigurateException e) {
|
||||
|
@ -27,6 +27,7 @@ import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent;
|
||||
import com.discordsrv.api.event.events.placeholder.PlaceholderLookupEvent;
|
||||
import com.discordsrv.api.placeholder.PlaceholderLookupResult;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.connection.BotConfig;
|
||||
import com.discordsrv.common.config.connection.ConnectionConfig;
|
||||
import com.discordsrv.common.discord.api.DiscordAPIImpl;
|
||||
import com.discordsrv.common.discord.api.entity.message.ReceivedDiscordMessageImpl;
|
||||
@ -101,6 +102,9 @@ public class JDAConnectionManager implements DiscordConnectionManager {
|
||||
// Disable all mentions by default for safety
|
||||
MessageRequest.setDefaultMentions(Collections.emptyList());
|
||||
|
||||
// Disable this warning (that doesn't even have a stacktrace)
|
||||
Message.suppressContentIntentWarning();
|
||||
|
||||
discordSRV.eventBus().subscribe(this);
|
||||
}
|
||||
|
||||
@ -247,7 +251,7 @@ public class JDAConnectionManager implements DiscordConnectionManager {
|
||||
TimeUnit.SECONDS
|
||||
);
|
||||
|
||||
ConnectionConfig.Bot botConfig = discordSRV.connectionConfig().bot;
|
||||
BotConfig botConfig = discordSRV.connectionConfig().bot;
|
||||
DiscordConnectionDetails connectionDetails = discordSRV.discordConnectionDetails();
|
||||
Set<GatewayIntent> intents = connectionDetails.getGatewayIntents();
|
||||
boolean membersIntent = intents.contains(GatewayIntent.GUILD_MEMBERS);
|
||||
|
@ -100,6 +100,12 @@ public class DiscordSRVLogger implements Logger {
|
||||
|
||||
@Override
|
||||
public void log(@Nullable String loggerName, @NotNull LogLevel logLevel, @Nullable String message, @Nullable Throwable throwable) {
|
||||
if (throwable != null && throwable.getMessage() != null
|
||||
&& (throwable.getStackTrace() == null || throwable.getStackTrace().length == 0)) {
|
||||
// Empty stack trace
|
||||
message = (message != null ? message + ": " : "") + throwable.getMessage();
|
||||
throwable = null;
|
||||
}
|
||||
if (throwable instanceof InsufficientPermissionException) {
|
||||
Permission permission = ((InsufficientPermissionException) throwable).getPermission();
|
||||
String msg = "The bot is missing the \"" + permission.getName() + "\" permission";
|
||||
|
@ -83,7 +83,7 @@ public interface Scheduler {
|
||||
ScheduledFuture<?> runLater(Runnable task, long timeMillis);
|
||||
|
||||
/**
|
||||
* Schedules the given task without any initial delay.
|
||||
* Schedules the given task at the given rate.
|
||||
*
|
||||
* @param task the task
|
||||
* @param rate the rate in the given unit
|
||||
@ -91,7 +91,7 @@ public interface Scheduler {
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
default ScheduledFuture<?> runAtFixedRate(@NotNull Runnable task, long rate, @NotNull TimeUnit unit) {
|
||||
return runAtFixedRate(task, 0, rate, unit);
|
||||
return runAtFixedRate(task, rate, rate, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,313 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.update;
|
||||
|
||||
import com.discordsrv.api.event.bus.Subscribe;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.connection.ConnectionConfig;
|
||||
import com.discordsrv.common.config.connection.UpdateConfig;
|
||||
import com.discordsrv.common.exception.MessageException;
|
||||
import com.discordsrv.common.logging.NamedLogger;
|
||||
import com.discordsrv.common.player.IPlayer;
|
||||
import com.discordsrv.common.player.event.PlayerConnectedEvent;
|
||||
import com.discordsrv.common.update.github.GitHubCompareResponse;
|
||||
import com.discordsrv.common.update.github.GithubRelease;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class UpdateChecker {
|
||||
|
||||
private static final String USER_DOWNLOAD_URL = "https://discordsrv.com/download";
|
||||
private static final String GITHUB_REPOSITORY = "DiscordSRV/DiscordSRV";
|
||||
private static final String GITHUB_DEV_BRANCH = "master";
|
||||
|
||||
private static final String DOWNLOAD_SERVICE_HOST = "https://download.discordsrv.com";
|
||||
private static final String DOWNLOAD_SERVICE_SNAPSHOT_CHANNEL = "snapshot";
|
||||
private static final String DOWNLOAD_SERVICE_RELEASE_CHANNEL = "release";
|
||||
|
||||
private static final String GITHUB_API_HOST = "https://api.github.com";
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
private final NamedLogger logger;
|
||||
|
||||
private boolean securityFailed = false;
|
||||
private VersionCheck latestCheck;
|
||||
private VersionCheck loggedCheck;
|
||||
|
||||
public UpdateChecker(DiscordSRV discordSRV) {
|
||||
this.discordSRV = discordSRV;
|
||||
this.logger = new NamedLogger(discordSRV, "UPDATES");
|
||||
discordSRV.eventBus().subscribe(this);
|
||||
}
|
||||
|
||||
public boolean isSecurityFailed() {
|
||||
return securityFailed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if enabling is permitted
|
||||
*/
|
||||
public boolean check(boolean logUpToDate) {
|
||||
UpdateConfig config = discordSRV.connectionConfig().update;
|
||||
boolean isSnapshot = discordSRV.version().endsWith("-SNAPSHOT");
|
||||
boolean isSecurity = config.security.enabled;
|
||||
boolean isFirstPartyNotification = config.firstPartyNotification;
|
||||
boolean isNotification = config.notificationEnabled;
|
||||
|
||||
if (!isSecurity && !isNotification) {
|
||||
// Nothing to do
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isFirstPartyNotification || isSecurity) {
|
||||
VersionCheck check = null;
|
||||
try {
|
||||
check = checkFirstParty(isSnapshot);
|
||||
if (check == null && isSecurity) {
|
||||
securityFailed = true;
|
||||
return false;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
List<String> failedThings = new ArrayList<>(2);
|
||||
if (isSecurity) {
|
||||
failedThings.add("perform version security check");
|
||||
}
|
||||
if (isFirstPartyNotification) {
|
||||
failedThings.add("check for updates");
|
||||
}
|
||||
logger.warning("Failed to " + String.join(" and ", failedThings)
|
||||
+ " from the first party API", t);
|
||||
|
||||
if (config.security.force) {
|
||||
logger.error("Security check is required (as configured in " + ConnectionConfig.FILE_NAME + ")"
|
||||
+ ", startup will be cancelled.");
|
||||
securityFailed = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isNotification && isFirstPartyNotification
|
||||
&& check != null && check.status != VersionCheck.Status.UNKNOWN) {
|
||||
latestCheck = check;
|
||||
log(check, logUpToDate);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isNotification && config.github.enabled) {
|
||||
VersionCheck check = null;
|
||||
try {
|
||||
check = checkGitHub(isSnapshot);
|
||||
} catch (Throwable t) {
|
||||
logger.warning("Failed to check for updates from GitHub", t);
|
||||
}
|
||||
|
||||
if (check != null && check.status != VersionCheck.Status.UNKNOWN) {
|
||||
latestCheck = check;
|
||||
log(check, logUpToDate);
|
||||
return true;
|
||||
} else {
|
||||
discordSRV.logger().error("Update check" + (isFirstPartyNotification ? "s" : "")
|
||||
+ " failed: unknown version");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private ResponseBody checkResponse(Request request, Response response, ResponseBody responseBody) throws IOException {
|
||||
if (responseBody == null || !response.isSuccessful()) {
|
||||
String responseString = responseBody == null
|
||||
? "response body is null"
|
||||
: StringUtils.substring(responseBody.string(), 0, 500);
|
||||
throw new MessageException("Request to " + request.url().host() + " failed: " + response.code() + ": " + responseString);
|
||||
}
|
||||
return responseBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code null} for preventing shutdown
|
||||
*/
|
||||
private VersionCheck checkFirstParty(boolean isSnapshot) throws IOException {
|
||||
Request request = new Request.Builder()
|
||||
.url(DOWNLOAD_SERVICE_HOST + "/v2/" + GITHUB_REPOSITORY
|
||||
+ "/" + (isSnapshot ? DOWNLOAD_SERVICE_SNAPSHOT_CHANNEL : DOWNLOAD_SERVICE_RELEASE_CHANNEL)
|
||||
+ "/version-check/" + (isSnapshot ? discordSRV.gitRevision() : discordSRV.version()))
|
||||
.get().build();
|
||||
|
||||
String responseString;
|
||||
try (Response response = discordSRV.httpClient().newCall(request).execute()) {
|
||||
ResponseBody responseBody = checkResponse(request, response, response.body());
|
||||
responseString = responseBody.string();
|
||||
}
|
||||
|
||||
VersionCheck versionCheck = discordSRV.json().readValue(responseString, VersionCheck.class);
|
||||
if (versionCheck == null) {
|
||||
throw new MessageException("Failed to parse " + request.url() + " response body: " + StringUtils.substring(responseString, 0, 500));
|
||||
}
|
||||
|
||||
boolean insecure = versionCheck.insecure;
|
||||
List<String> securityIssues = versionCheck.securityIssues;
|
||||
if (insecure) {
|
||||
logger.error("This version of DiscordSRV is insecure, security issues are listed below, startup will be cancelled.");
|
||||
for (String securityIssue : versionCheck.securityIssues) {
|
||||
logger.error(securityIssue);
|
||||
}
|
||||
|
||||
// Block startup
|
||||
return null;
|
||||
} else if (securityIssues != null && !securityIssues.isEmpty()) {
|
||||
logger.warning("There are security warnings for this version of DiscordSRV, listed below");
|
||||
for (String securityIssue : versionCheck.securityIssues) {
|
||||
logger.warning(securityIssue);
|
||||
}
|
||||
}
|
||||
|
||||
return versionCheck;
|
||||
}
|
||||
|
||||
private VersionCheck checkGitHub(boolean isSnapshot) throws IOException {
|
||||
if (isSnapshot) {
|
||||
Request request = new Request.Builder()
|
||||
.url(GITHUB_API_HOST + "/repos/" + GITHUB_REPOSITORY + "/compare/"
|
||||
+ GITHUB_DEV_BRANCH + "..." + discordSRV.gitRevision() + "?per_page=0")
|
||||
.get().build();
|
||||
|
||||
try (Response response = discordSRV.httpClient().newCall(request).execute()) {
|
||||
ResponseBody responseBody = checkResponse(request, response, response.body());
|
||||
GitHubCompareResponse compare = discordSRV.json().readValue(responseBody.byteStream(), GitHubCompareResponse.class);
|
||||
|
||||
VersionCheck versionCheck = new VersionCheck();
|
||||
versionCheck.amount = compare.behind_by;
|
||||
versionCheck.amountType = (compare.behind_by == 1 ? "commit" : "commits");
|
||||
versionCheck.amountSource = VersionCheck.AmountSource.GITHUB;
|
||||
if ("behind".equals(compare.status)) {
|
||||
versionCheck.status = VersionCheck.Status.OUTDATED;
|
||||
} else if ("identical".equals(compare.status)) {
|
||||
versionCheck.status = VersionCheck.Status.UP_TO_DATE;
|
||||
} else {
|
||||
versionCheck.status = VersionCheck.Status.UNKNOWN;
|
||||
}
|
||||
|
||||
return versionCheck;
|
||||
}
|
||||
}
|
||||
|
||||
String version = discordSRV.version();
|
||||
|
||||
int versionsBehind = 0;
|
||||
boolean found = false;
|
||||
|
||||
int perPage = 100;
|
||||
int page = 0;
|
||||
for (int i = 0; i < 3 /* max 3 loops */; i++) {
|
||||
Request request = new Request.Builder()
|
||||
.url(GITHUB_API_HOST + "/repos/" + GITHUB_REPOSITORY + "/releases?per_page=" + perPage + "&page=" + page)
|
||||
.get().build();
|
||||
|
||||
try (Response response = discordSRV.httpClient().newCall(request).execute()) {
|
||||
ResponseBody responseBody = checkResponse(request, response, response.body());
|
||||
List<GithubRelease> releases = discordSRV.json().readValue(responseBody.byteStream(), new TypeReference<List<GithubRelease>>() {});
|
||||
|
||||
for (GithubRelease release : releases) {
|
||||
if (version.equals(release.tag_name)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
versionsBehind++;
|
||||
}
|
||||
if (found || releases.size() < perPage) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VersionCheck versionCheck = new VersionCheck();
|
||||
versionCheck.amountSource = VersionCheck.AmountSource.GITHUB;
|
||||
versionCheck.amountType = (versionsBehind == 1 ? "release" : "releases");
|
||||
if (!found) {
|
||||
versionCheck.status = VersionCheck.Status.UNKNOWN;
|
||||
versionCheck.amount = -1;
|
||||
} else {
|
||||
versionCheck.status = versionsBehind == 0
|
||||
? VersionCheck.Status.UP_TO_DATE
|
||||
: VersionCheck.Status.OUTDATED;
|
||||
versionCheck.amount = versionsBehind;
|
||||
}
|
||||
return versionCheck;
|
||||
}
|
||||
|
||||
private void log(VersionCheck versionCheck, boolean logUpToDate) {
|
||||
switch (versionCheck.status) {
|
||||
case UP_TO_DATE: {
|
||||
if (logUpToDate) {
|
||||
logger.info("DiscordSRV is up-to-date.");
|
||||
loggedCheck = versionCheck;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OUTDATED: {
|
||||
// only log if there is new information
|
||||
if (loggedCheck == null || loggedCheck.amount != versionCheck.amount) {
|
||||
logger.warning(
|
||||
"DiscordSRV is outdated by " + versionCheck.amount + " " + versionCheck.amountType
|
||||
+ ". Get the latest version from https://discordsrv.com/dowload");
|
||||
loggedCheck = versionCheck;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected version check status: " + versionCheck.status.name());
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPlayerConnected(PlayerConnectedEvent event) {
|
||||
UpdateConfig config = discordSRV.connectionConfig().update;
|
||||
if (!config.notificationEnabled || !config.notificationInGame) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (latestCheck == null || latestCheck.status != VersionCheck.Status.OUTDATED) {
|
||||
return;
|
||||
}
|
||||
|
||||
IPlayer player = event.player();
|
||||
if (!player.hasPermission("discordsrv.updatenotification")) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.sendMessage(
|
||||
Component.text("There is a new version of DiscordSRV available, ", NamedTextColor.AQUA)
|
||||
.append(Component.text()
|
||||
.content("click here to download it")
|
||||
.clickEvent(ClickEvent.openUrl(USER_DOWNLOAD_URL)))
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class VersionCheck {
|
||||
|
||||
public Status status;
|
||||
|
||||
public int amount;
|
||||
public AmountSource amountSource;
|
||||
public String amountType;
|
||||
|
||||
public boolean insecure;
|
||||
public List<String> securityIssues;
|
||||
|
||||
public enum Status {
|
||||
UP_TO_DATE,
|
||||
OUTDATED,
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
public enum AmountSource {
|
||||
GITHUB
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.update.github;
|
||||
|
||||
public class GitHubCompareResponse {
|
||||
|
||||
public String status;
|
||||
public int behind_by;
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.update.github;
|
||||
|
||||
public class GithubRelease {
|
||||
|
||||
public String tag_name;
|
||||
}
|
@ -81,6 +81,11 @@ public final class DiscordSRVTranslation {
|
||||
Config config = (Config) configManager.createConfiguration();
|
||||
String fileIdentifier = config.getFileName();
|
||||
ConfigurationNode commentSection = node.node(fileIdentifier + "_comments");
|
||||
|
||||
String header = configManager.defaultOptions().header();
|
||||
if (header != null) {
|
||||
commentSection.node("$header").set(header);
|
||||
}
|
||||
|
||||
ObjectMapper.Factory mapperFactory = configManager.configObjectMapperBuilder()
|
||||
.addProcessor(Untranslated.class, untranslatedProcessorFactory)
|
||||
|
Loading…
Reference in New Issue
Block a user