mirror of
https://github.com/DiscordSRV/Ascension.git
synced 2024-12-26 17:18:29 +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.DiscordGuild;
|
||||||
import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
|
import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.annotations.Unmodifiable;
|
import org.jetbrains.annotations.Unmodifiable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -43,10 +44,11 @@ import java.util.concurrent.CompletableFuture;
|
|||||||
public interface ReceivedDiscordMessage extends Snowflake {
|
public interface ReceivedDiscordMessage extends Snowflake {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the content of this message.
|
* Gets the content of this message. This will return {@code null} if the bot doesn't have the MESSAGE_CONTENT intent,
|
||||||
* @return the message content
|
* 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();
|
String getContent();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,6 +33,7 @@ import com.discordsrv.common.channel.GlobalChannelLookupModule;
|
|||||||
import com.discordsrv.common.command.game.GameCommandModule;
|
import com.discordsrv.common.command.game.GameCommandModule;
|
||||||
import com.discordsrv.common.component.ComponentFactory;
|
import com.discordsrv.common.component.ComponentFactory;
|
||||||
import com.discordsrv.common.config.connection.ConnectionConfig;
|
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.LinkedAccountConfig;
|
||||||
import com.discordsrv.common.config.main.MainConfig;
|
import com.discordsrv.common.config.main.MainConfig;
|
||||||
import com.discordsrv.common.config.manager.ConnectionConfigManager;
|
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.profile.ProfileManager;
|
||||||
import com.discordsrv.common.storage.Storage;
|
import com.discordsrv.common.storage.Storage;
|
||||||
import com.discordsrv.common.storage.StorageType;
|
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 com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import net.dv8tion.jda.api.JDA;
|
import net.dv8tion.jda.api.JDA;
|
||||||
import net.dv8tion.jda.api.JDAInfo;
|
import net.dv8tion.jda.api.JDAInfo;
|
||||||
@ -133,12 +136,15 @@ public abstract class AbstractDiscordSRV<B extends IBootstrap, C extends MainCon
|
|||||||
private LinkProvider linkProvider;
|
private LinkProvider linkProvider;
|
||||||
|
|
||||||
// Version
|
// Version
|
||||||
|
private UpdateChecker updateChecker;
|
||||||
private String version;
|
private String version;
|
||||||
private String gitRevision;
|
private String gitRevision;
|
||||||
private String gitBranch;
|
private String gitBranch;
|
||||||
|
|
||||||
private OkHttpClient httpClient;
|
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
|
// Internal
|
||||||
private final ReentrantLock lifecycleLock = new ReentrantLock();
|
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.discordConnectionDetails = new DiscordConnectionDetailsImpl(this);
|
||||||
this.discordConnectionManager = new JDAConnectionManager(this);
|
this.discordConnectionManager = new JDAConnectionManager(this);
|
||||||
this.channelConfig = new ChannelConfigHelper(this);
|
this.channelConfig = new ChannelConfigHelper(this);
|
||||||
|
this.updateChecker = new UpdateChecker(this);
|
||||||
readManifest();
|
readManifest();
|
||||||
|
|
||||||
Dispatcher dispatcher = new Dispatcher();
|
Dispatcher dispatcher = new Dispatcher();
|
||||||
@ -587,6 +594,27 @@ public abstract class AbstractDiscordSRV<B extends IBootstrap, C extends MainCon
|
|||||||
channelConfig().reload();
|
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)) {
|
if (flags.contains(ReloadFlag.LINKED_ACCOUNT_PROVIDER)) {
|
||||||
LinkedAccountConfig linkedAccountConfig = config().linkedAccounts;
|
LinkedAccountConfig linkedAccountConfig = config().linkedAccounts;
|
||||||
if (linkedAccountConfig != null && linkedAccountConfig.enabled) {
|
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 com.discordsrv.common.config.Config;
|
||||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
|
||||||
|
|
||||||
@ConfigSerializable
|
@ConfigSerializable
|
||||||
public class ConnectionConfig implements Config {
|
public class ConnectionConfig implements Config {
|
||||||
@ -32,15 +31,9 @@ public class ConnectionConfig implements Config {
|
|||||||
return FILE_NAME;
|
return FILE_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bot bot = new Bot();
|
public BotConfig bot = new BotConfig();
|
||||||
|
|
||||||
public StorageConfig storage = new StorageConfig();
|
public StorageConfig storage = new StorageConfig();
|
||||||
|
|
||||||
@ConfigSerializable
|
public UpdateConfig update = new UpdateConfig();
|
||||||
public static class Bot {
|
|
||||||
|
|
||||||
@Comment("Don't know what this is? Neither do I")
|
|
||||||
public String token = "Token here";
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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.connection.ConnectionConfig;
|
||||||
import com.discordsrv.common.config.manager.loader.YamlConfigLoaderProvider;
|
import com.discordsrv.common.config.manager.loader.YamlConfigLoaderProvider;
|
||||||
import com.discordsrv.common.config.manager.manager.TranslatedConfigManager;
|
import com.discordsrv.common.config.manager.manager.TranslatedConfigManager;
|
||||||
|
import org.spongepowered.configurate.ConfigurationOptions;
|
||||||
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
||||||
|
|
||||||
public abstract class ConnectionConfigManager<C extends ConnectionConfig>
|
public abstract class ConnectionConfigManager<C extends ConnectionConfig>
|
||||||
@ -32,6 +33,18 @@ public abstract class ConnectionConfigManager<C extends ConnectionConfig>
|
|||||||
super(discordSRV);
|
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
|
@Override
|
||||||
protected String fileName() {
|
protected String fileName() {
|
||||||
return ConnectionConfig.FILE_NAME;
|
return ConnectionConfig.FILE_NAME;
|
||||||
|
@ -25,6 +25,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import org.spongepowered.configurate.CommentedConfigurationNode;
|
import org.spongepowered.configurate.CommentedConfigurationNode;
|
||||||
import org.spongepowered.configurate.ConfigurateException;
|
import org.spongepowered.configurate.ConfigurateException;
|
||||||
import org.spongepowered.configurate.ConfigurationNode;
|
import org.spongepowered.configurate.ConfigurationNode;
|
||||||
|
import org.spongepowered.configurate.ConfigurationOptions;
|
||||||
import org.spongepowered.configurate.loader.AbstractConfigurationLoader;
|
import org.spongepowered.configurate.loader.AbstractConfigurationLoader;
|
||||||
import org.spongepowered.configurate.serialize.SerializationException;
|
import org.spongepowered.configurate.serialize.SerializationException;
|
||||||
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
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>>
|
public abstract class TranslatedConfigManager<T extends Config, LT extends AbstractConfigurationLoader<CommentedConfigurationNode>>
|
||||||
extends ConfigurateConfigManager<T, LT> {
|
extends ConfigurateConfigManager<T, LT> {
|
||||||
|
|
||||||
|
private String header;
|
||||||
|
|
||||||
public TranslatedConfigManager(DiscordSRV discordSRV) {
|
public TranslatedConfigManager(DiscordSRV discordSRV) {
|
||||||
super(discordSRV);
|
super(discordSRV);
|
||||||
}
|
}
|
||||||
@ -48,6 +51,15 @@ public abstract class TranslatedConfigManager<T extends Config, LT extends Abstr
|
|||||||
super.save();
|
super.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigurationOptions defaultOptions() {
|
||||||
|
ConfigurationOptions options = super.defaultOptions();
|
||||||
|
if (header != null) {
|
||||||
|
options = options.header(header);
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected @Nullable ConfigurationNode getTranslation() throws ConfigurateException {
|
protected @Nullable ConfigurationNode getTranslation() throws ConfigurateException {
|
||||||
ConfigurationNode translation = getTranslationRoot();
|
ConfigurationNode translation = getTranslationRoot();
|
||||||
@ -77,6 +89,8 @@ public abstract class TranslatedConfigManager<T extends Config, LT extends Abstr
|
|||||||
ConfigurationNode comments = translationRoot.node(fileIdentifier + "_comments");
|
ConfigurationNode comments = translationRoot.node(fileIdentifier + "_comments");
|
||||||
|
|
||||||
CommentedConfigurationNode node = loader().createNode();
|
CommentedConfigurationNode node = loader().createNode();
|
||||||
|
this.header = comments.node("$header").getString();
|
||||||
|
|
||||||
save(config, (Class<T>) config.getClass(), node);
|
save(config, (Class<T>) config.getClass(), node);
|
||||||
translateNode(node, translation, comments);
|
translateNode(node, translation, comments);
|
||||||
} catch (ConfigurateException e) {
|
} 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.event.events.placeholder.PlaceholderLookupEvent;
|
||||||
import com.discordsrv.api.placeholder.PlaceholderLookupResult;
|
import com.discordsrv.api.placeholder.PlaceholderLookupResult;
|
||||||
import com.discordsrv.common.DiscordSRV;
|
import com.discordsrv.common.DiscordSRV;
|
||||||
|
import com.discordsrv.common.config.connection.BotConfig;
|
||||||
import com.discordsrv.common.config.connection.ConnectionConfig;
|
import com.discordsrv.common.config.connection.ConnectionConfig;
|
||||||
import com.discordsrv.common.discord.api.DiscordAPIImpl;
|
import com.discordsrv.common.discord.api.DiscordAPIImpl;
|
||||||
import com.discordsrv.common.discord.api.entity.message.ReceivedDiscordMessageImpl;
|
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
|
// Disable all mentions by default for safety
|
||||||
MessageRequest.setDefaultMentions(Collections.emptyList());
|
MessageRequest.setDefaultMentions(Collections.emptyList());
|
||||||
|
|
||||||
|
// Disable this warning (that doesn't even have a stacktrace)
|
||||||
|
Message.suppressContentIntentWarning();
|
||||||
|
|
||||||
discordSRV.eventBus().subscribe(this);
|
discordSRV.eventBus().subscribe(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,7 +251,7 @@ public class JDAConnectionManager implements DiscordConnectionManager {
|
|||||||
TimeUnit.SECONDS
|
TimeUnit.SECONDS
|
||||||
);
|
);
|
||||||
|
|
||||||
ConnectionConfig.Bot botConfig = discordSRV.connectionConfig().bot;
|
BotConfig botConfig = discordSRV.connectionConfig().bot;
|
||||||
DiscordConnectionDetails connectionDetails = discordSRV.discordConnectionDetails();
|
DiscordConnectionDetails connectionDetails = discordSRV.discordConnectionDetails();
|
||||||
Set<GatewayIntent> intents = connectionDetails.getGatewayIntents();
|
Set<GatewayIntent> intents = connectionDetails.getGatewayIntents();
|
||||||
boolean membersIntent = intents.contains(GatewayIntent.GUILD_MEMBERS);
|
boolean membersIntent = intents.contains(GatewayIntent.GUILD_MEMBERS);
|
||||||
|
@ -100,6 +100,12 @@ public class DiscordSRVLogger implements Logger {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void log(@Nullable String loggerName, @NotNull LogLevel logLevel, @Nullable String message, @Nullable Throwable throwable) {
|
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) {
|
if (throwable instanceof InsufficientPermissionException) {
|
||||||
Permission permission = ((InsufficientPermissionException) throwable).getPermission();
|
Permission permission = ((InsufficientPermissionException) throwable).getPermission();
|
||||||
String msg = "The bot is missing the \"" + permission.getName() + "\" permission";
|
String msg = "The bot is missing the \"" + permission.getName() + "\" permission";
|
||||||
|
@ -83,7 +83,7 @@ public interface Scheduler {
|
|||||||
ScheduledFuture<?> runLater(Runnable task, long timeMillis);
|
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 task the task
|
||||||
* @param rate the rate in the given unit
|
* @param rate the rate in the given unit
|
||||||
@ -91,7 +91,7 @@ public interface Scheduler {
|
|||||||
*/
|
*/
|
||||||
@ApiStatus.NonExtendable
|
@ApiStatus.NonExtendable
|
||||||
default ScheduledFuture<?> runAtFixedRate(@NotNull Runnable task, long rate, @NotNull TimeUnit unit) {
|
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;
|
||||||
|
}
|
@ -82,6 +82,11 @@ public final class DiscordSRVTranslation {
|
|||||||
String fileIdentifier = config.getFileName();
|
String fileIdentifier = config.getFileName();
|
||||||
ConfigurationNode commentSection = node.node(fileIdentifier + "_comments");
|
ConfigurationNode commentSection = node.node(fileIdentifier + "_comments");
|
||||||
|
|
||||||
|
String header = configManager.defaultOptions().header();
|
||||||
|
if (header != null) {
|
||||||
|
commentSection.node("$header").set(header);
|
||||||
|
}
|
||||||
|
|
||||||
ObjectMapper.Factory mapperFactory = configManager.configObjectMapperBuilder()
|
ObjectMapper.Factory mapperFactory = configManager.configObjectMapperBuilder()
|
||||||
.addProcessor(Untranslated.class, untranslatedProcessorFactory)
|
.addProcessor(Untranslated.class, untranslatedProcessorFactory)
|
||||||
.build();
|
.build();
|
||||||
|
Loading…
Reference in New Issue
Block a user