diff --git a/src/ARKServerManager/ARKServerManager.csproj b/src/ARKServerManager/ARKServerManager.csproj
index d18ce262..f7baf7b7 100644
--- a/src/ARKServerManager/ARKServerManager.csproj
+++ b/src/ARKServerManager/ARKServerManager.csproj
@@ -173,6 +173,7 @@
MSBuild:Compile
Designer
+
@@ -196,13 +197,15 @@
-
+
+
+
AddUserWindow.xaml
diff --git a/src/ARKServerManager/App.config b/src/ARKServerManager/App.config
index 605e2a65..446c2530 100644
--- a/src/ARKServerManager/App.config
+++ b/src/ARKServerManager/App.config
@@ -347,6 +347,12 @@
asmdata
+
+ https://discord.com/developers/applications
+
+
+ https://arkservermanager.freeforums.net/thread/8764/get-own-discord-bot
+
@@ -804,6 +810,39 @@
50
+
+ False
+
+
+ asm
+
+
+
+
+
+
+
+
+ False
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
diff --git a/src/ARKServerManager/App.xaml.cs b/src/ARKServerManager/App.xaml.cs
index 52716ef4..321e4a93 100644
--- a/src/ARKServerManager/App.xaml.cs
+++ b/src/ARKServerManager/App.xaml.cs
@@ -1,16 +1,16 @@
using ArkData;
-using Microsoft.WindowsAPICodePack.Dialogs;
using NLog;
using NLog.Config;
using NLog.Targets;
using ServerManagerTool.Common;
using ServerManagerTool.Common.Utils;
+using ServerManagerTool.DiscordBot;
using ServerManagerTool.Enums;
using ServerManagerTool.Lib;
using ServerManagerTool.Plugin.Common;
+using ServerManagerTool.Utils;
using ServerManagerTool.Windows;
using System;
-using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
@@ -21,7 +21,6 @@ using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
-using System.Xml;
using WPFSharp.Globalizer;
namespace ServerManagerTool
@@ -39,6 +38,7 @@ namespace ServerManagerTool
public event PropertyChangedEventHandler PropertyChanged;
+ private CancellationTokenSource _tokenSource;
private GlobalizedApplication _globalizer;
private bool _applicationStarted;
private string _args;
@@ -177,11 +177,6 @@ namespace ServerManagerTool
}
}
- private IList FetchProfiles()
- {
- return ServerManager.Instance.Servers.Select(s => new ServerManagerTool.Plugin.Common.Lib.Profile() { ProfileName = s?.Profile?.ProfileName ?? string.Empty, InstallationFolder = s?.Profile?.InstallDirectory ?? string.Empty }).ToList();
- }
-
public static string GetLogFolder() => IOUtils.NormalizePath(Path.Combine(Config.Default.DataDir, Config.Default.LogsDir));
public static string GetProfileLogFolder(string profileId) => IOUtils.NormalizePath(Path.Combine(Config.Default.DataDir, Config.Default.LogsDir, profileId.ToLower()));
@@ -312,7 +307,7 @@ namespace ServerManagerTool
var installPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
PluginHelper.Instance.BetaEnabled = this.BetaVersion;
PluginHelper.Instance.LoadPlugins(installPath, true);
- PluginHelper.Instance.SetFetchProfileCallback(FetchProfiles);
+ PluginHelper.Instance.SetFetchProfileCallback(DiscordPluginHelper.FetchProfiles);
OnResourceDictionaryChanged(Thread.CurrentThread.CurrentCulture.Name);
// check if we are starting ASM for the old server restart - no longer supported
@@ -412,6 +407,7 @@ namespace ServerManagerTool
ApplicationStarted = true;
+ var restartRequired = false;
if (string.IsNullOrWhiteSpace(Config.Default.DataDir))
{
var dataDirectoryWindow = new DataDirectoryWindow();
@@ -422,6 +418,8 @@ namespace ServerManagerTool
{
Environment.Exit(0);
}
+
+ restartRequired = true;
}
Config.Default.ConfigDirectory = Path.Combine(Config.Default.DataDir, Config.Default.ProfilesDir);
@@ -429,6 +427,11 @@ namespace ServerManagerTool
Config.Default.Save();
CommonConfig.Default.Save();
+ if (restartRequired)
+ {
+ Environment.Exit(0);
+ }
+
DataFileDetails.PlayerFileExtension = Config.Default.PlayerFileExtension;
DataFileDetails.TribeFileExtension = Config.Default.TribeFileExtension;
@@ -446,6 +449,25 @@ namespace ServerManagerTool
StartupUri = new Uri("Windows/AutoUpdateWindow.xaml", UriKind.RelativeOrAbsolute);
}
+
+ if (Config.Default.DiscordBotEnabled)
+ {
+ _tokenSource = new CancellationTokenSource();
+
+ Task discordTask = Task.Run(async () =>
+ {
+ await ServerManagerBotFactory.GetServerManagerBot()?.StartAsync(Config.Default.DiscordBotToken, Config.Default.DiscordBotPrefix, Config.Default.DataDir, DiscordBotHelper.HandleDiscordCommand, DiscordBotHelper.HandleTranslation, _tokenSource.Token);
+ }, _tokenSource.Token)
+ .ContinueWith(t => {
+ var message = t.Exception.InnerException is null ? t.Exception.Message : t.Exception.InnerException.Message;
+ if (message.StartsWith("#"))
+ {
+ message = _globalizer.GetResourceString(message.Substring(1)) ?? message.Substring(1);
+ }
+
+ MessageBox.Show(message, _globalizer.GetResourceString("DiscordBot_ErrorTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
+ }, TaskContinuationOptions.OnlyOnFaulted);
+ }
}
protected override void OnExit(ExitEventArgs e)
@@ -486,6 +508,12 @@ namespace ServerManagerTool
private void ShutDownApplication()
{
+ if (!(_tokenSource is null))
+ {
+ _tokenSource.Cancel();
+ _tokenSource.Dispose();
+ }
+
if (ApplicationStarted)
{
foreach (var server in ServerManager.Instance.Servers)
diff --git a/src/ARKServerManager/Art/favicon.ico b/src/ARKServerManager/Art/favicon.ico
index 2ed732cb..9f1a5760 100644
Binary files a/src/ARKServerManager/Art/favicon.ico and b/src/ARKServerManager/Art/favicon.ico differ
diff --git a/src/ARKServerManager/Config.Designer.cs b/src/ARKServerManager/Config.Designer.cs
index dbac3d1f..7eed5ed1 100644
--- a/src/ARKServerManager/Config.Designer.cs
+++ b/src/ARKServerManager/Config.Designer.cs
@@ -2812,5 +2812,155 @@ namespace ServerManagerTool {
return ((string)(this["DefaultDataDirectoryName"]));
}
}
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool DiscordBotEnabled {
+ get {
+ return ((bool)(this["DiscordBotEnabled"]));
+ }
+ set {
+ this["DiscordBotEnabled"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("asm")]
+ public string DiscordBotPrefix {
+ get {
+ return ((string)(this["DiscordBotPrefix"]));
+ }
+ set {
+ this["DiscordBotPrefix"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string DiscordBotToken {
+ get {
+ return ((string)(this["DiscordBotToken"]));
+ }
+ set {
+ this["DiscordBotToken"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string DiscordBotServerId {
+ get {
+ return ((string)(this["DiscordBotServerId"]));
+ }
+ set {
+ this["DiscordBotServerId"] = value;
+ }
+ }
+
+ [global::System.Configuration.ApplicationScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("https://discord.com/developers/applications")]
+ public string DiscordBotApplyUrl {
+ get {
+ return ((string)(this["DiscordBotApplyUrl"]));
+ }
+ }
+
+ [global::System.Configuration.ApplicationScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("https://arkservermanager.freeforums.net/thread/8764/get-own-discord-bot")]
+ public string DiscordBotHelpUrl {
+ get {
+ return ((string)(this["DiscordBotHelpUrl"]));
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool SectionDiscordBotIsExpanded {
+ get {
+ return ((bool)(this["SectionDiscordBotIsExpanded"]));
+ }
+ set {
+ this["SectionDiscordBotIsExpanded"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("True")]
+ public bool AllowDiscordBackup {
+ get {
+ return ((bool)(this["AllowDiscordBackup"]));
+ }
+ set {
+ this["AllowDiscordBackup"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("True")]
+ public bool AllowDiscordUpdate {
+ get {
+ return ((bool)(this["AllowDiscordUpdate"]));
+ }
+ set {
+ this["AllowDiscordUpdate"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("True")]
+ public bool AllowDiscordStart {
+ get {
+ return ((bool)(this["AllowDiscordStart"]));
+ }
+ set {
+ this["AllowDiscordStart"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("True")]
+ public bool AllowDiscordRestart {
+ get {
+ return ((bool)(this["AllowDiscordRestart"]));
+ }
+ set {
+ this["AllowDiscordRestart"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("True")]
+ public bool AllowDiscordShutdown {
+ get {
+ return ((bool)(this["AllowDiscordShutdown"]));
+ }
+ set {
+ this["AllowDiscordShutdown"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("True")]
+ public bool AllowDiscordStop {
+ get {
+ return ((bool)(this["AllowDiscordStop"]));
+ }
+ set {
+ this["AllowDiscordStop"] = value;
+ }
+ }
}
}
diff --git a/src/ARKServerManager/Config.settings b/src/ARKServerManager/Config.settings
index 72345566..9ee1d712 100644
--- a/src/ARKServerManager/Config.settings
+++ b/src/ARKServerManager/Config.settings
@@ -779,5 +779,44 @@
asmdata
+
+ False
+
+
+ asm
+
+
+
+
+
+
+
+
+ https://discord.com/developers/applications
+
+
+ https://arkservermanager.freeforums.net/thread/8764/get-own-discord-bot
+
+
+ False
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
\ No newline at end of file
diff --git a/src/ARKServerManager/Delegates/ServerStatusChangeDelegate.cs b/src/ARKServerManager/Delegates/ServerStatusChangeDelegate.cs
new file mode 100644
index 00000000..c7ab834a
--- /dev/null
+++ b/src/ARKServerManager/Delegates/ServerStatusChangeDelegate.cs
@@ -0,0 +1,6 @@
+using ServerManagerTool.Enums;
+
+namespace ServerManagerTool.Delegates
+{
+ public delegate void ServerStatusChangeDelegate(ServerStatus serverStatus);
+}
diff --git a/src/ARKServerManager/Enums/AvailabilityStatus.cs b/src/ARKServerManager/Enums/AvailabilityStatus.cs
index f2588330..81846709 100644
--- a/src/ARKServerManager/Enums/AvailabilityStatus.cs
+++ b/src/ARKServerManager/Enums/AvailabilityStatus.cs
@@ -3,9 +3,9 @@
public enum AvailabilityStatus
{
Unknown,
- NeedPublicIP,
+ SetPublicIP,
Unavailable,
- WaitingForPublication,
+ Waiting,
Available
}
}
diff --git a/src/ARKServerManager/Enums/ServerProcessType.cs b/src/ARKServerManager/Enums/ServerProcessType.cs
index 861e2081..7298dfd9 100644
--- a/src/ARKServerManager/Enums/ServerProcessType.cs
+++ b/src/ARKServerManager/Enums/ServerProcessType.cs
@@ -10,5 +10,6 @@
Backup,
Shutdown,
Restart,
+ Update,
}
}
diff --git a/src/ARKServerManager/Globalization/en-US/en-US.xaml b/src/ARKServerManager/Globalization/en-US/en-US.xaml
index 49ab73d1..ff889d9f 100644
--- a/src/ARKServerManager/Globalization/en-US/en-US.xaml
+++ b/src/ARKServerManager/Globalization/en-US/en-US.xaml
@@ -610,7 +610,24 @@
This message will be displayed when the server shutdown has been cancelled.
Show shutdown reason with ALL shutdown messages
If enabled, the shutdown reason will be shown with all shutdown message; otherwise it will only be shown at the start of the server shutdown.
-
+
+ Enable Discord Bot
+ You will need to restart the server manager if you change any settings for the Discord Bot.
+ Token:
+ The token associated with the discord bot.
+ Server Id:
+ The id of the discord server the bot will listen to.
+ Prefix:
+ The prefix that must be used when sending a command via discord.
+ Get Token...
+ Help...
+ If enabled, the backup command can be sent from discord.
+ If enabled, the restart command can be sent from discord.
+ If enabled, the shutdown command can be sent from discord.
+ If enabled, the start command can be sent from discord.
+ If enabled, the stop command can be sent from discord.
+ If enabled, the update command can be sent from discord.
+
SMTP Email Settings
Host:
The name or IP address of the host used for SMTP transmissions.
@@ -775,6 +792,9 @@
Reinstall SteamCMD Error
An error occured while trying to reinstall SteamCMD. This has left SteamCmd in an unstable state, try reinstalling again or please report this.\r\nException: {0}
+ Discord Bot Running Commands
+ The discord bot has one or more running commands, do you want to continue shutting down the server manager?
+
Start Server Confirmation
You are about to start the server, do you want to continue?
Shutdown Server Confirmation
@@ -1179,6 +1199,24 @@
If enabled, the server will be restarted even if shutdown for Auto-Restarts and Auto-Updates.
+
+ Discord Bot Details
+ Channel Id:
+ The id of the discord server channel this profile will listen to.
+ Allow Backup
+ If enabled, the profile will listen for backup commands from discord.
+ Allow Restart
+ If enabled, the profile will listen for restart commands from discord.
+ Allow Shutdown
+ If enabled, the profile will listen for shutdown commands from discord.
+ Allow Start
+ If enabled, the profile will listen for start commands from discord.
+ Allow Stop
+ If enabled, the profile will listen for stop commands from discord.
+ Allow Update
+ If enabled, the profile will listen for update commands from discord.
+
+
Rules
Enable Hardcore Mode
@@ -5523,4 +5561,32 @@
There was a problem while performing the server update. This may leave your server in a incomplete state.\r\n\r\nDo you want to continue with the server start, this could cause problems?
+
+ Discord Bot Error
+ The discord bot requires a valid token so it can log into the discord server\r\nThis can be set in the global settings.
+ The discord bot prefix contains invalid characters. Only letters and numbers are allowed.
+
+ Command '{0}' has not been enabled.
+ Unknown command '{0}'.
+ Another command is currently being processed.
+ Another command '{0}' is currently running against profile '{1}'.
+ Command '{0}' has been disabled for profile '{1}'.
+
+ The '{0}' command requires a profile id.
+ Profile '{0}' was not found or is not associated with the channel.
+ Profile '{0}' is in a state '{1}' that cannot run this command.
+ Profile '{0}' is currently being updated.
+
+ Call to server '{0}' failed.
+ A backup request for server '{0}' has been sent.
+ A restart request for server '{0}' has been sent.
+ A shutdown request for server '{0}' has been sent.
+ A start request for server '{0}' has been sent.
+ A stop request for server '{0}' has been sent.
+ An update request for server '{0}' has been sent.
+
+ Count:
+ Map:
+
+
\ No newline at end of file
diff --git a/src/ARKServerManager/Lib/ServerBranchSnapshot.cs b/src/ARKServerManager/Lib/BranchSnapshot.cs
similarity index 52%
rename from src/ARKServerManager/Lib/ServerBranchSnapshot.cs
rename to src/ARKServerManager/Lib/BranchSnapshot.cs
index b9f34073..c3b0283d 100644
--- a/src/ARKServerManager/Lib/ServerBranchSnapshot.cs
+++ b/src/ARKServerManager/Lib/BranchSnapshot.cs
@@ -3,15 +3,37 @@ using System.Collections.Generic;
namespace ServerManagerTool.Lib
{
- public class ServerBranchSnapshot
+ public class BranchSnapshot
{
+ private BranchSnapshot()
+ {
+ }
+
public string BranchName = string.Empty;
public string BranchPassword = string.Empty;
+
+ public static BranchSnapshot Create(ServerProfile profile)
+ {
+ return new BranchSnapshot
+ {
+ BranchName = profile.BranchName,
+ BranchPassword = profile.BranchPassword
+ };
+ }
+
+ public static BranchSnapshot Create(ServerProfileSnapshot profile)
+ {
+ return new BranchSnapshot
+ {
+ BranchName = profile.BranchName,
+ BranchPassword = profile.BranchPassword
+ };
+ }
}
- public class ServerBranchSnapshotComparer : IEqualityComparer
+ public class BranchSnapshotComparer : IEqualityComparer
{
- public bool Equals(ServerBranchSnapshot x, ServerBranchSnapshot y)
+ public bool Equals(BranchSnapshot x, BranchSnapshot y)
{
//Check whether the compared objects reference the same data.
if (Object.ReferenceEquals(x, y)) return true;
@@ -24,7 +46,7 @@ namespace ServerManagerTool.Lib
return x.BranchName == y.BranchName;
}
- public int GetHashCode(ServerBranchSnapshot snapshot)
+ public int GetHashCode(BranchSnapshot snapshot)
{
//Check whether the object is null
if (snapshot is null) return 0;
diff --git a/src/ARKServerManager/Lib/Server.cs b/src/ARKServerManager/Lib/Server.cs
index bb934d88..06a7fcf9 100644
--- a/src/ARKServerManager/Lib/Server.cs
+++ b/src/ARKServerManager/Lib/Server.cs
@@ -94,7 +94,7 @@ namespace ServerManagerTool.Lib
await this.Runtime.StopAsync();
}
- public async Task UpgradeAsync(CancellationToken cancellationToken, bool updateServer, ServerBranchSnapshot branch, bool validate, bool updateMods, ProgressDelegate progressCallback)
+ public async Task UpgradeAsync(CancellationToken cancellationToken, bool updateServer, BranchSnapshot branch, bool validate, bool updateMods, ProgressDelegate progressCallback)
{
await this.Runtime.AttachToProfile(this.Profile);
var success = await this.Runtime.UpgradeAsync(cancellationToken, updateServer, branch, validate, updateMods, progressCallback);
diff --git a/src/ARKServerManager/Lib/ServerApp.cs b/src/ARKServerManager/Lib/ServerApp.cs
index 718376d3..0f433ad8 100644
--- a/src/ARKServerManager/Lib/ServerApp.cs
+++ b/src/ARKServerManager/Lib/ServerApp.cs
@@ -2,6 +2,7 @@
using ServerManagerTool.Common.Lib;
using ServerManagerTool.Common.Model;
using ServerManagerTool.Common.Utils;
+using ServerManagerTool.Delegates;
using ServerManagerTool.Enums;
using ServerManagerTool.Plugin.Common;
using ServerManagerTool.Utils;
@@ -105,6 +106,7 @@ namespace ServerManagerTool.Lib
public int ShutdownInterval = Config.Default.ServerShutdown_GracePeriod;
public ProgressDelegate ProgressCallback = null;
public ProcessWindowStyle SteamCMDProcessWindowStyle = ProcessWindowStyle.Minimized;
+ public ServerStatusChangeDelegate ServerStatusChangeCallback = null;
public ServerApp(bool resetStartTime = false)
{
@@ -112,7 +114,7 @@ namespace ServerManagerTool.Lib
_startTime = DateTime.Now;
}
- private void BackupServer()
+ private void BackupServer(CancellationToken cancellationToken)
{
if (_profile == null || _profile.SotFEnabled)
{
@@ -140,65 +142,65 @@ namespace ServerManagerTool.Lib
if (_serverRunning)
{
- // check if RCON is enabled
- if (_profile.RCONEnabled)
+ try
{
- try
+ emailMessage.AppendLine();
+
+ var sent = false;
+
+ // perform a world save
+ if (!string.IsNullOrWhiteSpace(Config.Default.ServerBackup_WorldSaveMessage))
{
- try
+ ProcessAlert(AlertType.Backup, Config.Default.ServerBackup_WorldSaveMessage);
+ sent = SendMessageAsync(Config.Default.ServerBackup_WorldSaveMessage, cancellationToken).Result;
+ if (sent)
{
- emailMessage.AppendLine();
-
- var sent = false;
-
- // perform a world save
- if (!string.IsNullOrWhiteSpace(Config.Default.ServerBackup_WorldSaveMessage))
- {
- ProcessAlert(AlertType.Backup, Config.Default.ServerBackup_WorldSaveMessage);
- sent = SendMessageAsync(Config.Default.ServerBackup_WorldSaveMessage, CancellationToken.None).Result;
- if (sent)
- {
- emailMessage.AppendLine("sent server save message.");
- }
- }
-
- sent = SendCommandAsync(Config.Default.ServerSaveCommand, false).Result;
- if (sent)
- {
- emailMessage.AppendLine("sent server save command.");
- Task.Delay(Config.Default.ServerShutdown_WorldSaveDelay * 1000).Wait();
- }
- }
- catch (Exception ex)
- {
- Debug.WriteLine($"RCON> {Config.Default.ServerSaveCommand} command.\r\n{ex.Message}");
+ emailMessage.AppendLine("sent server save message.");
}
}
- finally
+
+ sent = SendCommandAsync(Config.Default.ServerSaveCommand, false).Result;
+ if (sent)
{
- CloseRconConsole();
+ emailMessage.AppendLine("sent server save command.");
+ Task.Delay(Config.Default.ServerShutdown_WorldSaveDelay * 1000).Wait();
}
}
- else
+ catch (Exception ex)
{
- LogProfileMessage("RCON not enabled.");
+ Debug.WriteLine($"RCON> {Config.Default.ServerSaveCommand} command.\r\n{ex.Message}");
}
}
if (ExitCode != EXITCODE_NORMALEXIT)
return;
+ if (cancellationToken.IsCancellationRequested)
+ {
+ ExitCode = EXITCODE_CANCELLED;
+ return;
+ }
// make a backup of the current profile and config files.
CreateProfileBackupArchiveFile(_profile);
if (ExitCode != EXITCODE_NORMALEXIT)
return;
+ if (cancellationToken.IsCancellationRequested)
+ {
+ ExitCode = EXITCODE_CANCELLED;
+ return;
+ }
// make a backup of the current world file.
CreateServerBackupArchiveFile(emailMessage, _profile);
if (ExitCode != EXITCODE_NORMALEXIT)
return;
+ if (cancellationToken.IsCancellationRequested)
+ {
+ ExitCode = EXITCODE_CANCELLED;
+ return;
+ }
if (Config.Default.EmailNotify_AutoBackup)
{
@@ -264,7 +266,15 @@ namespace ServerManagerTool.Lib
if (updateServer)
{
- UpgradeLocal(true, cancellationToken, true);
+ try
+ {
+ ServerStatusChangeCallback?.Invoke(ServerStatus.Updating);
+ UpgradeLocal(true, true, cancellationToken);
+ }
+ finally
+ {
+ ServerStatusChangeCallback?.Invoke(ServerStatus.Stopped);
+ }
}
if (ExitCode != EXITCODE_NORMALEXIT)
@@ -300,17 +310,20 @@ namespace ServerManagerTool.Lib
return;
}
- // check if the server was previously running before the update.
- if (!_serverRunning && !_profile.AutoRestartIfShutdown)
+ // check if the server was previously running.
+ if (!_serverRunning)
{
- LogProfileMessage("Server was not running, server will not be started.");
+ if (_profile.AutoRestartIfShutdown)
+ {
+ LogProfileMessage("Server was not running, server will be started as the setting to restart if shutdown is TRUE.");
+ }
+ else
+ {
+ LogProfileMessage("Server was not running, server will not be started.");
- ExitCode = EXITCODE_NORMALEXIT;
- return;
- }
- if (!_serverRunning && _profile.AutoRestartIfShutdown)
- {
- LogProfileMessage("Server was not running, server will be started as the setting to restart if shutdown is TRUE.");
+ ExitCode = EXITCODE_NORMALEXIT;
+ return;
+ }
}
// Find the server process.
@@ -394,7 +407,7 @@ namespace ServerManagerTool.Lib
try
{
// create a connection to the server
- var endPoint = new IPEndPoint(IPAddress.Parse(_profile.ServerIP), _profile.QueryPort);
+ var endPoint = new IPEndPoint(_profile.ServerIPAddress, _profile.QueryPort);
gameServer = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endPoint);
// check if there is a shutdown reason
@@ -407,10 +420,6 @@ namespace ServerManagerTool.Lib
}
LogProfileMessage("Starting shutdown timer...");
- if (!CheckForOnlinePlayers)
- {
- LogProfileMessage("CheckForOnlinePlayers disabled, shutdown timer will not perform online player check.");
- }
var minutesLeft = ShutdownInterval;
while (minutesLeft > 0)
@@ -422,7 +431,7 @@ namespace ServerManagerTool.Lib
if (!string.IsNullOrWhiteSpace(Config.Default.ServerShutdown_CancelMessage))
{
ProcessAlert(AlertType.Shutdown, Config.Default.ServerShutdown_CancelMessage);
- SendMessageAsync(Config.Default.ServerShutdown_CancelMessage, CancellationToken.None).Wait();
+ SendMessageAsync(Config.Default.ServerShutdown_CancelMessage, cancellationToken).Wait();
}
ExitCode = EXITCODE_CANCELLED;
@@ -452,7 +461,8 @@ namespace ServerManagerTool.Lib
}
else
{
- Debug.WriteLine($"CheckForOnlinePlayers disabled");
+ Debug.WriteLine($"CheckForOnlinePlayers disabled, shutdown timer cancelled.");
+ break;
}
var message = string.Empty;
@@ -526,7 +536,7 @@ namespace ServerManagerTool.Lib
{
LogProfileMessage(Config.Default.ServerShutdown_WorldSaveMessage);
ProcessAlert(AlertType.ShutdownMessage, Config.Default.ServerShutdown_WorldSaveMessage);
- SendMessageAsync(Config.Default.ServerShutdown_WorldSaveMessage, cancellationToken).Wait();
+ SendMessageAsync(Config.Default.ServerShutdown_WorldSaveMessage, cancellationToken).Wait(cancellationToken);
}
if (SendCommandAsync(Config.Default.ServerSaveCommand, false).Result)
@@ -551,7 +561,7 @@ namespace ServerManagerTool.Lib
if (!string.IsNullOrWhiteSpace(Config.Default.ServerShutdown_CancelMessage))
{
ProcessAlert(AlertType.Shutdown, Config.Default.ServerShutdown_CancelMessage);
- SendMessageAsync(Config.Default.ServerShutdown_CancelMessage, CancellationToken.None).Wait();
+ SendMessageAsync(Config.Default.ServerShutdown_CancelMessage, cancellationToken).Wait();
}
ExitCode = EXITCODE_CANCELLED;
@@ -577,8 +587,6 @@ namespace ServerManagerTool.Lib
}
finally
{
- CloseRconConsole();
-
gameServer?.Dispose();
gameServer = null;
}
@@ -590,11 +598,9 @@ namespace ServerManagerTool.Lib
if (!string.IsNullOrWhiteSpace(Config.Default.ServerShutdown_CancelMessage))
{
ProcessAlert(AlertType.Shutdown, Config.Default.ServerShutdown_CancelMessage);
- SendMessageAsync(Config.Default.ServerShutdown_CancelMessage, CancellationToken.None).Wait();
+ SendMessageAsync(Config.Default.ServerShutdown_CancelMessage, cancellationToken).Wait();
}
- CloseRconConsole();
-
ExitCode = EXITCODE_CANCELLED;
return;
}
@@ -643,8 +649,6 @@ namespace ServerManagerTool.Lib
LogProfileMessage("Exiting server timed out, attempting to close the server.");
}
- CloseRconConsole();
-
// Method 2 - Close the process
sent = process.CloseMainWindow();
@@ -711,7 +715,7 @@ namespace ServerManagerTool.Lib
ExitCode = EXITCODE_SHUTDOWN_TIMEOUT;
}
- private void UpgradeLocal(bool validate, CancellationToken cancellationToken, bool updateMods)
+ private void UpgradeLocal(bool validate, bool updateMods, CancellationToken cancellationToken)
{
if (_profile == null)
{
@@ -1240,7 +1244,7 @@ namespace ServerManagerTool.Lib
{
// perform a steamcmd validate to confirm all the files
LogProfileMessage("Validating server files (*new*).");
- UpgradeLocal(true, CancellationToken.None, false);
+ UpgradeLocal(true, false, CancellationToken.None);
LogProfileMessage("Validated server files (*new*).");
}
@@ -1766,17 +1770,6 @@ namespace ServerManagerTool.Lib
ExitCode = EXITCODE_NORMALEXIT;
}
- private void CloseRconConsole()
- {
- if (_rconConsole != null)
- {
- _rconConsole.Dispose();
- _rconConsole = null;
-
- Task.Delay(1000).Wait();
- }
- }
-
public void CheckServerWorldFileExists(ServerProfileSnapshot profile = null)
{
// do nothing if profile is null or SotF
@@ -2568,39 +2561,42 @@ namespace ServerManagerTool.Lib
int rconRetries = 0;
int maxRetries = retryIfFailed ? RCON_MAXRETRIES : 1;
- while (retries < maxRetries && rconRetries < RCON_MAXRETRIES)
+ try
{
- SetupRconConsole();
-
- if (_rconConsole == null)
+ while (retries < maxRetries && rconRetries < RCON_MAXRETRIES)
{
- LogProfileMessage($"RCON> {command} - attempt {rconRetries + 1} (a).", false);
-#if DEBUG
- LogProfileMessage("RCON connection not created.", false);
-#endif
- rconRetries++;
- }
- else
- {
- rconRetries = 0;
- try
- {
- _rconConsole.SendCommand(command);
- LogProfileMessage($"RCON> {command}");
+ SetupRconConsole();
- return true;
- }
- catch (Exception ex)
+ if (_rconConsole == null)
{
- LogProfileMessage($"RCON> {command} - attempt {retries + 1} (b).", false);
-#if DEBUG
- LogProfileMessage($"{ex.Message}", false);
-#endif
+ LogProfileMessage($"RCON> {command} - attempt {rconRetries + 1} (a).", false);
+ LogProfileMessage("RCON connection not created.", false);
+ rconRetries++;
}
+ else
+ {
+ rconRetries = 0;
+ try
+ {
+ _rconConsole.SendCommand(command);
+ LogProfileMessage($"RCON> {command}");
- retries++;
+ return true;
+ }
+ catch (Exception ex)
+ {
+ LogProfileMessage($"RCON> {command} - attempt {retries + 1} (b).", false);
+ LogProfileMessage($"{ex.Message}", false);
+ }
+
+ retries++;
+ }
}
}
+ finally
+ {
+ CloseRconConsole();
+ }
return false;
}
@@ -2662,6 +2658,17 @@ namespace ServerManagerTool.Lib
}
}
+ private void CloseRconConsole()
+ {
+ if (_rconConsole != null)
+ {
+ _rconConsole.Dispose();
+ _rconConsole = null;
+
+ Task.Delay(1000).Wait();
+ }
+ }
+
private void SetupRconConsole()
{
CloseRconConsole();
@@ -2671,7 +2678,7 @@ namespace ServerManagerTool.Lib
try
{
- var endPoint = new IPEndPoint(IPAddress.Parse(_profile.ServerIP), _profile.RCONPort);
+ var endPoint = new IPEndPoint(_profile.ServerIPAddress, _profile.RCONPort);
var server = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endPoint, sendTimeOut: 10000, receiveTimeOut: 10000);
if (server == null)
{
@@ -2708,7 +2715,7 @@ namespace ServerManagerTool.Lib
}
}
- public int PerformProfileBackup(ServerProfileSnapshot profile)
+ public int PerformProfileBackup(ServerProfileSnapshot profile, CancellationToken cancellationToken)
{
_profile = profile;
@@ -2733,7 +2740,7 @@ namespace ServerManagerTool.Lib
// check if the mutex was established
if (createdNew)
{
- BackupServer();
+ BackupServer(cancellationToken);
if (ExitCode != EXITCODE_NORMALEXIT)
{
@@ -2869,7 +2876,7 @@ namespace ServerManagerTool.Lib
return ExitCode;
}
- public int PerformProfileUpdate(ServerBranchSnapshot branch, ServerProfileSnapshot profile)
+ public int PerformProfileUpdate(BranchSnapshot branch, ServerProfileSnapshot profile)
{
_profile = profile;
@@ -2945,7 +2952,7 @@ namespace ServerManagerTool.Lib
return ExitCode;
}
- public int PerformServerBranchUpdate(ServerBranchSnapshot branch)
+ public int PerformServerBranchUpdate(BranchSnapshot branch)
{
if (branch == null)
return EXITCODE_NORMALEXIT;
@@ -3089,7 +3096,7 @@ namespace ServerManagerTool.Lib
SendEmails = true,
ServerProcess = ServerProcessType.AutoBackup
};
- exitCodes.TryAdd(profile, app.PerformProfileBackup(profile));
+ exitCodes.TryAdd(profile, app.PerformProfileBackup(profile, CancellationToken.None));
});
foreach (var profile in _profiles.Keys)
@@ -3233,8 +3240,8 @@ namespace ServerManagerTool.Lib
if (exitCode == EXITCODE_NORMALEXIT)
{
- var branches = _profiles.Keys.Where(p => p.EnableAutoUpdate).Select(p => new ServerBranchSnapshot() { BranchName = p.BranchName, BranchPassword = p.BranchPassword}).Distinct(new ServerBranchSnapshotComparer()).ToArray();
- var exitCodes = new ConcurrentDictionary();
+ var branches = _profiles.Keys.Where(p => p.EnableAutoUpdate).Select(p => BranchSnapshot.Create(p)).Distinct(new BranchSnapshotComparer()).ToArray();
+ var exitCodes = new ConcurrentDictionary();
// update the server cache for each branch
if (Config.Default.AutoUpdate_ParallelUpdate)
diff --git a/src/ARKServerManager/Lib/ServerProfile.cs b/src/ARKServerManager/Lib/ServerProfile.cs
index dfac1b1a..d8dc7a6d 100644
--- a/src/ARKServerManager/Lib/ServerProfile.cs
+++ b/src/ARKServerManager/Lib/ServerProfile.cs
@@ -957,6 +957,64 @@ namespace ServerManagerTool.Lib
}
#endregion
+ #region Discord Bot
+ public static readonly DependencyProperty DiscordChannelIdProperty = DependencyProperty.Register(nameof(DiscordChannelId), typeof(string), typeof(ServerProfile), new PropertyMetadata(String.Empty));
+ [DataMember]
+ public string DiscordChannelId
+ {
+ get { return (string)GetValue(DiscordChannelIdProperty); }
+ set { SetValue(DiscordChannelIdProperty, value); }
+ }
+
+ public static readonly DependencyProperty AllowDiscordBackupProperty = DependencyProperty.Register(nameof(AllowDiscordBackup), typeof(bool), typeof(ServerProfile), new PropertyMetadata(true));
+ [DataMember]
+ public bool AllowDiscordBackup
+ {
+ get { return (bool)GetValue(AllowDiscordBackupProperty); }
+ set { SetValue(AllowDiscordBackupProperty, value); }
+ }
+
+ public static readonly DependencyProperty AllowDiscordRestartProperty = DependencyProperty.Register(nameof(AllowDiscordRestart), typeof(bool), typeof(ServerProfile), new PropertyMetadata(true));
+ [DataMember]
+ public bool AllowDiscordRestart
+ {
+ get { return (bool)GetValue(AllowDiscordRestartProperty); }
+ set { SetValue(AllowDiscordRestartProperty, value); }
+ }
+
+ public static readonly DependencyProperty AllowDiscordShutdownProperty = DependencyProperty.Register(nameof(AllowDiscordShutdown), typeof(bool), typeof(ServerProfile), new PropertyMetadata(true));
+ [DataMember]
+ public bool AllowDiscordShutdown
+ {
+ get { return (bool)GetValue(AllowDiscordShutdownProperty); }
+ set { SetValue(AllowDiscordShutdownProperty, value); }
+ }
+
+ public static readonly DependencyProperty AllowDiscordStartProperty = DependencyProperty.Register(nameof(AllowDiscordStart), typeof(bool), typeof(ServerProfile), new PropertyMetadata(true));
+ [DataMember]
+ public bool AllowDiscordStart
+ {
+ get { return (bool)GetValue(AllowDiscordStartProperty); }
+ set { SetValue(AllowDiscordStartProperty, value); }
+ }
+
+ public static readonly DependencyProperty AllowDiscordStopProperty = DependencyProperty.Register(nameof(AllowDiscordStop), typeof(bool), typeof(ServerProfile), new PropertyMetadata(true));
+ [DataMember]
+ public bool AllowDiscordStop
+ {
+ get { return (bool)GetValue(AllowDiscordStopProperty); }
+ set { SetValue(AllowDiscordStopProperty, value); }
+ }
+
+ public static readonly DependencyProperty AllowDiscordUpdateProperty = DependencyProperty.Register(nameof(AllowDiscordUpdate), typeof(bool), typeof(ServerProfile), new PropertyMetadata(true));
+ [DataMember]
+ public bool AllowDiscordUpdate
+ {
+ get { return (bool)GetValue(AllowDiscordUpdateProperty); }
+ set { SetValue(AllowDiscordUpdateProperty, value); }
+ }
+ #endregion
+
#region Rules
public static readonly DependencyProperty EnableHardcoreProperty = DependencyProperty.Register(nameof(EnableHardcore), typeof(bool), typeof(ServerProfile), new PropertyMetadata(false));
[IniFileEntry(IniFiles.GameUserSettings, IniSections.GUS_ServerSettings, ServerProfileCategory.Rules, "ServerHardcore")]
diff --git a/src/ARKServerManager/Lib/ServerProfileSnapshot.cs b/src/ARKServerManager/Lib/ServerProfileSnapshot.cs
index 940bf1e5..0d2238ec 100644
--- a/src/ARKServerManager/Lib/ServerProfileSnapshot.cs
+++ b/src/ARKServerManager/Lib/ServerProfileSnapshot.cs
@@ -7,6 +7,10 @@ namespace ServerManagerTool.Lib
{
public class ServerProfileSnapshot
{
+ private ServerProfileSnapshot()
+ {
+ }
+
public string ProfileId;
public string ProfileName;
public string InstallDirectory;
@@ -16,7 +20,7 @@ namespace ServerManagerTool.Lib
public string AdminPassword;
public string ServerName;
public string ServerArgs;
- public string ServerIP;
+ public IPAddress ServerIPAddress;
public int ServerPort;
public int ServerPeerPort;
public int QueryPort;
@@ -67,7 +71,7 @@ namespace ServerManagerTool.Lib
AdminPassword = profile.AdminPassword,
ServerName = profile.ServerName,
ServerArgs = profile.GetServerArgs(),
- ServerIP = string.IsNullOrWhiteSpace(profile.ServerIP) ? IPAddress.Loopback.ToString() : profile.ServerIP.Trim(),
+ ServerIPAddress = string.IsNullOrWhiteSpace(profile.ServerIP) ? IPAddress.Loopback : IPAddress.TryParse(profile.ServerIP.Trim(), out IPAddress ipAddress) ? ipAddress : IPAddress.Loopback,
ServerPort = profile.ServerPort,
ServerPeerPort = profile.ServerPeerPort,
QueryPort = profile.QueryPort,
diff --git a/src/ARKServerManager/Lib/ServerRuntime.cs b/src/ARKServerManager/Lib/ServerRuntime.cs
index 420cde9f..b1276696 100644
--- a/src/ARKServerManager/Lib/ServerRuntime.cs
+++ b/src/ARKServerManager/Lib/ServerRuntime.cs
@@ -33,7 +33,7 @@ namespace ServerManagerTool.Lib
public event EventHandler StatusUpdate;
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
- private readonly GlobalizedApplication _globalizer = GlobalizedApplication.Instance;
+ private static readonly GlobalizedApplication _globalizer = GlobalizedApplication.Instance;
private readonly List profileNotifiers = new List();
private Process serverProcess;
private IAsyncDisposable updateRegistration;
@@ -169,6 +169,9 @@ namespace ServerManagerTool.Lib
ServerProfile.ServerIPProperty,
ServerProfile.MaxPlayersProperty,
+ ServerProfile.ServerPasswordProperty,
+ ServerProfile.AdminPasswordProperty,
+
ServerProfile.ServerMapProperty,
ServerProfile.ServerModIdsProperty,
ServerProfile.TotalConversionModIdProperty,
@@ -177,7 +180,7 @@ namespace ServerManagerTool.Lib
},
(s, p) =>
{
- if (Status == ServerStatus.Stopped || Status == ServerStatus.Uninstalled || Status == ServerStatus.Unknown)
+ if (Status == ServerStatus.Stopped || Status == ServerStatus.Uninstalled || Status == ServerStatus.Unknown || Status == ServerStatus.Updating)
{
AttachToProfileCore(profile);
}
@@ -198,16 +201,7 @@ namespace ServerManagerTool.Lib
return;
}
- if (!String.IsNullOrWhiteSpace(this.ProfileSnapshot.ServerIP) && IPAddress.TryParse(this.ProfileSnapshot.ServerIP, out IPAddress localServerIpAddress))
- {
- // Use the explicit Server IP
- localServerQueryEndPoint = new IPEndPoint(localServerIpAddress, Convert.ToUInt16(this.ProfileSnapshot.QueryPort));
- }
- else
- {
- // No Server IP specified, use Loopback
- localServerQueryEndPoint = new IPEndPoint(IPAddress.Loopback, Convert.ToUInt16(this.ProfileSnapshot.QueryPort));
- }
+ localServerQueryEndPoint = new IPEndPoint(this.ProfileSnapshot.ServerIPAddress, Convert.ToUInt16(this.ProfileSnapshot.QueryPort));
//
// Get the public endpoint for querying Steam
@@ -281,13 +275,13 @@ namespace ServerManagerTool.Lib
case WatcherServerStatus.RunningLocalCheck:
if (oldStatus != ServerStatus.Stopping)
- UpdateServerStatus(ServerStatus.Running, this.Availability != AvailabilityStatus.Available ? AvailabilityStatus.WaitingForPublication : this.Availability, oldStatus != ServerStatus.Running && oldStatus != ServerStatus.Unknown);
+ UpdateServerStatus(ServerStatus.Running, this.Availability != AvailabilityStatus.Available ? AvailabilityStatus.Waiting : this.Availability, oldStatus != ServerStatus.Running && oldStatus != ServerStatus.Unknown);
if (this.ProfileSnapshot.MOTDIntervalEnabled && this.motdIntervalTimer != null && !this.motdIntervalTimer.Enabled) this.motdIntervalTimer.Start();
break;
case WatcherServerStatus.RunningExternalCheck:
if (oldStatus != ServerStatus.Stopping)
- UpdateServerStatus(ServerStatus.Running, AvailabilityStatus.WaitingForPublication, oldStatus != ServerStatus.Running && oldStatus != ServerStatus.Unknown);
+ UpdateServerStatus(ServerStatus.Running, AvailabilityStatus.Waiting, oldStatus != ServerStatus.Running && oldStatus != ServerStatus.Unknown);
if (this.ProfileSnapshot.MOTDIntervalEnabled && this.motdIntervalTimer != null && !this.motdIntervalTimer.Enabled) this.motdIntervalTimer.Start();
break;
@@ -439,7 +433,7 @@ namespace ServerManagerTool.Lib
}
CheckServerWorldFileExists();
- UpdateServerStatus(ServerStatus.Initializing, this.Availability, false);
+ UpdateServerStatus(ServerStatus.Initializing, false);
try
{
@@ -498,12 +492,12 @@ namespace ServerManagerTool.Lib
}
}
- public async Task UpgradeAsync(CancellationToken cancellationToken, bool updateServer, ServerBranchSnapshot branch, bool validate, bool updateMods, ProgressDelegate progressCallback)
+ public async Task UpgradeAsync(CancellationToken cancellationToken, bool updateServer, BranchSnapshot branch, bool validate, bool updateMods, ProgressDelegate progressCallback)
{
return await UpgradeAsync(cancellationToken, updateServer, branch, validate, updateMods, null, progressCallback);
}
- public async Task UpgradeAsync(CancellationToken cancellationToken, bool updateServer, ServerBranchSnapshot branch, bool validate, bool updateMods, string[] updateModIds, ProgressDelegate progressCallback)
+ public async Task UpgradeAsync(CancellationToken cancellationToken, bool updateServer, BranchSnapshot branch, bool validate, bool updateMods, string[] updateModIds, ProgressDelegate progressCallback)
{
if (updateServer && !Environment.Is64BitOperatingSystem)
{
@@ -520,7 +514,7 @@ namespace ServerManagerTool.Lib
bool isNewInstallation = this.Status == ServerStatus.Uninstalled;
- UpdateServerStatus(ServerStatus.Updating, Availability, false);
+ UpdateServerStatus(ServerStatus.Updating, false);
// Run the SteamCMD to install the server
var steamCmdFile = SteamCmdUpdater.GetSteamCmdFile(Config.Default.DataDir);
@@ -957,7 +951,7 @@ namespace ServerManagerTool.Lib
finally
{
this.lastModStatusQuery = DateTime.MinValue;
- UpdateServerStatus(ServerStatus.Stopped, Availability, false);
+ UpdateServerStatus(ServerStatus.Stopped, false);
}
}
@@ -993,6 +987,11 @@ namespace ServerManagerTool.Lib
this.lastModStatusQuery = DateTime.MinValue;
}
+ public void UpdateServerStatus(ServerStatus serverStatus, bool sendAlert)
+ {
+ UpdateServerStatus(serverStatus, Availability, sendAlert);
+ }
+
public void UpdateServerStatus(ServerStatus serverStatus, AvailabilityStatus availabilityStatus, bool sendAlert)
{
this.Status = serverStatus;
@@ -1006,32 +1005,29 @@ namespace ServerManagerTool.Lib
public void UpdateServerStatusString()
{
- switch (Status)
+ StatusString = GetServerStatusString(Status);
+ }
+
+ public static string GetServerStatusString(ServerStatus status)
+ {
+ switch (status)
{
case ServerStatus.Initializing:
- StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusInitializingLabel");
- break;
+ return _globalizer.GetResourceString("ServerSettings_RuntimeStatusInitializingLabel");
case ServerStatus.Running:
- StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusRunningLabel");
- break;
+ return _globalizer.GetResourceString("ServerSettings_RuntimeStatusRunningLabel");
case ServerStatus.Stopped:
- StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusStoppedLabel");
- break;
+ return _globalizer.GetResourceString("ServerSettings_RuntimeStatusStoppedLabel");
case ServerStatus.Stopping:
- StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusStoppingLabel");
- break;
+ return _globalizer.GetResourceString("ServerSettings_RuntimeStatusStoppingLabel");
case ServerStatus.Uninstalled:
- StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusUninstalledLabel");
- break;
+ return _globalizer.GetResourceString("ServerSettings_RuntimeStatusUninstalledLabel");
case ServerStatus.Unknown:
- StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusUnknownLabel");
- break;
+ return _globalizer.GetResourceString("ServerSettings_RuntimeStatusUnknownLabel");
case ServerStatus.Updating:
- StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusUpdatingLabel");
- break;
+ return _globalizer.GetResourceString("ServerSettings_RuntimeStatusUpdatingLabel");
default:
- StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusUnknownLabel");
- break;
+ return _globalizer.GetResourceString("ServerSettings_RuntimeStatusUnknownLabel");
}
}
@@ -1091,7 +1087,7 @@ namespace ServerManagerTool.Lib
try
{
- var endPoint = new IPEndPoint(IPAddress.Parse(this.ProfileSnapshot.ServerIP), this.ProfileSnapshot.RCONPort);
+ var endPoint = new IPEndPoint(this.ProfileSnapshot.ServerIPAddress, this.ProfileSnapshot.RCONPort);
var server = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endPoint, sendTimeOut: 10000, receiveTimeOut: 10000);
if (server == null)
diff --git a/src/ARKServerManager/Styles/Default.xaml b/src/ARKServerManager/Styles/Default.xaml
index e2b09de0..9fecf6e6 100644
--- a/src/ARKServerManager/Styles/Default.xaml
+++ b/src/ARKServerManager/Styles/Default.xaml
@@ -713,5 +713,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ARKServerManager/Utils/DiscordBotHelper.cs b/src/ARKServerManager/Utils/DiscordBotHelper.cs
new file mode 100644
index 00000000..97abbec7
--- /dev/null
+++ b/src/ARKServerManager/Utils/DiscordBotHelper.cs
@@ -0,0 +1,735 @@
+using QueryMaster;
+using ServerManagerTool.Common.Utils;
+using ServerManagerTool.DiscordBot.Enums;
+using ServerManagerTool.Enums;
+using ServerManagerTool.Lib;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using WPFSharp.Globalizer;
+
+namespace ServerManagerTool.Utils
+{
+ internal static class DiscordBotHelper
+ {
+ private static readonly GlobalizedApplication _globalizer = GlobalizedApplication.Instance;
+ private static bool _runningCommand = false;
+
+ private static readonly Dictionary _currentProfileCommands = new Dictionary();
+
+ public static bool HasRunningCommands => _currentProfileCommands.Count > 0;
+
+ public static IList HandleDiscordCommand(CommandType commandType, string serverId, string channelId, string profileId, CancellationToken token)
+ {
+ // check if incoming values are valid
+ if (string.IsNullOrWhiteSpace(serverId) || string.IsNullOrWhiteSpace(channelId))
+ return null;
+
+ // check if the server ids match
+ if (!serverId.Equals(Config.Default.DiscordBotServerId))
+ return new List();
+
+ if (_runningCommand)
+ return new List { _globalizer.GetResourceString("DiscordBot_CommandRunning") };
+ _runningCommand = true;
+
+ try
+ {
+ switch (commandType)
+ {
+ case CommandType.Info:
+ return GetServerInfo(channelId, profileId);
+ case CommandType.List:
+ return GetServerList(channelId);
+ case CommandType.Status:
+ return GetServerStatus(channelId, profileId);
+
+ case CommandType.Backup:
+ if (Config.Default.AllowDiscordBackup)
+ return BackupServer(channelId, profileId, token);
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
+ case CommandType.Restart:
+ if (Config.Default.AllowDiscordRestart)
+ return RestartServer(channelId, profileId, token);
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
+ case CommandType.Shutdown:
+ if (Config.Default.AllowDiscordShutdown)
+ return ShutdownServer(channelId, profileId, token);
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
+ case CommandType.Stop:
+ if (Config.Default.AllowDiscordStop)
+ return StopServer(channelId, profileId, token);
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
+ case CommandType.Start:
+ if (Config.Default.AllowDiscordStart)
+ return StartServer(channelId, profileId, token);
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
+ case CommandType.Update:
+ if (Config.Default.AllowDiscordUpdate)
+ return UpdateServer(channelId, profileId, token);
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
+
+ default:
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), commandType) };
+ }
+ }
+ catch (Exception ex)
+ {
+ var message = ex.InnerException is null ? ex.Message : ex.InnerException.Message;
+ return new string[] { message };
+ }
+ finally
+ {
+ _runningCommand = false;
+ }
+ }
+
+ public static string HandleTranslation(string translationKey)
+ {
+ return string.IsNullOrWhiteSpace(translationKey) ? string.Empty : _globalizer.GetResourceString(translationKey) ?? translationKey;
+ }
+
+ private static IList GetServerInfo(string channelId, string profileId)
+ {
+ if (string.IsNullOrWhiteSpace(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Info) };
+ }
+
+ // check if another command is being run against the profile
+ if (_currentProfileCommands.ContainsKey(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) };
+ }
+ _currentProfileCommands.Add(profileId, CommandType.Info);
+
+ try
+ {
+ var serverName = string.Empty;
+ var serverIp = IPAddress.Loopback;
+ var queryPort = 0;
+
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+
+ if (server is null)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId));
+ }
+
+ switch (server.Runtime.Status)
+ {
+ case ServerStatus.Initializing:
+ case ServerStatus.Stopping:
+ case ServerStatus.Stopped:
+ case ServerStatus.Uninstalled:
+ case ServerStatus.Unknown:
+ case ServerStatus.Updating:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString));
+ }
+
+ serverName = server.Profile.ServerName;
+ if (!string.IsNullOrWhiteSpace(server.Profile.ServerIP))
+ {
+ IPAddress.TryParse(server.Profile.ServerIP, out serverIp);
+ }
+ queryPort = server.Profile.QueryPort;
+ }).Wait();
+
+ List response = new List();
+
+ try
+ {
+ using (var gameServer = ServerQuery.GetServerInstance(EngineType.Source, new IPEndPoint(serverIp, queryPort)))
+ {
+ var info = gameServer?.GetInfo();
+ if (info is null)
+ {
+ response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_InfoFailed"), serverName));
+ }
+ else
+ {
+ var mapName = _globalizer.GetResourceString($"Map_{info.Map}") ?? info.Map;
+ response.Add($"```{info.Name}\n{_globalizer.GetResourceString("DiscordBot_MapLabel")} {mapName}\n{_globalizer.GetResourceString("ServerSettings_PlayersLabel")} {info.Players} / {info.MaxPlayers}```");
+ }
+ }
+ }
+ catch (Exception)
+ {
+ response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_InfoFailed"), serverName));
+ }
+
+ return response;
+ }
+ finally
+ {
+ _currentProfileCommands.Remove(profileId);
+ }
+ }
+
+ private static IList GetServerList(string channelId)
+ {
+ List response = new List();
+
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var serverList = ServerManager.Instance.Servers.Where(s => Equals(channelId, s.Profile.DiscordChannelId));
+
+ response.Add($"**{_globalizer.GetResourceString("DiscordBot_CountLabel")}** {serverList.Count()}");
+ foreach (var server in serverList)
+ {
+ response.Add($"```{_globalizer.GetResourceString("ServerSettings_ProfileIdLabel")} {server.Profile.ProfileID}\n{_globalizer.GetResourceString("ServerSettings_ProfileLabel")} {server.Profile.ProfileName}\n{_globalizer.GetResourceString("ServerSettings_ServerNameLabel")} {server.Profile.ServerName}```");
+ }
+ }).Wait();
+
+ return response;
+ }
+
+ private static IList GetServerStatus(string channelId, string profileId)
+ {
+ List response = new List();
+
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var serverList = ServerManager.Instance.Servers.Where(s => Equals(channelId, s.Profile.DiscordChannelId) && (string.IsNullOrWhiteSpace(profileId) || Equals(profileId, s.Profile.ProfileID)));
+
+ response.Add($"**{_globalizer.GetResourceString("DiscordBot_CountLabel")}** {serverList.Count()}");
+ foreach (var server in serverList)
+ {
+ response.Add($"```{_globalizer.GetResourceString("ServerSettings_ProfileLabel")} {server.Profile.ProfileName}\n{_globalizer.GetResourceString("ServerSettings_ServerNameLabel")} {server.Profile.ServerName}\n{_globalizer.GetResourceString("ServerSettings_StatusLabel")} {server.Runtime.StatusString}\n{_globalizer.GetResourceString("ServerSettings_AvailabilityLabel")} {_globalizer.GetResourceString($"ServerSettings_Availability_{server.Runtime.Availability}")}```");
+ }
+ }).Wait();
+
+ return response;
+ }
+
+ private static IList BackupServer(string channelId, string profileId, CancellationToken token)
+ {
+ if (string.IsNullOrWhiteSpace(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Backup) };
+ }
+
+ // check if another command is being run against the profile
+ if (_currentProfileCommands.ContainsKey(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) };
+ }
+ _currentProfileCommands.Add(profileId, CommandType.Backup);
+
+ ServerProfileSnapshot profile = null;
+ Task task = null;
+
+ try
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+
+ if (server is null)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId));
+ }
+
+ if (!server.Profile.AllowDiscordBackup)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_CommandDisabledProfile"), CommandType.Backup, profileId));
+ }
+
+ switch (server.Runtime.Status)
+ {
+ case ServerStatus.Initializing:
+ case ServerStatus.Stopping:
+ case ServerStatus.Uninstalled:
+ case ServerStatus.Unknown:
+ case ServerStatus.Updating:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString));
+ }
+
+ profile = ServerProfileSnapshot.Create(server.Profile);
+ }).Wait();
+
+ List response = new List();
+
+ var app = new ServerApp(true)
+ {
+ DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup,
+ OutputLogs = false,
+ SendAlerts = true,
+ SendEmails = false,
+ ServerProcess = ServerProcessType.Backup,
+ ServerStatusChangeCallback = (ServerStatus serverStatus) =>
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+ server.Runtime.UpdateServerStatus(serverStatus, true);
+ }).Wait();
+ }
+ };
+
+ task = Task.Run(() =>
+ {
+ app.PerformProfileBackup(profile, token);
+ _currentProfileCommands.Remove(profileId);
+ });
+
+ response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_BackupRequested"), profile.ServerName));
+
+ return response;
+ }
+ finally
+ {
+ if (task is null)
+ {
+ _currentProfileCommands.Remove(profileId);
+ }
+ }
+ }
+
+ private static IList RestartServer(string channelId, string profileId, CancellationToken token)
+ {
+ if (string.IsNullOrWhiteSpace(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Restart) };
+ }
+
+ // check if another command is being run against the profile
+ if (_currentProfileCommands.ContainsKey(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) };
+ }
+ _currentProfileCommands.Add(profileId, CommandType.Restart);
+
+ ServerProfileSnapshot profile = null;
+ Task task = null;
+
+ try
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+
+ if (server is null)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId));
+ }
+
+ if (!server.Profile.AllowDiscordRestart)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_CommandDisabledProfile"), CommandType.Restart, profileId));
+ }
+
+ switch (server.Runtime.Status)
+ {
+ case ServerStatus.Initializing:
+ case ServerStatus.Stopping:
+ case ServerStatus.Uninstalled:
+ case ServerStatus.Unknown:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString));
+
+ case ServerStatus.Updating:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileUpdating"), profileId));
+ }
+
+ profile = ServerProfileSnapshot.Create(server.Profile);
+ profile.AutoRestartIfShutdown = true;
+ }).Wait();
+
+ List response = new List();
+
+ var app = new ServerApp(true)
+ {
+ DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup,
+ OutputLogs = false,
+ SendAlerts = true,
+ SendEmails = false,
+ ServerProcess = ServerProcessType.Restart,
+ ServerStatusChangeCallback = (ServerStatus serverStatus) =>
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+ server.Runtime.UpdateServerStatus(serverStatus, true);
+ }).Wait();
+ }
+ };
+
+ task = Task.Run(() =>
+ {
+ app.PerformProfileShutdown(profile, true, false, false, token);
+ _currentProfileCommands.Remove(profileId);
+ });
+
+ response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_RestartRequested"), profile.ServerName));
+
+ return response;
+ }
+ finally
+ {
+ if (task is null)
+ {
+ _currentProfileCommands.Remove(profileId);
+ }
+ }
+ }
+
+ private static IList ShutdownServer(string channelId, string profileId, CancellationToken token)
+ {
+ if (string.IsNullOrWhiteSpace(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Shutdown) };
+ }
+
+ // check if another command is being run against the profile
+ if (_currentProfileCommands.ContainsKey(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) };
+ }
+ _currentProfileCommands.Add(profileId, CommandType.Shutdown);
+
+ ServerProfileSnapshot profile = null;
+ Task task = null;
+
+ try
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+
+ if (server is null)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId));
+ }
+
+ if (!server.Profile.AllowDiscordShutdown)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_CommandDisabledProfile"), CommandType.Shutdown, profileId));
+ }
+
+ switch (server.Runtime.Status)
+ {
+ case ServerStatus.Initializing:
+ case ServerStatus.Stopping:
+ case ServerStatus.Stopped:
+ case ServerStatus.Uninstalled:
+ case ServerStatus.Unknown:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString));
+
+ case ServerStatus.Updating:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileUpdating"), profileId));
+ }
+
+ profile = ServerProfileSnapshot.Create(server.Profile);
+ }).Wait();
+
+ List response = new List();
+
+ var app = new ServerApp(true)
+ {
+ DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup,
+ OutputLogs = false,
+ SendAlerts = true,
+ SendEmails = false,
+ ServerProcess = ServerProcessType.Shutdown,
+ ServerStatusChangeCallback = (ServerStatus serverStatus) =>
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+ server.Runtime.UpdateServerStatus(serverStatus, true);
+ }).Wait();
+ }
+ };
+
+ task = Task.Run(() =>
+ {
+ app.PerformProfileShutdown(profile, false, false, false, token);
+ _currentProfileCommands.Remove(profileId);
+ });
+
+ response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_ShutdownRequested"), profile.ServerName));
+
+ return response;
+ }
+ finally
+ {
+ if (task is null)
+ {
+ _currentProfileCommands.Remove(profileId);
+ }
+ }
+ }
+
+ private static IList StopServer(string channelId, string profileId, CancellationToken token)
+ {
+ if (string.IsNullOrWhiteSpace(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Stop) };
+ }
+
+ // check if another command is being run against the profile
+ if (_currentProfileCommands.ContainsKey(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) };
+ }
+ _currentProfileCommands.Add(profileId, CommandType.Stop);
+
+ ServerProfileSnapshot profile = null;
+ Task task = null;
+
+ try
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+
+ if (server is null)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId));
+ }
+
+ if (!server.Profile.AllowDiscordStop)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_CommandDisabledProfile"), CommandType.Stop, profileId));
+ }
+
+ switch (server.Runtime.Status)
+ {
+ case ServerStatus.Initializing:
+ case ServerStatus.Stopping:
+ case ServerStatus.Stopped:
+ case ServerStatus.Uninstalled:
+ case ServerStatus.Unknown:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString));
+
+ case ServerStatus.Updating:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileUpdating"), profileId));
+ }
+
+ profile = ServerProfileSnapshot.Create(server.Profile);
+ }).Wait();
+
+ List response = new List();
+
+ var app = new ServerApp(true)
+ {
+ DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup,
+ OutputLogs = false,
+ SendAlerts = true,
+ SendEmails = false,
+ ServerProcess = ServerProcessType.Shutdown,
+ ShutdownInterval = 0,
+ ServerStatusChangeCallback = (ServerStatus serverStatus) =>
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+ server.Runtime.UpdateServerStatus(serverStatus, true);
+ }).Wait();
+ }
+ };
+
+ task = Task.Run(() =>
+ {
+ app.PerformProfileShutdown(profile, false, false, false, token);
+ _currentProfileCommands.Remove(profileId);
+ });
+
+ response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_StopRequested"), profile.ServerName));
+
+ return response;
+ }
+ finally
+ {
+ if (task is null)
+ {
+ _currentProfileCommands.Remove(profileId);
+ }
+ }
+ }
+
+ private static IList StartServer(string channelId, string profileId, CancellationToken token)
+ {
+ if (string.IsNullOrWhiteSpace(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Start) };
+ }
+
+ // check if another command is being run against the profile
+ if (_currentProfileCommands.ContainsKey(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) };
+ }
+ _currentProfileCommands.Add(profileId, CommandType.Start);
+
+ ServerProfileSnapshot profile = null;
+ Task task = null;
+
+ try
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+
+ if (server is null)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId));
+ }
+
+ if (!server.Profile.AllowDiscordStart)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_CommandDisabledProfile"), CommandType.Start, profileId));
+ }
+
+ switch (server.Runtime.Status)
+ {
+ case ServerStatus.Initializing:
+ case ServerStatus.Stopping:
+ case ServerStatus.Running:
+ case ServerStatus.Uninstalled:
+ case ServerStatus.Unknown:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString));
+
+ case ServerStatus.Updating:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileUpdating"), profileId));
+ }
+
+ profile = ServerProfileSnapshot.Create(server.Profile);
+ profile.AutoRestartIfShutdown = true;
+ }).Wait();
+
+ List response = new List();
+
+ var app = new ServerApp(true)
+ {
+ DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup,
+ OutputLogs = false,
+ SendAlerts = true,
+ SendEmails = false,
+ ServerProcess = ServerProcessType.Restart,
+ ServerStatusChangeCallback = (ServerStatus serverStatus) =>
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+ server.Runtime.UpdateServerStatus(serverStatus, true);
+ }).Wait();
+ }
+ };
+
+ task = Task.Run(() =>
+ {
+ app.PerformProfileShutdown(profile, true, false, false, token);
+ _currentProfileCommands.Remove(profileId);
+ });
+
+ response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_StartRequested"), profile.ServerName));
+
+ return response;
+ }
+ finally
+ {
+ if (task is null)
+ {
+ _currentProfileCommands.Remove(profileId);
+ }
+ }
+ }
+
+ private static IList UpdateServer(string channelId, string profileId, CancellationToken token)
+ {
+ if (string.IsNullOrWhiteSpace(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Update) };
+ }
+
+ // check if another command is being run against the profile
+ if (_currentProfileCommands.ContainsKey(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) };
+ }
+ _currentProfileCommands.Add(profileId, CommandType.Update);
+
+ ServerProfileSnapshot profile = null;
+ bool performRestart = false;
+ Task task = null;
+
+ try
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+
+ if (server is null)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId));
+ }
+
+ if (!server.Profile.AllowDiscordUpdate)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_CommandDisabledProfile"), CommandType.Update, profileId));
+ }
+
+ switch (server.Runtime.Status)
+ {
+ case ServerStatus.Initializing:
+ case ServerStatus.Stopping:
+ case ServerStatus.Unknown:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString));
+
+ case ServerStatus.Running:
+ performRestart = true;
+ break;
+
+ case ServerStatus.Updating:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileUpdating"), profileId));
+ }
+
+ profile = ServerProfileSnapshot.Create(server.Profile);
+ }).Wait();
+
+ List response = new List();
+
+ var app = new ServerApp(true)
+ {
+ DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup,
+ OutputLogs = false,
+ SendAlerts = true,
+ SendEmails = false,
+ ServerProcess = ServerProcessType.Update,
+ ServerStatusChangeCallback = (ServerStatus serverStatus) =>
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+ server.Runtime.UpdateServerStatus(serverStatus, true);
+ }).Wait();
+ }
+ };
+
+ task = Task.Run(() =>
+ {
+ app.PerformProfileShutdown(profile, performRestart, true, false, token);
+ _currentProfileCommands.Remove(profileId);
+ });
+
+ response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_UpdateRequested"), profile.ServerName));
+
+ return response;
+ }
+ finally
+ {
+ if (task is null)
+ {
+ _currentProfileCommands.Remove(profileId);
+ }
+ }
+ }
+ }
+}
diff --git a/src/ARKServerManager/Utils/DiscordPluginHelper.cs b/src/ARKServerManager/Utils/DiscordPluginHelper.cs
new file mode 100644
index 00000000..7ec9ee93
--- /dev/null
+++ b/src/ARKServerManager/Utils/DiscordPluginHelper.cs
@@ -0,0 +1,18 @@
+using ServerManagerTool.Lib;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace ServerManagerTool.Utils
+{
+ internal static class DiscordPluginHelper
+ {
+ public static IList FetchProfiles()
+ {
+ return ServerManager.Instance.Servers.Select(s => new ServerManagerTool.Plugin.Common.Lib.Profile()
+ {
+ ProfileName = s?.Profile?.ProfileName ?? string.Empty,
+ InstallationFolder = s?.Profile?.InstallDirectory ?? string.Empty
+ }).ToList();
+ }
+ }
+}
diff --git a/src/ARKServerManager/Windows/GlobalSettingsControl.xaml b/src/ARKServerManager/Windows/GlobalSettingsControl.xaml
index 0aff8781..9685a6fd 100644
--- a/src/ARKServerManager/Windows/GlobalSettingsControl.xaml
+++ b/src/ARKServerManager/Windows/GlobalSettingsControl.xaml
@@ -579,6 +579,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -621,7 +670,7 @@
-
+
@@ -645,7 +694,7 @@
-
+
diff --git a/src/ARKServerManager/Windows/GlobalSettingsControl.xaml.cs b/src/ARKServerManager/Windows/GlobalSettingsControl.xaml.cs
index 75b35541..6c0a866b 100644
--- a/src/ARKServerManager/Windows/GlobalSettingsControl.xaml.cs
+++ b/src/ARKServerManager/Windows/GlobalSettingsControl.xaml.cs
@@ -289,6 +289,16 @@ namespace ServerManagerTool
}
}
+ private void DiscordBotApply_Click(object sender, RoutedEventArgs e)
+ {
+ Process.Start(Config.Default.DiscordBotApplyUrl);
+ }
+
+ private void DiscordBotHelp_Click(object sender, RoutedEventArgs e)
+ {
+ Process.Start(Config.Default.DiscordBotHelpUrl);
+ }
+
private void ComboBox_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var comboBox = sender as ComboBox;
@@ -326,6 +336,8 @@ namespace ServerManagerTool
textBox = SteamAPIKeyTextBox;
if (Equals(hideTextBox, HideEmailPasswordTextBox))
textBox = EmailPasswordTextBox;
+ if (Equals(hideTextBox, HideDiscordBotTokenTextBox))
+ textBox = DiscordBotTokenTextBox;
if (textBox != null)
{
@@ -349,6 +361,8 @@ namespace ServerManagerTool
hideTextBox = HideSteamAPIKeyTextBox;
if (textBox == EmailPasswordTextBox)
hideTextBox = HideEmailPasswordTextBox;
+ if (textBox == DiscordBotTokenTextBox)
+ hideTextBox = HideDiscordBotTokenTextBox;
if (hideTextBox != null)
{
diff --git a/src/ARKServerManager/Windows/MainWindow.xaml b/src/ARKServerManager/Windows/MainWindow.xaml
index 422c1c1c..2c7bd445 100644
--- a/src/ARKServerManager/Windows/MainWindow.xaml
+++ b/src/ARKServerManager/Windows/MainWindow.xaml
@@ -10,7 +10,7 @@
xmlns:com="clr-namespace:ServerManagerTool.Common;assembly=ServerManager.Common"
xmlns:enum="clr-namespace:ServerManagerTool.Enums"
MinWidth="900" MinHeight="600" Width="1100" Height="900" Left="50" Top="50" WindowState="Normal"
- Loaded="Window_Loaded" SizeChanged="Window_SizeChanged" StateChanged="Window_StateChanged" LocationChanged="Window_LocationChanged"
+ Loaded="MainWindow_Loaded" SizeChanged="MainWindow_SizeChanged" StateChanged="MainWindow_StateChanged" LocationChanged="MainWindow_LocationChanged"
Name="Main" Icon="../Art/favicon.ico" Title="{DynamicResource MainWindow_Title}">
diff --git a/src/ARKServerManager/Windows/MainWindow.xaml.cs b/src/ARKServerManager/Windows/MainWindow.xaml.cs
index d4ea4f77..352049ae 100644
--- a/src/ARKServerManager/Windows/MainWindow.xaml.cs
+++ b/src/ARKServerManager/Windows/MainWindow.xaml.cs
@@ -6,6 +6,7 @@ using ServerManagerTool.Common.Utils;
using ServerManagerTool.Enums;
using ServerManagerTool.Lib;
using ServerManagerTool.Plugin.Common;
+using ServerManagerTool.Utils;
using ServerManagerTool.Windows;
using System;
using System.Diagnostics;
@@ -169,7 +170,7 @@ namespace ServerManagerTool
GlobalizedApplication.Instance.GlobalizationManager.ResourceDictionaryChangedEvent += ResourceDictionaryChangedEvent;
}
- private void Window_Loaded(object sender, RoutedEventArgs e)
+ private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
//
// Kick off the initialization.
@@ -199,15 +200,7 @@ namespace ServerManagerTool
this.scheduledTaskChecker.PostAction(CheckForScheduledTasks).DoNotWait();
}
- private void Window_Closed(object sender, EventArgs e)
- {
- if (sender is Window window)
- window.Closed -= Window_Closed;
-
- this.Activate();
- }
-
- private void Window_LocationChanged(object sender, EventArgs e)
+ private void MainWindow_LocationChanged(object sender, EventArgs e)
{
if (this.WindowState == WindowState.Normal)
{
@@ -216,7 +209,7 @@ namespace ServerManagerTool
}
}
- private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
+ private void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (this.WindowState == WindowState.Normal)
{
@@ -225,7 +218,7 @@ namespace ServerManagerTool
}
}
- private void Window_StateChanged(object sender, EventArgs e)
+ private void MainWindow_StateChanged(object sender, EventArgs e)
{
if (Config.Default.MainWindow_MinimizeToTray && this.WindowState == WindowState.Minimized)
{
@@ -233,8 +226,26 @@ namespace ServerManagerTool
}
}
+ private void Window_Closed(object sender, EventArgs e)
+ {
+ if (sender is Window window)
+ window.Closed -= Window_Closed;
+
+ this.Activate();
+ }
+
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
+ if (DiscordBotHelper.HasRunningCommands)
+ {
+ var result = MessageBox.Show(_globalizer.GetResourceString("MainWindow_DiscordBot_RunningCommandsLabel"), _globalizer.GetResourceString("MainWindow_DiscordBot_RunningCommandsTitle"), MessageBoxButton.YesNo, MessageBoxImage.Question);
+ if (result == MessageBoxResult.No)
+ {
+ e.Cancel = true;
+ return;
+ }
+ }
+
base.OnClosing(e);
RCONWindow.CloseAllWindows();
PlayerListWindow.CloseAllWindows();
diff --git a/src/ARKServerManager/Windows/RCONWindow.xaml.cs b/src/ARKServerManager/Windows/RCONWindow.xaml.cs
index 05ef0af8..e2ec9fbf 100644
--- a/src/ARKServerManager/Windows/RCONWindow.xaml.cs
+++ b/src/ARKServerManager/Windows/RCONWindow.xaml.cs
@@ -664,7 +664,7 @@ namespace ServerManagerTool
ProfileId = server.Runtime.ProfileSnapshot.ProfileId,
ProfileName = server.Runtime.ProfileSnapshot.ProfileName,
MaxPlayers = server.Runtime.MaxPlayers,
- RCONHost = server.Runtime.ProfileSnapshot.ServerIP,
+ RCONHost = server.Runtime.ProfileSnapshot.ServerIPAddress.ToString(),
RCONPort = server.Runtime.ProfileSnapshot.RCONPort,
PGM_Enabled = server.Profile.PGM_Enabled,
diff --git a/src/ARKServerManager/Windows/ServerMonitorWindow.xaml.cs b/src/ARKServerManager/Windows/ServerMonitorWindow.xaml.cs
index 6e141131..a72fba33 100644
--- a/src/ARKServerManager/Windows/ServerMonitorWindow.xaml.cs
+++ b/src/ARKServerManager/Windows/ServerMonitorWindow.xaml.cs
@@ -182,7 +182,7 @@ namespace ServerManagerTool.Windows
var profile = ServerProfileSnapshot.Create(server.Profile);
- var exitCode = await Task.Run(() => app.PerformProfileBackup(profile));
+ var exitCode = await Task.Run(() => app.PerformProfileBackup(profile, CancellationToken.None));
if (exitCode != ServerApp.EXITCODE_NORMALEXIT && exitCode != ServerApp.EXITCODE_CANCELLED)
{
throw new ApplicationException($"An error occured during the backup process - ExitCode: {exitCode}");
@@ -623,7 +623,7 @@ namespace ServerManagerTool.Windows
await Task.Delay(1000);
- var branch = new ServerBranchSnapshot() { BranchName = serverProfile.BranchName, BranchPassword = serverProfile.BranchPassword };
+ var branch = BranchSnapshot.Create(serverProfile);
return await server.UpgradeAsync(_upgradeCancellationSource.Token, updateServer, branch, true, updateMods, (p, m, n) => { TaskUtils.RunOnUIThreadAsync(() => { window?.AddMessage(m, n); }).DoNotWait(); });
}
else
diff --git a/src/ARKServerManager/Windows/ServerSettingsControl.xaml b/src/ARKServerManager/Windows/ServerSettingsControl.xaml
index 1ec4b3da..580ee4ee 100644
--- a/src/ARKServerManager/Windows/ServerSettingsControl.xaml
+++ b/src/ARKServerManager/Windows/ServerSettingsControl.xaml
@@ -420,13 +420,13 @@
-
+
-
+
@@ -1579,6 +1579,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ARKServerManager/Windows/ServerSettingsControl.xaml.cs b/src/ARKServerManager/Windows/ServerSettingsControl.xaml.cs
index 9b53372e..0175709a 100644
--- a/src/ARKServerManager/Windows/ServerSettingsControl.xaml.cs
+++ b/src/ARKServerManager/Windows/ServerSettingsControl.xaml.cs
@@ -1099,7 +1099,7 @@ namespace ServerManagerTool
var profile = ServerProfileSnapshot.Create(Server.Profile);
- var exitCode = await Task.Run(() => app.PerformProfileBackup(profile));
+ var exitCode = await Task.Run(() => app.PerformProfileBackup(profile, CancellationToken.None));
if (exitCode != ServerApp.EXITCODE_NORMALEXIT && exitCode != ServerApp.EXITCODE_CANCELLED)
throw new ApplicationException($"An error occured during the backup process - ExitCode: {exitCode}");
@@ -4266,7 +4266,7 @@ namespace ServerManagerTool
await Task.Delay(1000);
- var branch = new ServerBranchSnapshot() { BranchName = this.Server.Profile.BranchName, BranchPassword = this.Server.Profile.BranchPassword };
+ var branch = BranchSnapshot.Create(this.Server.Profile);
return await this.Server.UpgradeAsync(_upgradeCancellationSource.Token, updateServer, branch, true, updateMods, (p, m, n) => { TaskUtils.RunOnUIThreadAsync(() => { window?.AddMessage(m, n); }).DoNotWait(); });
}
else
diff --git a/src/ConanServerManager/App.config b/src/ConanServerManager/App.config
index 5b188135..30962fd5 100644
--- a/src/ConanServerManager/App.config
+++ b/src/ConanServerManager/App.config
@@ -257,6 +257,12 @@
csmdata
+
+ https://discord.com/developers/applications
+
+
+ https://servermanagers.freeforums.net/thread/99/get-own-discord-bot
+
@@ -570,6 +576,39 @@
250
+
+ False
+
+
+ csm
+
+
+
+
+
+
+
+
+ False
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
\ No newline at end of file
diff --git a/src/ConanServerManager/App.xaml.cs b/src/ConanServerManager/App.xaml.cs
index 00eb25a8..f6ebab3d 100644
--- a/src/ConanServerManager/App.xaml.cs
+++ b/src/ConanServerManager/App.xaml.cs
@@ -1,15 +1,15 @@
-using Microsoft.WindowsAPICodePack.Dialogs;
-using NLog;
+using NLog;
using NLog.Config;
using NLog.Targets;
using ServerManagerTool.Common;
using ServerManagerTool.Common.Utils;
+using ServerManagerTool.DiscordBot;
using ServerManagerTool.Enums;
using ServerManagerTool.Lib;
using ServerManagerTool.Plugin.Common;
+using ServerManagerTool.Utils;
using ServerManagerTool.Windows;
using System;
-using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
@@ -37,6 +37,7 @@ namespace ServerManagerTool
public event PropertyChangedEventHandler PropertyChanged;
+ private CancellationTokenSource _tokenSource;
private GlobalizedApplication _globalizer;
private bool _applicationStarted;
private string _args;
@@ -175,11 +176,6 @@ namespace ServerManagerTool
}
}
- private IList FetchProfiles()
- {
- return ServerManager.Instance.Servers.Select(s => new ServerManagerTool.Plugin.Common.Lib.Profile() { ProfileName = s?.Profile?.ProfileName ?? string.Empty, InstallationFolder = s?.Profile?.InstallDirectory ?? string.Empty }).ToList();
- }
-
public static string GetLogFolder() => IOUtils.NormalizePath(Path.Combine(Config.Default.DataPath, Config.Default.LogsRelativePath));
public static string GetProfileLogFolder(string profileId) => IOUtils.NormalizePath(Path.Combine(Config.Default.DataPath, Config.Default.LogsRelativePath, profileId.ToLower()));
@@ -304,7 +300,7 @@ namespace ServerManagerTool
var installPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
PluginHelper.Instance.BetaEnabled = this.BetaVersion;
PluginHelper.Instance.LoadPlugins(installPath, true);
- PluginHelper.Instance.SetFetchProfileCallback(FetchProfiles);
+ PluginHelper.Instance.SetFetchProfileCallback(DiscordPluginHelper.FetchProfiles);
OnResourceDictionaryChanged(Thread.CurrentThread.CurrentCulture.Name);
// check if we are starting server manager for server shutdown
@@ -395,6 +391,7 @@ namespace ServerManagerTool
this.ApplicationStarted = true;
+ var restartRequired = false;
if (string.IsNullOrWhiteSpace(Config.Default.DataPath))
{
var dataDirectoryWindow = new DataDirectoryWindow();
@@ -405,6 +402,8 @@ namespace ServerManagerTool
{
Environment.Exit(0);
}
+
+ restartRequired = true;
}
Config.Default.ConfigPath = Path.Combine(Config.Default.DataPath, Config.Default.ProfilesRelativePath);
@@ -412,6 +411,11 @@ namespace ServerManagerTool
Config.Default.Save();
CommonConfig.Default.Save();
+ if (restartRequired)
+ {
+ Environment.Exit(0);
+ }
+
if (e.Args.Any(a => a.StartsWith(Constants.ARG_SERVERMONITOR, StringComparison.OrdinalIgnoreCase)))
{
ServerRuntime.EnableUpdateModStatus = false;
@@ -426,6 +430,25 @@ namespace ServerManagerTool
StartupUri = new Uri("Windows/AutoUpdateWindow.xaml", UriKind.RelativeOrAbsolute);
}
+
+ if (Config.Default.DiscordBotEnabled)
+ {
+ _tokenSource = new CancellationTokenSource();
+
+ Task discordTask = Task.Run(async () =>
+ {
+ await ServerManagerBotFactory.GetServerManagerBot()?.StartAsync(Config.Default.DiscordBotToken,Config.Default.DiscordBotPrefix, Config.Default.DataPath, DiscordBotHelper.HandleDiscordCommand, DiscordBotHelper.HandleTranslation, _tokenSource.Token);
+ }, _tokenSource.Token)
+ .ContinueWith(t => {
+ var message = t.Exception.InnerException is null ? t.Exception.Message : t.Exception.InnerException.Message;
+ if (message.StartsWith("#"))
+ {
+ message = _globalizer.GetResourceString(message.Substring(1)) ?? message.Substring(1);
+ }
+
+ MessageBox.Show(message, _globalizer.GetResourceString("DiscordBot_ErrorTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
+ }, TaskContinuationOptions.OnlyOnFaulted);
+ }
}
protected override void OnExit(ExitEventArgs e)
@@ -466,6 +489,12 @@ namespace ServerManagerTool
private void ShutDownApplication()
{
+ if (!(_tokenSource is null))
+ {
+ _tokenSource.Cancel();
+ _tokenSource.Dispose();
+ }
+
if (this.ApplicationStarted)
{
foreach (var server in ServerManager.Instance.Servers)
diff --git a/src/ConanServerManager/ConanServerManager.csproj b/src/ConanServerManager/ConanServerManager.csproj
index 2c167420..8c938509 100644
--- a/src/ConanServerManager/ConanServerManager.csproj
+++ b/src/ConanServerManager/ConanServerManager.csproj
@@ -160,6 +160,7 @@
MSBuild:Compile
Designer
+
@@ -171,6 +172,8 @@
+
+
AddUserWindow.xaml
diff --git a/src/ConanServerManager/Config.Designer.cs b/src/ConanServerManager/Config.Designer.cs
index 7239440f..e11896ed 100644
--- a/src/ConanServerManager/Config.Designer.cs
+++ b/src/ConanServerManager/Config.Designer.cs
@@ -1965,5 +1965,155 @@ namespace ServerManagerTool {
return ((string)(this["DefaultDataDirectoryName"]));
}
}
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool DiscordBotEnabled {
+ get {
+ return ((bool)(this["DiscordBotEnabled"]));
+ }
+ set {
+ this["DiscordBotEnabled"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("csm")]
+ public string DiscordBotPrefix {
+ get {
+ return ((string)(this["DiscordBotPrefix"]));
+ }
+ set {
+ this["DiscordBotPrefix"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string DiscordBotToken {
+ get {
+ return ((string)(this["DiscordBotToken"]));
+ }
+ set {
+ this["DiscordBotToken"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string DiscordBotServerId {
+ get {
+ return ((string)(this["DiscordBotServerId"]));
+ }
+ set {
+ this["DiscordBotServerId"] = value;
+ }
+ }
+
+ [global::System.Configuration.ApplicationScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("https://discord.com/developers/applications")]
+ public string DiscordBotApplyUrl {
+ get {
+ return ((string)(this["DiscordBotApplyUrl"]));
+ }
+ }
+
+ [global::System.Configuration.ApplicationScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("https://servermanagers.freeforums.net/thread/99/get-own-discord-bot")]
+ public string DiscordBotHelpUrl {
+ get {
+ return ((string)(this["DiscordBotHelpUrl"]));
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool SectionDiscordBotIsExpanded {
+ get {
+ return ((bool)(this["SectionDiscordBotIsExpanded"]));
+ }
+ set {
+ this["SectionDiscordBotIsExpanded"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("True")]
+ public bool AllowDiscordBackup {
+ get {
+ return ((bool)(this["AllowDiscordBackup"]));
+ }
+ set {
+ this["AllowDiscordBackup"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("True")]
+ public bool AllowDiscordUpdate {
+ get {
+ return ((bool)(this["AllowDiscordUpdate"]));
+ }
+ set {
+ this["AllowDiscordUpdate"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("True")]
+ public bool AllowDiscordStart {
+ get {
+ return ((bool)(this["AllowDiscordStart"]));
+ }
+ set {
+ this["AllowDiscordStart"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("True")]
+ public bool AllowDiscordRestart {
+ get {
+ return ((bool)(this["AllowDiscordRestart"]));
+ }
+ set {
+ this["AllowDiscordRestart"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("True")]
+ public bool AllowDiscordShutdown {
+ get {
+ return ((bool)(this["AllowDiscordShutdown"]));
+ }
+ set {
+ this["AllowDiscordShutdown"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("True")]
+ public bool AllowDiscordStop {
+ get {
+ return ((bool)(this["AllowDiscordStop"]));
+ }
+ set {
+ this["AllowDiscordStop"] = value;
+ }
+ }
}
}
diff --git a/src/ConanServerManager/Config.settings b/src/ConanServerManager/Config.settings
index edc93f78..8716e104 100644
--- a/src/ConanServerManager/Config.settings
+++ b/src/ConanServerManager/Config.settings
@@ -545,5 +545,44 @@
csmdata
+
+ False
+
+
+ csm
+
+
+
+
+
+
+
+
+ https://discord.com/developers/applications
+
+
+ https://servermanagers.freeforums.net/thread/99/get-own-discord-bot
+
+
+ False
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
\ No newline at end of file
diff --git a/src/ConanServerManager/Delegates/ServerStatusChangeDelegate.cs b/src/ConanServerManager/Delegates/ServerStatusChangeDelegate.cs
new file mode 100644
index 00000000..c7ab834a
--- /dev/null
+++ b/src/ConanServerManager/Delegates/ServerStatusChangeDelegate.cs
@@ -0,0 +1,6 @@
+using ServerManagerTool.Enums;
+
+namespace ServerManagerTool.Delegates
+{
+ public delegate void ServerStatusChangeDelegate(ServerStatus serverStatus);
+}
diff --git a/src/ConanServerManager/Enums/AvailabilityStatus.cs b/src/ConanServerManager/Enums/AvailabilityStatus.cs
index f2588330..81846709 100644
--- a/src/ConanServerManager/Enums/AvailabilityStatus.cs
+++ b/src/ConanServerManager/Enums/AvailabilityStatus.cs
@@ -3,9 +3,9 @@
public enum AvailabilityStatus
{
Unknown,
- NeedPublicIP,
+ SetPublicIP,
Unavailable,
- WaitingForPublication,
+ Waiting,
Available
}
}
diff --git a/src/ConanServerManager/Enums/ServerProcessType.cs b/src/ConanServerManager/Enums/ServerProcessType.cs
index 861e2081..7298dfd9 100644
--- a/src/ConanServerManager/Enums/ServerProcessType.cs
+++ b/src/ConanServerManager/Enums/ServerProcessType.cs
@@ -10,5 +10,6 @@
Backup,
Shutdown,
Restart,
+ Update,
}
}
diff --git a/src/ConanServerManager/Globalization/en-US/en-US.xaml b/src/ConanServerManager/Globalization/en-US/en-US.xaml
index c1e939f7..ffca5034 100644
--- a/src/ConanServerManager/Globalization/en-US/en-US.xaml
+++ b/src/ConanServerManager/Globalization/en-US/en-US.xaml
@@ -628,6 +628,9 @@
Reinstall SteamCMD Error
An error occured while trying to reinstall SteamCMD. This has left SteamCmd in an unstable state, try reinstalling again or please report this.\r\nException: {0}
+ Discord Bot Running Commands
+ The discord bot has one or more running commands, do you want to continue shutting down the server manager?
+
Start Server Confirmation
You are about to start the server, do you want to continue?
Shutdown Server Confirmation
@@ -768,6 +771,23 @@
Show shutdown reason with ALL shutdown messages
If enabled, the shutdown reason will be shown with all shutdown message; otherwise it will only be shown at the start of the server shutdown.
+ Enable Discord Bot
+ You will need to restart the server manager if you change any settings for the Discord Bot.
+ Token:
+ The token associated with the discord bot.
+ Server Id:
+ The id of the discord server the bot will listen to.
+ Prefix:
+ The prefix that must be used when sending a command via discord.
+ Get Token...
+ Help...
+ If enabled, the backup command can be sent from discord.
+ If enabled, the restart command can be sent from discord.
+ If enabled, the shutdown command can be sent from discord.
+ If enabled, the start command can be sent from discord.
+ If enabled, the stop command can be sent from discord.
+ If enabled, the update command can be sent from discord.
+
SMTP Email Settings
Host:
The name or IP address of the host used for SMTP transmissions.
@@ -1121,6 +1141,24 @@
If enabled, the server will be restarted even if shutdown for Auto-Restarts and Auto-Updates.
+
+ Discord Bot Details
+ Channel Id:
+ The id of the discord server channel this profile will listen to.
+ Allow Backup
+ If enabled, the profile will listen for backup commands from discord.
+ Allow Restart
+ If enabled, the profile will listen for restart commands from discord.
+ Allow Shutdown
+ If enabled, the profile will listen for shutdown commands from discord.
+ Allow Start
+ If enabled, the profile will listen for start commands from discord.
+ Allow Stop
+ If enabled, the profile will listen for stop commands from discord.
+ Allow Update
+ If enabled, the profile will listen for update commands from discord.
+
+
Server File Details
NOTE: Any changes will require a server restart to take effect.
@@ -1193,4 +1231,32 @@
There was a problem while performing the server update. This may leave your server in a incomplete state.\r\n\r\nDo you want to continue with the server start, this could cause problems?
+
+ Discord Bot Error
+ The discord bot requires a valid token so it can log into the discord server\r\nThis can be set in the global settings.
+ The discord bot prefix contains invalid characters. Only letters and numbers are allowed.
+
+ Command '{0}' has not been enabled.
+ Unknown command '{0}'.
+ Another command is currently being processed.
+ Another command '{0}' is currently running against profile '{1}'.
+ Command '{0}' has been disabled for profile '{1}'.
+
+ The '{0}' command requires a profile id.
+ Profile '{0}' was not found or is not associated with the channel.
+ Profile '{0}' is in a state '{1}' that cannot run this command.
+ Profile '{0}' is currently being updated.
+
+ Call to server '{0}' failed.
+ A backup request for server '{0}' has been sent.
+ A restart request for server '{0}' has been sent.
+ A shutdown request for server '{0}' has been sent.
+ A start request for server '{0}' has been sent.
+ A stop request for server '{0}' has been sent.
+ An update request for server '{0}' has been sent.
+
+ Count:
+ Map:
+
+
\ No newline at end of file
diff --git a/src/ConanServerManager/Lib/BranchSnapshot.cs b/src/ConanServerManager/Lib/BranchSnapshot.cs
index 2b81437d..c3b0283d 100644
--- a/src/ConanServerManager/Lib/BranchSnapshot.cs
+++ b/src/ConanServerManager/Lib/BranchSnapshot.cs
@@ -5,8 +5,30 @@ namespace ServerManagerTool.Lib
{
public class BranchSnapshot
{
+ private BranchSnapshot()
+ {
+ }
+
public string BranchName = string.Empty;
public string BranchPassword = string.Empty;
+
+ public static BranchSnapshot Create(ServerProfile profile)
+ {
+ return new BranchSnapshot
+ {
+ BranchName = profile.BranchName,
+ BranchPassword = profile.BranchPassword
+ };
+ }
+
+ public static BranchSnapshot Create(ServerProfileSnapshot profile)
+ {
+ return new BranchSnapshot
+ {
+ BranchName = profile.BranchName,
+ BranchPassword = profile.BranchPassword
+ };
+ }
}
public class BranchSnapshotComparer : IEqualityComparer
diff --git a/src/ConanServerManager/Lib/ServerApp.cs b/src/ConanServerManager/Lib/ServerApp.cs
index 7d745a88..7b5be7cb 100644
--- a/src/ConanServerManager/Lib/ServerApp.cs
+++ b/src/ConanServerManager/Lib/ServerApp.cs
@@ -3,6 +3,7 @@ using ServerManagerTool.Common;
using ServerManagerTool.Common.Lib;
using ServerManagerTool.Common.Model;
using ServerManagerTool.Common.Utils;
+using ServerManagerTool.Delegates;
using ServerManagerTool.Enums;
using ServerManagerTool.Plugin.Common;
using ServerManagerTool.Utils;
@@ -108,6 +109,7 @@ namespace ServerManagerTool.Lib
public int ShutdownInterval = Config.Default.ServerShutdown_GracePeriod;
public ProgressDelegate ProgressCallback = null;
public ProcessWindowStyle SteamCMDProcessWindowStyle = ProcessWindowStyle.Minimized;
+ public ServerStatusChangeDelegate ServerStatusChangeCallback = null;
public ServerApp(bool resetStartTime = false)
{
@@ -115,7 +117,7 @@ namespace ServerManagerTool.Lib
_startTime = DateTime.Now;
}
- private void BackupServer()
+ private void BackupServer(CancellationToken cancellationToken)
{
if (_profile == null)
{
@@ -144,58 +146,67 @@ namespace ServerManagerTool.Lib
if (_serverRunning)
{
- // check if RCON is enabled
- //if (_profile.RconEnabled)
- //{
- // try
- // {
- // emailMessage.AppendLine();
+ try
+ {
+ emailMessage.AppendLine();
- // // perform a world save
- // if (!string.IsNullOrWhiteSpace(Config.Default.ServerBackup_WorldSaveMessage))
- // {
- // ProcessAlert(AlertType.Backup, Config.Default.ServerBackup_WorldSaveMessage);
- // sent = SendMessage(Config.Default.ServerBackup_WorldSaveMessage, CancellationToken.None);
- // if (sent)
- // {
- // emailMessage.AppendLine("sent server save message.");
- // }
- // }
+ var sent = false;
- // sent = SendCommand(Config.Default.ServerSaveCommand, false);
- // if (sent)
- // {
- // emailMessage.AppendLine("sent server save command.");
- // Task.Delay(Config.Default.ServerShutdown_WorldSaveDelay * 1000).Wait();
- // }
- // }
- // catch (Exception ex)
- // {
- // CloseRconConsole();
+ // perform a world save
+ if (!string.IsNullOrWhiteSpace(Config.Default.ServerBackup_WorldSaveMessage))
+ {
+ ProcessAlert(AlertType.Backup, Config.Default.ServerBackup_WorldSaveMessage);
+ sent = SendMessageAsync(Config.Default.ServerBackup_WorldSaveMessage, CancellationToken.None).Result;
+ if (sent)
+ {
+ emailMessage.AppendLine("sent server save message.");
+ }
+ }
- // Debug.WriteLine($"RCON> {Config.Default.ServerSaveCommand} command.\r\n{ex.Message}");
- // }
- //}
- //else
- //{
- // LogProfileMessage("RCON not enabled.");
- //}
+ sent = SendCommandAsync(Config.Default.ServerSaveCommand, false).Result;
+ if (sent)
+ {
+ emailMessage.AppendLine("sent server save command.");
+ Task.Delay(Config.Default.ServerShutdown_WorldSaveDelay * 1000).Wait();
+ }
+ }
+ catch (Exception ex)
+ {
+ CloseRconConsole();
+
+ Debug.WriteLine($"RCON> {Config.Default.ServerSaveCommand} command.\r\n{ex.Message}");
+ }
}
if (ExitCode != EXITCODE_NORMALEXIT)
return;
+ if (cancellationToken.IsCancellationRequested)
+ {
+ ExitCode = EXITCODE_CANCELLED;
+ return;
+ }
// make a backup of the current profile and config files.
CreateProfileBackupArchiveFile();
if (ExitCode != EXITCODE_NORMALEXIT)
return;
+ if (cancellationToken.IsCancellationRequested)
+ {
+ ExitCode = EXITCODE_CANCELLED;
+ return;
+ }
// make a backup of the current world file.
CreateServerBackupArchiveFile(emailMessage);
if (ExitCode != EXITCODE_NORMALEXIT)
return;
+ if (cancellationToken.IsCancellationRequested)
+ {
+ ExitCode = EXITCODE_CANCELLED;
+ return;
+ }
if (Config.Default.EmailNotify_AutoBackup)
{
@@ -261,7 +272,15 @@ namespace ServerManagerTool.Lib
if (updateServer)
{
- UpgradeLocal(true, steamCmdRemoveQuit, cancellationToken, true);
+ try
+ {
+ ServerStatusChangeCallback?.Invoke(ServerStatus.Updating);
+ UpgradeLocal(true, true, steamCmdRemoveQuit, cancellationToken);
+ }
+ finally
+ {
+ ServerStatusChangeCallback?.Invoke(ServerStatus.Stopped);
+ }
}
if (ExitCode != EXITCODE_NORMALEXIT)
@@ -297,17 +316,20 @@ namespace ServerManagerTool.Lib
return;
}
- // check if the server was previously running before the update.
- if (!_serverRunning && !_profile.AutoRestartIfShutdown)
+ // check if the server was previously running.
+ if (!_serverRunning)
{
- LogProfileMessage("Server was not running, server will not be started.");
+ if (_profile.AutoRestartIfShutdown)
+ {
+ LogProfileMessage("Server was not running, server will be started as the setting to restart if shutdown is TRUE.");
+ }
+ else
+ {
+ LogProfileMessage("Server was not running, server will not be started.");
- ExitCode = EXITCODE_NORMALEXIT;
- return;
- }
- if (!_serverRunning && _profile.AutoRestartIfShutdown)
- {
- LogProfileMessage("Server was not running, server will be started as the setting to restart if shutdown is TRUE.");
+ ExitCode = EXITCODE_NORMALEXIT;
+ return;
+ }
}
// Find the server process.
@@ -380,10 +402,15 @@ namespace ServerManagerTool.Lib
_serverRunning = true;
LogProfileMessage($"Server process found PID {process.Id}.");
+ QueryMaster.Server gameServer = null;
bool sent = false;
try
{
+ // create a connection to the server
+ var endPoint = new IPEndPoint(_profile.ServerIPAddress, _profile.QueryPort);
+ gameServer = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endPoint);
+
// check if there is a shutdown reason
if (!string.IsNullOrWhiteSpace(ShutdownReason) && !Config.Default.ServerShutdown_AllMessagesShowReason)
{
@@ -394,10 +421,6 @@ namespace ServerManagerTool.Lib
}
LogProfileMessage("Starting shutdown timer...");
- if (!CheckForOnlinePlayers)
- {
- LogProfileMessage("CheckForOnlinePlayers disabled, shutdown timer will not perform online player check.");
- }
var minutesLeft = ShutdownInterval;
while (minutesLeft > 0)
@@ -420,8 +443,11 @@ namespace ServerManagerTool.Lib
{
try
{
- var gameFile = GetServerWorldFile();
- var playerCount = DataContainer.GetOnlinePlayerCount(gameFile);
+ // BH - commented out until Funcom fix the Online player status column in the world save database
+ //var gameFile = GetServerWorldFile();
+ //var playerCount = DataContainer.GetOnlinePlayerCount(gameFile);
+ var playerInfo = gameServer?.GetPlayers()?.Where(p => !string.IsNullOrWhiteSpace(p.Name?.Trim())).ToList();
+ var playerCount = playerInfo?.Count ?? -1;
// check if anyone is logged into the server
if (playerCount <= 0)
@@ -439,7 +465,8 @@ namespace ServerManagerTool.Lib
}
else
{
- Debug.WriteLine($"CheckForOnlinePlayers disabled");
+ Debug.WriteLine($"CheckForOnlinePlayers disabled, shutdown timer cancelled.");
+ break;
}
var message = string.Empty;
@@ -506,7 +533,7 @@ namespace ServerManagerTool.Lib
// BH - commented out until funcom provide a way to send a save command
// check if we need to perform a world save
- //if (_profile.RconEnabled && Config.Default.ServerShutdown_EnableWorldSave)
+ //if (Config.Default.ServerShutdown_EnableWorldSave)
//{
// try
// {
@@ -515,10 +542,10 @@ namespace ServerManagerTool.Lib
// {
// LogProfileMessage(Config.Default.ServerShutdown_WorldSaveMessage);
// ProcessAlert(AlertType.ShutdownMessage, Config.Default.ServerShutdown_WorldSaveMessage);
- // SendMessage(Config.Default.ServerShutdown_WorldSaveMessage, cancellationToken);
+ // SendMessageAsync(Config.Default.ServerShutdown_WorldSaveMessage, cancellationToken).Wait(cancellationToken);
// }
- // if (SendCommand(Config.Default.ServerSaveCommand, false))
+ // if (SendCommandAsync(Config.Default.ServerSaveCommand, false).Result)
// {
// try
// {
@@ -566,7 +593,8 @@ namespace ServerManagerTool.Lib
}
finally
{
- CloseRconConsole();
+ gameServer?.Dispose();
+ gameServer = null;
}
if (cancellationToken.IsCancellationRequested)
@@ -579,8 +607,6 @@ namespace ServerManagerTool.Lib
SendMessageAsync(Config.Default.ServerShutdown_CancelMessage, CancellationToken.None).Wait();
}
- CloseRconConsole();
-
ExitCode = EXITCODE_CANCELLED;
return;
}
@@ -654,7 +680,7 @@ namespace ServerManagerTool.Lib
ExitCode = EXITCODE_SHUTDOWN_TIMEOUT;
}
- private void UpgradeLocal(bool validate, bool steamCmdRemoveQuit, CancellationToken cancellationToken, bool updateMods)
+ private void UpgradeLocal(bool validate, bool updateMods, bool steamCmdRemoveQuit, CancellationToken cancellationToken)
{
if (_profile == null)
{
@@ -1172,7 +1198,7 @@ namespace ServerManagerTool.Lib
{
// perform a steamcmd validate to confirm all the files
LogProfileMessage("Validating server files (*new*).");
- UpgradeLocal(true, false, CancellationToken.None, false);
+ UpgradeLocal(true, false, false, CancellationToken.None);
LogProfileMessage("Validated server files (*new*).");
}
@@ -1693,17 +1719,6 @@ namespace ServerManagerTool.Lib
ExitCode = EXITCODE_NORMALEXIT;
}
- private void CloseRconConsole()
- {
- if (_rconConsole != null)
- {
- _rconConsole.Dispose();
- _rconConsole = null;
-
- Task.Delay(1000).Wait();
- }
- }
-
public void CreateProfileBackupArchiveFile(ServerProfileSnapshot profile = null)
{
var oldProfile = _profile;
@@ -2403,36 +2418,43 @@ namespace ServerManagerTool.Lib
int rconRetries = 0;
int maxRetries = retryIfFailed ? RCON_MAXRETRIES : 1;
- while (retries < maxRetries && rconRetries < RCON_MAXRETRIES)
+ try
{
- SetupRconConsole();
-
- if (_rconConsole == null)
+ while (retries < maxRetries && rconRetries < RCON_MAXRETRIES)
{
- LogProfileMessage($"RCON> {command} - attempt {rconRetries + 1} (a).", false);
- LogProfileMessage("RCON connection could not be created.", false);
- rconRetries++;
- }
- else
- {
- rconRetries = 0;
- try
- {
- _rconConsole.SendCommand(command);
- LogProfileMessage($"RCON> {command}");
+ SetupRconConsole();
- return true;
- }
- catch (Exception ex)
+ if (_rconConsole == null)
{
- LogProfileMessage($"RCON> {command} - attempt {retries + 1} (b).", false);
- LogProfileMessage($"{ex.Message}", false);
- LogProfileMessage($"{ex.StackTrace}", false);
+ LogProfileMessage($"RCON> {command} - attempt {rconRetries + 1} (a).", false);
+ LogProfileMessage("RCON connection could not be created.", false);
+ rconRetries++;
}
+ else
+ {
+ rconRetries = 0;
+ try
+ {
+ _rconConsole.SendCommand(command);
+ LogProfileMessage($"RCON> {command}");
- retries++;
+ return true;
+ }
+ catch (Exception ex)
+ {
+ LogProfileMessage($"RCON> {command} - attempt {retries + 1} (b).", false);
+ LogProfileMessage($"{ex.Message}", false);
+ LogProfileMessage($"{ex.StackTrace}", false);
+ }
+
+ retries++;
+ }
}
}
+ finally
+ {
+ CloseRconConsole();
+ }
return false;
}
@@ -2494,6 +2516,17 @@ namespace ServerManagerTool.Lib
}
}
+ private void CloseRconConsole()
+ {
+ if (_rconConsole != null)
+ {
+ _rconConsole.Dispose();
+ _rconConsole = null;
+
+ Task.Delay(1000).Wait();
+ }
+ }
+
private void SetupRconConsole()
{
CloseRconConsole();
@@ -2503,7 +2536,7 @@ namespace ServerManagerTool.Lib
try
{
- var endPoint = new IPEndPoint(IPAddress.Parse(_profile.ServerIP), _profile.RconPort);
+ var endPoint = new IPEndPoint(_profile.ServerIPAddress, _profile.RconPort);
var server = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endPoint, sendTimeOut: 10000, receiveTimeOut: 10000);
if (server == null)
{
@@ -2541,7 +2574,7 @@ namespace ServerManagerTool.Lib
}
}
- public int PerformProfileBackup(ServerProfileSnapshot profile)
+ public int PerformProfileBackup(ServerProfileSnapshot profile, CancellationToken cancellationToken)
{
_profile = profile;
@@ -2563,7 +2596,7 @@ namespace ServerManagerTool.Lib
// check if the mutex was established
if (createdNew)
{
- BackupServer();
+ BackupServer(cancellationToken);
if (ExitCode != EXITCODE_NORMALEXIT)
{
@@ -2916,7 +2949,7 @@ namespace ServerManagerTool.Lib
SendEmails = true,
ServerProcess = ServerProcessType.AutoBackup
};
- exitCodes.TryAdd(profile, app.PerformProfileBackup(profile));
+ exitCodes.TryAdd(profile, app.PerformProfileBackup(profile, CancellationToken.None));
});
foreach (var profile in _profiles.Keys)
@@ -3060,7 +3093,7 @@ namespace ServerManagerTool.Lib
if (exitCode == EXITCODE_NORMALEXIT)
{
- var branches = _profiles.Keys.Where(p => p.EnableAutoUpdate).Select(p => new BranchSnapshot() { BranchName = p.BranchName, BranchPassword = p.BranchPassword }).Distinct(new BranchSnapshotComparer()).ToArray();
+ var branches = _profiles.Keys.Where(p => p.EnableAutoUpdate).Select(p => BranchSnapshot.Create(p)).Distinct(new BranchSnapshotComparer()).ToArray();
var exitCodes = new ConcurrentDictionary();
// update the server cache for each branch
diff --git a/src/ConanServerManager/Lib/ServerProfile.cs b/src/ConanServerManager/Lib/ServerProfile.cs
index ed673611..9e6531e3 100644
--- a/src/ConanServerManager/Lib/ServerProfile.cs
+++ b/src/ConanServerManager/Lib/ServerProfile.cs
@@ -567,6 +567,64 @@ namespace ServerManagerTool.Lib
}
#endregion
+ #region Discord Bot
+ public static readonly DependencyProperty DiscordChannelIdProperty = DependencyProperty.Register(nameof(DiscordChannelId), typeof(string), typeof(ServerProfile), new PropertyMetadata(String.Empty));
+ [DataMember]
+ public string DiscordChannelId
+ {
+ get { return (string)GetValue(DiscordChannelIdProperty); }
+ set { SetValue(DiscordChannelIdProperty, value); }
+ }
+
+ public static readonly DependencyProperty AllowDiscordBackupProperty = DependencyProperty.Register(nameof(AllowDiscordBackup), typeof(bool), typeof(ServerProfile), new PropertyMetadata(true));
+ [DataMember]
+ public bool AllowDiscordBackup
+ {
+ get { return (bool)GetValue(AllowDiscordBackupProperty); }
+ set { SetValue(AllowDiscordBackupProperty, value); }
+ }
+
+ public static readonly DependencyProperty AllowDiscordRestartProperty = DependencyProperty.Register(nameof(AllowDiscordRestart), typeof(bool), typeof(ServerProfile), new PropertyMetadata(true));
+ [DataMember]
+ public bool AllowDiscordRestart
+ {
+ get { return (bool)GetValue(AllowDiscordRestartProperty); }
+ set { SetValue(AllowDiscordRestartProperty, value); }
+ }
+
+ public static readonly DependencyProperty AllowDiscordShutdownProperty = DependencyProperty.Register(nameof(AllowDiscordShutdown), typeof(bool), typeof(ServerProfile), new PropertyMetadata(true));
+ [DataMember]
+ public bool AllowDiscordShutdown
+ {
+ get { return (bool)GetValue(AllowDiscordShutdownProperty); }
+ set { SetValue(AllowDiscordShutdownProperty, value); }
+ }
+
+ public static readonly DependencyProperty AllowDiscordStartProperty = DependencyProperty.Register(nameof(AllowDiscordStart), typeof(bool), typeof(ServerProfile), new PropertyMetadata(true));
+ [DataMember]
+ public bool AllowDiscordStart
+ {
+ get { return (bool)GetValue(AllowDiscordStartProperty); }
+ set { SetValue(AllowDiscordStartProperty, value); }
+ }
+
+ public static readonly DependencyProperty AllowDiscordStopProperty = DependencyProperty.Register(nameof(AllowDiscordStop), typeof(bool), typeof(ServerProfile), new PropertyMetadata(true));
+ [DataMember]
+ public bool AllowDiscordStop
+ {
+ get { return (bool)GetValue(AllowDiscordStopProperty); }
+ set { SetValue(AllowDiscordStopProperty, value); }
+ }
+
+ public static readonly DependencyProperty AllowDiscordUpdateProperty = DependencyProperty.Register(nameof(AllowDiscordUpdate), typeof(bool), typeof(ServerProfile), new PropertyMetadata(true));
+ [DataMember]
+ public bool AllowDiscordUpdate
+ {
+ get { return (bool)GetValue(AllowDiscordUpdateProperty); }
+ set { SetValue(AllowDiscordUpdateProperty, value); }
+ }
+ #endregion
+
#region Server Files
public static readonly DependencyProperty ServerFilesBlacklistedProperty = DependencyProperty.Register(nameof(ServerFilesBlacklisted), typeof(PlayerUserList), typeof(ServerProfile), new PropertyMetadata(null));
[DataMember]
diff --git a/src/ConanServerManager/Lib/ServerProfileSnapshot.cs b/src/ConanServerManager/Lib/ServerProfileSnapshot.cs
index 543aa4c2..e74521b8 100644
--- a/src/ConanServerManager/Lib/ServerProfileSnapshot.cs
+++ b/src/ConanServerManager/Lib/ServerProfileSnapshot.cs
@@ -7,13 +7,17 @@ namespace ServerManagerTool.Lib
{
public class ServerProfileSnapshot
{
+ private ServerProfileSnapshot()
+ {
+ }
+
public string ProfileId;
public string ProfileName;
public string ServerName;
public string InstallDirectory;
public string GameFile;
public string AdminPassword;
- public string ServerIP;
+ public IPAddress ServerIPAddress;
public int ServerPort;
public int ServerPeerPort;
public int QueryPort;
@@ -56,7 +60,7 @@ namespace ServerManagerTool.Lib
InstallDirectory = profile.InstallDirectory,
GameFile = profile.GetServerWorldFile(),
AdminPassword = profile.AdminPassword,
- ServerIP = string.IsNullOrWhiteSpace(profile.ServerIP) ? IPAddress.Loopback.ToString() : profile.ServerIP.Trim(),
+ ServerIPAddress = string.IsNullOrWhiteSpace(profile.ServerIP) || !IPAddress.TryParse(profile.ServerIP.Trim(), out IPAddress ipAddress) ? IPAddress.Loopback : ipAddress,
ServerPort = profile.ServerPort,
ServerPeerPort = profile.ServerPeerPort,
QueryPort = profile.QueryPort,
diff --git a/src/ConanServerManager/Lib/ServerRuntime.cs b/src/ConanServerManager/Lib/ServerRuntime.cs
index 44ff0e60..d2370360 100644
--- a/src/ConanServerManager/Lib/ServerRuntime.cs
+++ b/src/ConanServerManager/Lib/ServerRuntime.cs
@@ -32,7 +32,7 @@ namespace ServerManagerTool.Lib
public event EventHandler StatusUpdate;
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
- private readonly GlobalizedApplication _globalizer = GlobalizedApplication.Instance;
+ private static readonly GlobalizedApplication _globalizer = GlobalizedApplication.Instance;
private readonly List profileNotifiers = new List();
private Process serverProcess;
private IAsyncDisposable updateRegistration;
@@ -167,13 +167,19 @@ namespace ServerManagerTool.Lib
ServerProfile.QueryPortProperty,
ServerProfile.ServerIPProperty,
ServerProfile.MaxPlayersProperty,
+
+ ServerProfile.ServerPasswordProperty,
+ ServerProfile.AdminPasswordProperty,
+
ServerProfile.ServerMapProperty,
ServerProfile.ServerMapSaveFileNameProperty,
ServerProfile.ServerModIdsProperty,
+
+ ServerProfile.MOTDIntervalProperty,
},
(s, p) =>
{
- if (Status == ServerStatus.Stopped || Status == ServerStatus.Uninstalled || Status == ServerStatus.Unknown)
+ if (Status == ServerStatus.Stopped || Status == ServerStatus.Uninstalled || Status == ServerStatus.Unknown || Status == ServerStatus.Updating)
{
AttachToProfileCore(profile);
}
@@ -194,16 +200,7 @@ namespace ServerManagerTool.Lib
return;
}
- if (!String.IsNullOrWhiteSpace(this.ProfileSnapshot.ServerIP) && IPAddress.TryParse(this.ProfileSnapshot.ServerIP, out IPAddress localServerIpAddress))
- {
- // Use the explicit Server IP
- localServerQueryEndPoint = new IPEndPoint(localServerIpAddress, Convert.ToUInt16(this.ProfileSnapshot.QueryPort));
- }
- else
- {
- // No Server IP specified, use Loopback
- localServerQueryEndPoint = new IPEndPoint(IPAddress.Loopback, Convert.ToUInt16(this.ProfileSnapshot.QueryPort));
- }
+ localServerQueryEndPoint = new IPEndPoint(this.ProfileSnapshot.ServerIPAddress, Convert.ToUInt16(this.ProfileSnapshot.QueryPort));
//
// Get the public endpoint for querying Steam
@@ -277,13 +274,13 @@ namespace ServerManagerTool.Lib
case WatcherServerStatus.RunningLocalCheck:
if (oldStatus != ServerStatus.Stopping)
- UpdateServerStatus(ServerStatus.Running, this.Availability != AvailabilityStatus.Available ? AvailabilityStatus.WaitingForPublication : this.Availability, oldStatus != ServerStatus.Running && oldStatus != ServerStatus.Unknown);
+ UpdateServerStatus(ServerStatus.Running, this.Availability != AvailabilityStatus.Available ? AvailabilityStatus.Waiting : this.Availability, oldStatus != ServerStatus.Running && oldStatus != ServerStatus.Unknown);
if (this.ProfileSnapshot.MOTDIntervalEnabled && this.motdIntervalTimer != null && !this.motdIntervalTimer.Enabled) this.motdIntervalTimer.Start();
break;
case WatcherServerStatus.RunningExternalCheck:
if (oldStatus != ServerStatus.Stopping)
- UpdateServerStatus(ServerStatus.Running, AvailabilityStatus.WaitingForPublication, oldStatus != ServerStatus.Running && oldStatus != ServerStatus.Unknown);
+ UpdateServerStatus(ServerStatus.Running, AvailabilityStatus.Waiting, oldStatus != ServerStatus.Running && oldStatus != ServerStatus.Unknown);
if (this.ProfileSnapshot.MOTDIntervalEnabled && this.motdIntervalTimer != null && !this.motdIntervalTimer.Enabled) this.motdIntervalTimer.Start();
break;
@@ -442,7 +439,7 @@ namespace ServerManagerTool.Lib
}
}
- UpdateServerStatus(ServerStatus.Initializing, this.Availability, false);
+ UpdateServerStatus(ServerStatus.Initializing, false);
try
{
@@ -516,7 +513,7 @@ namespace ServerManagerTool.Lib
bool isNewInstallation = this.Status == ServerStatus.Uninstalled;
- UpdateServerStatus(ServerStatus.Updating, Availability, false);
+ UpdateServerStatus(ServerStatus.Updating, false);
// Run the SteamCMD to install the server
var steamCmdFile = SteamCmdUpdater.GetSteamCmdFile(Config.Default.DataPath);
@@ -938,7 +935,7 @@ namespace ServerManagerTool.Lib
finally
{
this.lastModStatusQuery = DateTime.MinValue;
- UpdateServerStatus(ServerStatus.Stopped, Availability, false);
+ UpdateServerStatus(ServerStatus.Stopped, false);
}
}
@@ -961,6 +958,11 @@ namespace ServerManagerTool.Lib
this.lastModStatusQuery = DateTime.MinValue;
}
+ public void UpdateServerStatus(ServerStatus serverStatus, bool sendAlert)
+ {
+ UpdateServerStatus(serverStatus, Availability, sendAlert);
+ }
+
public void UpdateServerStatus(ServerStatus serverStatus, AvailabilityStatus availabilityStatus, bool sendAlert)
{
this.Status = serverStatus;
@@ -974,32 +976,29 @@ namespace ServerManagerTool.Lib
public void UpdateServerStatusString()
{
- switch (Status)
+ StatusString = GetServerStatusString(Status);
+ }
+
+ public static string GetServerStatusString(ServerStatus status)
+ {
+ switch (status)
{
case ServerStatus.Initializing:
- StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusInitializingLabel");
- break;
+ return _globalizer.GetResourceString("ServerSettings_RuntimeStatusInitializingLabel");
case ServerStatus.Running:
- StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusRunningLabel");
- break;
+ return _globalizer.GetResourceString("ServerSettings_RuntimeStatusRunningLabel");
case ServerStatus.Stopped:
- StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusStoppedLabel");
- break;
+ return _globalizer.GetResourceString("ServerSettings_RuntimeStatusStoppedLabel");
case ServerStatus.Stopping:
- StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusStoppingLabel");
- break;
+ return _globalizer.GetResourceString("ServerSettings_RuntimeStatusStoppingLabel");
case ServerStatus.Uninstalled:
- StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusUninstalledLabel");
- break;
+ return _globalizer.GetResourceString("ServerSettings_RuntimeStatusUninstalledLabel");
case ServerStatus.Unknown:
- StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusUnknownLabel");
- break;
+ return _globalizer.GetResourceString("ServerSettings_RuntimeStatusUnknownLabel");
case ServerStatus.Updating:
- StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusUpdatingLabel");
- break;
+ return _globalizer.GetResourceString("ServerSettings_RuntimeStatusUpdatingLabel");
default:
- StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusUnknownLabel");
- break;
+ return _globalizer.GetResourceString("ServerSettings_RuntimeStatusUnknownLabel");
}
}
@@ -1059,7 +1058,7 @@ namespace ServerManagerTool.Lib
try
{
- var endPoint = new IPEndPoint(IPAddress.Parse(this.ProfileSnapshot.ServerIP), this.ProfileSnapshot.RconPort);
+ var endPoint = new IPEndPoint(this.ProfileSnapshot.ServerIPAddress, this.ProfileSnapshot.RconPort);
var server = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endPoint, sendTimeOut: 10000, receiveTimeOut: 10000);
if (server == null)
diff --git a/src/ConanServerManager/Styles/Default.xaml b/src/ConanServerManager/Styles/Default.xaml
index 59ac044d..75585067 100644
--- a/src/ConanServerManager/Styles/Default.xaml
+++ b/src/ConanServerManager/Styles/Default.xaml
@@ -671,5 +671,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ConanServerManager/Utils/DiscordBotHelper.cs b/src/ConanServerManager/Utils/DiscordBotHelper.cs
new file mode 100644
index 00000000..727ac5f0
--- /dev/null
+++ b/src/ConanServerManager/Utils/DiscordBotHelper.cs
@@ -0,0 +1,735 @@
+using QueryMaster;
+using ServerManagerTool.Common.Utils;
+using ServerManagerTool.DiscordBot.Enums;
+using ServerManagerTool.Enums;
+using ServerManagerTool.Lib;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using WPFSharp.Globalizer;
+
+namespace ServerManagerTool.Utils
+{
+ internal static class DiscordBotHelper
+ {
+ private static readonly GlobalizedApplication _globalizer = GlobalizedApplication.Instance;
+ private static bool _runningCommand = false;
+
+ private static readonly Dictionary _currentProfileCommands = new Dictionary();
+
+ public static bool HasRunningCommands => _currentProfileCommands.Count > 0;
+
+ public static IList HandleDiscordCommand(CommandType commandType, string serverId, string channelId, string profileId, CancellationToken token)
+ {
+ // check if incoming values are valid
+ if (string.IsNullOrWhiteSpace(serverId) || string.IsNullOrWhiteSpace(channelId))
+ return null;
+
+ // check if the server ids match
+ if (!serverId.Equals(Config.Default.DiscordBotServerId))
+ return new List();
+
+ if (_runningCommand)
+ return new List { _globalizer.GetResourceString("DiscordBot_CommandRunning") };
+ _runningCommand = true;
+
+ try
+ {
+ switch (commandType)
+ {
+ case CommandType.Info:
+ return GetServerInfo(channelId, profileId);
+ case CommandType.List:
+ return GetServerList(channelId);
+ case CommandType.Status:
+ return GetServerStatus(channelId, profileId);
+
+ case CommandType.Backup:
+ if (Config.Default.AllowDiscordBackup)
+ return BackupServer(channelId, profileId, token);
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
+ case CommandType.Restart:
+ if (Config.Default.AllowDiscordRestart)
+ return RestartServer(channelId, profileId, token);
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
+ case CommandType.Shutdown:
+ if (Config.Default.AllowDiscordShutdown)
+ return ShutdownServer(channelId, profileId, token);
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
+ case CommandType.Stop:
+ if (Config.Default.AllowDiscordStop)
+ return StopServer(channelId, profileId, token);
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
+ case CommandType.Start:
+ if (Config.Default.AllowDiscordStart)
+ return StartServer(channelId, profileId, token);
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
+ case CommandType.Update:
+ if (Config.Default.AllowDiscordUpdate)
+ return UpdateServer(channelId, profileId, token);
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
+
+ default:
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), commandType) };
+ }
+ }
+ catch (Exception ex)
+ {
+ var message = ex.InnerException is null ? ex.Message : ex.InnerException.Message;
+ return new string[] { message };
+ }
+ finally
+ {
+ _runningCommand = false;
+ }
+ }
+
+ public static string HandleTranslation(string translationKey)
+ {
+ return string.IsNullOrWhiteSpace(translationKey) ? string.Empty : _globalizer.GetResourceString(translationKey) ?? translationKey;
+ }
+
+ private static IList GetServerInfo(string channelId, string profileId)
+ {
+ if (string.IsNullOrWhiteSpace(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Info) };
+ }
+
+ // check if another command is being run against the profile
+ if (_currentProfileCommands.ContainsKey(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) };
+ }
+ _currentProfileCommands.Add(profileId, CommandType.Info);
+
+ try
+ {
+ var serverName = string.Empty;
+ var serverIp = IPAddress.Loopback;
+ var queryPort = 0;
+
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+
+ if (server is null)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId));
+ }
+
+ switch (server.Runtime.Status)
+ {
+ case ServerStatus.Initializing:
+ case ServerStatus.Stopping:
+ case ServerStatus.Stopped:
+ case ServerStatus.Uninstalled:
+ case ServerStatus.Unknown:
+ case ServerStatus.Updating:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString));
+ }
+
+ serverName = server.Profile.ServerName;
+ if (!string.IsNullOrWhiteSpace(server.Profile.ServerIP))
+ {
+ IPAddress.TryParse(server.Profile.ServerIP, out serverIp);
+ }
+ queryPort = server.Profile.QueryPort;
+ }).Wait();
+
+ List response = new List();
+
+ try
+ {
+ using (var gameServer = ServerQuery.GetServerInstance(EngineType.Source, new IPEndPoint(serverIp, queryPort)))
+ {
+ var info = gameServer?.GetInfo();
+ if (info is null)
+ {
+ response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_InfoFailed"), serverName));
+ }
+ else
+ {
+ var mapName = _globalizer.GetResourceString($"Map_{info.Map}") ?? info.Map;
+ response.Add($"```{info.Name}\n{_globalizer.GetResourceString("DiscordBot_MapLabel")} {mapName}\n{_globalizer.GetResourceString("ServerSettings_PlayersLabel")} {info.Players} / {info.MaxPlayers}```");
+ }
+ }
+ }
+ catch (Exception)
+ {
+ response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_InfoFailed"), serverName));
+ }
+
+ return response;
+ }
+ finally
+ {
+ _currentProfileCommands.Remove(profileId);
+ }
+ }
+
+ private static IList GetServerList(string channelId)
+ {
+ List response = new List();
+
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var serverList = ServerManager.Instance.Servers.Where(s => Equals(channelId, s.Profile.DiscordChannelId));
+
+ response.Add($"**{_globalizer.GetResourceString("DiscordBot_CountLabel")}** {serverList.Count()}");
+ foreach (var server in serverList)
+ {
+ response.Add($"```{_globalizer.GetResourceString("ServerSettings_ProfileIdLabel")} {server.Profile.ProfileID}\n{_globalizer.GetResourceString("ServerSettings_ProfileLabel")} {server.Profile.ProfileName}\n{_globalizer.GetResourceString("ServerSettings_ServerNameLabel")} {server.Profile.ServerName}```");
+ }
+ }).Wait();
+
+ return response;
+ }
+
+ private static IList GetServerStatus(string channelId, string profileId)
+ {
+ List response = new List();
+
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var serverList = ServerManager.Instance.Servers.Where(s => Equals(channelId, s.Profile.DiscordChannelId) && (string.IsNullOrWhiteSpace(profileId) || Equals(profileId, s.Profile.ProfileID)));
+
+ response.Add($"**{_globalizer.GetResourceString("DiscordBot_CountLabel")}** {serverList.Count()}");
+ foreach (var server in serverList)
+ {
+ response.Add($"```{_globalizer.GetResourceString("ServerSettings_ProfileLabel")} {server.Profile.ProfileName}\n{_globalizer.GetResourceString("ServerSettings_ServerNameLabel")} {server.Profile.ServerName}\n{_globalizer.GetResourceString("ServerSettings_StatusLabel")} {server.Runtime.StatusString}\n{_globalizer.GetResourceString("ServerSettings_AvailabilityLabel")} {_globalizer.GetResourceString($"ServerSettings_Availability_{server.Runtime.Availability}")}```");
+ }
+ }).Wait();
+
+ return response;
+ }
+
+ private static IList BackupServer(string channelId, string profileId, CancellationToken token)
+ {
+ if (string.IsNullOrWhiteSpace(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Backup) };
+ }
+
+ // check if another command is being run against the profile
+ if (_currentProfileCommands.ContainsKey(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) };
+ }
+ _currentProfileCommands.Add(profileId, CommandType.Backup);
+
+ ServerProfileSnapshot profile = null;
+ Task task = null;
+
+ try
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+
+ if (server is null)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId));
+ }
+
+ if (!server.Profile.AllowDiscordBackup)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_CommandDisabledProfile"), CommandType.Backup, profileId));
+ }
+
+ switch (server.Runtime.Status)
+ {
+ case ServerStatus.Initializing:
+ case ServerStatus.Stopping:
+ case ServerStatus.Uninstalled:
+ case ServerStatus.Unknown:
+ case ServerStatus.Updating:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString));
+ }
+
+ profile = ServerProfileSnapshot.Create(server.Profile);
+ }).Wait();
+
+ List response = new List();
+
+ var app = new ServerApp(true)
+ {
+ DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup,
+ OutputLogs = false,
+ SendAlerts = true,
+ SendEmails = false,
+ ServerProcess = ServerProcessType.Backup,
+ ServerStatusChangeCallback = (ServerStatus serverStatus) =>
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+ server.Runtime.UpdateServerStatus(serverStatus, true);
+ }).Wait();
+ }
+ };
+
+ task = Task.Run(() =>
+ {
+ app.PerformProfileBackup(profile, token);
+ _currentProfileCommands.Remove(profileId);
+ });
+
+ response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_BackupRequested"), profile.ServerName));
+
+ return response;
+ }
+ finally
+ {
+ if (task is null)
+ {
+ _currentProfileCommands.Remove(profileId);
+ }
+ }
+ }
+
+ private static IList RestartServer(string channelId, string profileId, CancellationToken token)
+ {
+ if (string.IsNullOrWhiteSpace(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Restart) };
+ }
+
+ // check if another command is being run against the profile
+ if (_currentProfileCommands.ContainsKey(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) };
+ }
+ _currentProfileCommands.Add(profileId, CommandType.Restart);
+
+ ServerProfileSnapshot profile = null;
+ Task task = null;
+
+ try
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+
+ if (server is null)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId));
+ }
+
+ if (!server.Profile.AllowDiscordRestart)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_CommandDisabledProfile"), CommandType.Restart, profileId));
+ }
+
+ switch (server.Runtime.Status)
+ {
+ case ServerStatus.Initializing:
+ case ServerStatus.Stopping:
+ case ServerStatus.Uninstalled:
+ case ServerStatus.Unknown:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString));
+
+ case ServerStatus.Updating:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileUpdating"), profileId));
+ }
+
+ profile = ServerProfileSnapshot.Create(server.Profile);
+ profile.AutoRestartIfShutdown = true;
+ }).Wait();
+
+ List response = new List();
+
+ var app = new ServerApp(true)
+ {
+ DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup,
+ OutputLogs = false,
+ SendAlerts = true,
+ SendEmails = false,
+ ServerProcess = ServerProcessType.Restart,
+ ServerStatusChangeCallback = (ServerStatus serverStatus) =>
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+ server.Runtime.UpdateServerStatus(serverStatus, true);
+ }).Wait();
+ }
+ };
+
+ task = Task.Run(() =>
+ {
+ app.PerformProfileShutdown(profile, true, false, false, false, token);
+ _currentProfileCommands.Remove(profileId);
+ });
+
+ response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_RestartRequested"), profile.ServerName));
+
+ return response;
+ }
+ finally
+ {
+ if (task is null)
+ {
+ _currentProfileCommands.Remove(profileId);
+ }
+ }
+ }
+
+ private static IList ShutdownServer(string channelId, string profileId, CancellationToken token)
+ {
+ if (string.IsNullOrWhiteSpace(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Shutdown) };
+ }
+
+ // check if another command is being run against the profile
+ if (_currentProfileCommands.ContainsKey(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) };
+ }
+ _currentProfileCommands.Add(profileId, CommandType.Shutdown);
+
+ ServerProfileSnapshot profile = null;
+ Task task = null;
+
+ try
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+
+ if (server is null)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId));
+ }
+
+ if (!server.Profile.AllowDiscordShutdown)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_CommandDisabledProfile"), CommandType.Shutdown, profileId));
+ }
+
+ switch (server.Runtime.Status)
+ {
+ case ServerStatus.Initializing:
+ case ServerStatus.Stopping:
+ case ServerStatus.Stopped:
+ case ServerStatus.Uninstalled:
+ case ServerStatus.Unknown:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString));
+
+ case ServerStatus.Updating:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileUpdating"), profileId));
+ }
+
+ profile = ServerProfileSnapshot.Create(server.Profile);
+ }).Wait();
+
+ List response = new List();
+
+ var app = new ServerApp(true)
+ {
+ DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup,
+ OutputLogs = false,
+ SendAlerts = true,
+ SendEmails = false,
+ ServerProcess = ServerProcessType.Shutdown,
+ ServerStatusChangeCallback = (ServerStatus serverStatus) =>
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+ server.Runtime.UpdateServerStatus(serverStatus, true);
+ }).Wait();
+ }
+ };
+
+ task = Task.Run(() =>
+ {
+ app.PerformProfileShutdown(profile, false, false, false, false, token);
+ _currentProfileCommands.Remove(profileId);
+ });
+
+ response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_ShutdownRequested"), profile.ServerName));
+
+ return response;
+ }
+ finally
+ {
+ if (task is null)
+ {
+ _currentProfileCommands.Remove(profileId);
+ }
+ }
+ }
+
+ private static IList StopServer(string channelId, string profileId, CancellationToken token)
+ {
+ if (string.IsNullOrWhiteSpace(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Stop) };
+ }
+
+ // check if another command is being run against the profile
+ if (_currentProfileCommands.ContainsKey(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) };
+ }
+ _currentProfileCommands.Add(profileId, CommandType.Stop);
+
+ ServerProfileSnapshot profile = null;
+ Task task = null;
+
+ try
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+
+ if (server is null)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId));
+ }
+
+ if (!server.Profile.AllowDiscordStop)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_CommandDisabledProfile"), CommandType.Stop, profileId));
+ }
+
+ switch (server.Runtime.Status)
+ {
+ case ServerStatus.Initializing:
+ case ServerStatus.Stopping:
+ case ServerStatus.Stopped:
+ case ServerStatus.Uninstalled:
+ case ServerStatus.Unknown:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString));
+
+ case ServerStatus.Updating:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileUpdating"), profileId));
+ }
+
+ profile = ServerProfileSnapshot.Create(server.Profile);
+ }).Wait();
+
+ List response = new List();
+
+ var app = new ServerApp(true)
+ {
+ DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup,
+ OutputLogs = false,
+ SendAlerts = true,
+ SendEmails = false,
+ ServerProcess = ServerProcessType.Shutdown,
+ ShutdownInterval = 0,
+ ServerStatusChangeCallback = (ServerStatus serverStatus) =>
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+ server.Runtime.UpdateServerStatus(serverStatus, true);
+ }).Wait();
+ }
+ };
+
+ task = Task.Run(() =>
+ {
+ app.PerformProfileShutdown(profile, false, false, false, false, token);
+ _currentProfileCommands.Remove(profileId);
+ });
+
+ response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_StopRequested"), profile.ServerName));
+
+ return response;
+ }
+ finally
+ {
+ if (task is null)
+ {
+ _currentProfileCommands.Remove(profileId);
+ }
+ }
+ }
+
+ private static IList StartServer(string channelId, string profileId, CancellationToken token)
+ {
+ if (string.IsNullOrWhiteSpace(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Start) };
+ }
+
+ // check if another command is being run against the profile
+ if (_currentProfileCommands.ContainsKey(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) };
+ }
+ _currentProfileCommands.Add(profileId, CommandType.Start);
+
+ ServerProfileSnapshot profile = null;
+ Task task = null;
+
+ try
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+
+ if (server is null)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId));
+ }
+
+ if (!server.Profile.AllowDiscordStart)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_CommandDisabledProfile"), CommandType.Start, profileId));
+ }
+
+ switch (server.Runtime.Status)
+ {
+ case ServerStatus.Initializing:
+ case ServerStatus.Stopping:
+ case ServerStatus.Running:
+ case ServerStatus.Uninstalled:
+ case ServerStatus.Unknown:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString));
+
+ case ServerStatus.Updating:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileUpdating"), profileId));
+ }
+
+ profile = ServerProfileSnapshot.Create(server.Profile);
+ profile.AutoRestartIfShutdown = true;
+ }).Wait();
+
+ List response = new List();
+
+ var app = new ServerApp(true)
+ {
+ DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup,
+ OutputLogs = false,
+ SendAlerts = true,
+ SendEmails = false,
+ ServerProcess = ServerProcessType.Restart,
+ ServerStatusChangeCallback = (ServerStatus serverStatus) =>
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+ server.Runtime.UpdateServerStatus(serverStatus, true);
+ }).Wait();
+ }
+ };
+
+ task = Task.Run(() =>
+ {
+ app.PerformProfileShutdown(profile, true, false, false, false, token);
+ _currentProfileCommands.Remove(profileId);
+ });
+
+ response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_StartRequested"), profile.ServerName));
+
+ return response;
+ }
+ finally
+ {
+ if (task is null)
+ {
+ _currentProfileCommands.Remove(profileId);
+ }
+ }
+ }
+
+ private static IList UpdateServer(string channelId, string profileId, CancellationToken token)
+ {
+ if (string.IsNullOrWhiteSpace(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Update) };
+ }
+
+ // check if another command is being run against the profile
+ if (_currentProfileCommands.ContainsKey(profileId))
+ {
+ return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) };
+ }
+ _currentProfileCommands.Add(profileId, CommandType.Update);
+
+ ServerProfileSnapshot profile = null;
+ bool performRestart = false;
+ Task task = null;
+
+ try
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+
+ if (server is null)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId));
+ }
+
+ if (!server.Profile.AllowDiscordUpdate)
+ {
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_CommandDisabledProfile"), CommandType.Update, profileId));
+ }
+
+ switch (server.Runtime.Status)
+ {
+ case ServerStatus.Initializing:
+ case ServerStatus.Stopping:
+ case ServerStatus.Unknown:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString));
+
+ case ServerStatus.Running:
+ performRestart = true;
+ break;
+
+ case ServerStatus.Updating:
+ throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileUpdating"), profileId));
+ }
+
+ profile = ServerProfileSnapshot.Create(server.Profile);
+ }).Wait();
+
+ List response = new List();
+
+ var app = new ServerApp(true)
+ {
+ DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup,
+ OutputLogs = false,
+ SendAlerts = true,
+ SendEmails = false,
+ ServerProcess = ServerProcessType.Update,
+ ServerStatusChangeCallback = (ServerStatus serverStatus) =>
+ {
+ TaskUtils.RunOnUIThreadAsync(() =>
+ {
+ var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID));
+ server.Runtime.UpdateServerStatus(serverStatus, true);
+ }).Wait();
+ }
+ };
+
+ task = Task.Run(() =>
+ {
+ app.PerformProfileShutdown(profile, performRestart, true, false, false, token);
+ _currentProfileCommands.Remove(profileId);
+ });
+
+ response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_UpdateRequested"), profile.ServerName));
+
+ return response;
+ }
+ finally
+ {
+ if (task is null)
+ {
+ _currentProfileCommands.Remove(profileId);
+ }
+ }
+ }
+ }
+}
diff --git a/src/ConanServerManager/Utils/DiscordPluginHelper.cs b/src/ConanServerManager/Utils/DiscordPluginHelper.cs
new file mode 100644
index 00000000..7ec9ee93
--- /dev/null
+++ b/src/ConanServerManager/Utils/DiscordPluginHelper.cs
@@ -0,0 +1,18 @@
+using ServerManagerTool.Lib;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace ServerManagerTool.Utils
+{
+ internal static class DiscordPluginHelper
+ {
+ public static IList FetchProfiles()
+ {
+ return ServerManager.Instance.Servers.Select(s => new ServerManagerTool.Plugin.Common.Lib.Profile()
+ {
+ ProfileName = s?.Profile?.ProfileName ?? string.Empty,
+ InstallationFolder = s?.Profile?.InstallDirectory ?? string.Empty
+ }).ToList();
+ }
+ }
+}
diff --git a/src/ConanServerManager/Windows/GlobalSettingsControl.xaml b/src/ConanServerManager/Windows/GlobalSettingsControl.xaml
index 1b02776d..a52e7497 100644
--- a/src/ConanServerManager/Windows/GlobalSettingsControl.xaml
+++ b/src/ConanServerManager/Windows/GlobalSettingsControl.xaml
@@ -527,6 +527,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -538,13 +587,13 @@
-
+
-
+
-
+
@@ -569,7 +618,7 @@
-
+
@@ -593,7 +642,7 @@
-
+
diff --git a/src/ConanServerManager/Windows/GlobalSettingsControl.xaml.cs b/src/ConanServerManager/Windows/GlobalSettingsControl.xaml.cs
index df398ec8..2b59e598 100644
--- a/src/ConanServerManager/Windows/GlobalSettingsControl.xaml.cs
+++ b/src/ConanServerManager/Windows/GlobalSettingsControl.xaml.cs
@@ -257,6 +257,16 @@ namespace ServerManagerTool
Process.Start(Config.Default.SteamWebAPIKeyHelpUrl);
}
+ private void DiscordBotApply_Click(object sender, RoutedEventArgs e)
+ {
+ Process.Start(Config.Default.DiscordBotApplyUrl);
+ }
+
+ private void DiscordBotHelp_Click(object sender, RoutedEventArgs e)
+ {
+ Process.Start(Config.Default.DiscordBotHelpUrl);
+ }
+
private async void SteamCMDAuthenticate_Click(object sender, RoutedEventArgs e)
{
var cursor = this.Cursor;
@@ -335,6 +345,8 @@ namespace ServerManagerTool
textBox = SteamAPIKeyTextBox;
if (Equals(hideTextBox, HideEmailPasswordTextBox))
textBox = EmailPasswordTextBox;
+ if (Equals(hideTextBox, HideDiscordBotTokenTextBox))
+ textBox = DiscordBotTokenTextBox;
if (textBox != null)
{
@@ -358,6 +370,8 @@ namespace ServerManagerTool
hideTextBox = HideSteamAPIKeyTextBox;
if (textBox == EmailPasswordTextBox)
hideTextBox = HideEmailPasswordTextBox;
+ if (textBox == DiscordBotTokenTextBox)
+ hideTextBox = HideDiscordBotTokenTextBox;
if (hideTextBox != null)
{
diff --git a/src/ConanServerManager/Windows/MainWindow.xaml b/src/ConanServerManager/Windows/MainWindow.xaml
index e83bab9b..8a4cd1d7 100644
--- a/src/ConanServerManager/Windows/MainWindow.xaml
+++ b/src/ConanServerManager/Windows/MainWindow.xaml
@@ -10,7 +10,7 @@
xmlns:com="clr-namespace:ServerManagerTool.Common;assembly=ServerManager.Common"
xmlns:enum="clr-namespace:ServerManagerTool.Enums"
MinWidth="900" MinHeight="600" Width="1100" Height="900" Left="50" Top="50" WindowState="Normal"
- Loaded="Window_Loaded" SizeChanged="Window_SizeChanged" StateChanged="Window_StateChanged" LocationChanged="Window_LocationChanged"
+ Loaded="MainWindow_Loaded" SizeChanged="MainWindow_SizeChanged" StateChanged="MainWindow_StateChanged" LocationChanged="MainWindow_LocationChanged"
Name="Main" Icon="../Art/favicon.ico" Title="{DynamicResource MainWindow_Title}">
diff --git a/src/ConanServerManager/Windows/MainWindow.xaml.cs b/src/ConanServerManager/Windows/MainWindow.xaml.cs
index be46d9b5..cbd30acb 100644
--- a/src/ConanServerManager/Windows/MainWindow.xaml.cs
+++ b/src/ConanServerManager/Windows/MainWindow.xaml.cs
@@ -6,6 +6,7 @@ using ServerManagerTool.Common.Utils;
using ServerManagerTool.Enums;
using ServerManagerTool.Lib;
using ServerManagerTool.Plugin.Common;
+using ServerManagerTool.Utils;
using ServerManagerTool.Windows;
using System;
using System.Diagnostics;
@@ -162,7 +163,7 @@ namespace ServerManagerTool
GlobalizedApplication.Instance.GlobalizationManager.ResourceDictionaryChangedEvent += ResourceDictionaryChangedEvent;
}
- private void Window_Loaded(object sender, RoutedEventArgs e)
+ private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
//
// Kick off the initialization.
@@ -192,15 +193,7 @@ namespace ServerManagerTool
this.scheduledTaskChecker.PostAction(CheckForScheduledTasks).DoNotWait();
}
- private void Window_Closed(object sender, EventArgs e)
- {
- if (sender is Window window)
- window.Closed -= Window_Closed;
-
- this.Activate();
- }
-
- private void Window_LocationChanged(object sender, EventArgs e)
+ private void MainWindow_LocationChanged(object sender, EventArgs e)
{
if (this.WindowState == WindowState.Normal)
{
@@ -209,7 +202,7 @@ namespace ServerManagerTool
}
}
- private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
+ private void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (this.WindowState == WindowState.Normal)
{
@@ -218,7 +211,7 @@ namespace ServerManagerTool
}
}
- private void Window_StateChanged(object sender, EventArgs e)
+ private void MainWindow_StateChanged(object sender, EventArgs e)
{
if (Config.Default.MainWindow_MinimizeToTray && this.WindowState == WindowState.Minimized)
{
@@ -226,8 +219,26 @@ namespace ServerManagerTool
}
}
+ private void Window_Closed(object sender, EventArgs e)
+ {
+ if (sender is Window window)
+ window.Closed -= Window_Closed;
+
+ this.Activate();
+ }
+
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
+ if (DiscordBotHelper.HasRunningCommands)
+ {
+ var result = MessageBox.Show(_globalizer.GetResourceString("MainWindow_DiscordBot_RunningCommandsLabel"), _globalizer.GetResourceString("MainWindow_DiscordBot_RunningCommandsTitle"), MessageBoxButton.YesNo, MessageBoxImage.Question);
+ if (result == MessageBoxResult.No)
+ {
+ e.Cancel = true;
+ return;
+ }
+ }
+
base.OnClosing(e);
RconWindow.CloseAllWindows();
PlayerListWindow.CloseAllWindows();
diff --git a/src/ConanServerManager/Windows/RconWindow.xaml.cs b/src/ConanServerManager/Windows/RconWindow.xaml.cs
index f426ad8c..21f11ed4 100644
--- a/src/ConanServerManager/Windows/RconWindow.xaml.cs
+++ b/src/ConanServerManager/Windows/RconWindow.xaml.cs
@@ -542,7 +542,7 @@ namespace ServerManagerTool
ProfileId = server.Runtime.ProfileSnapshot.ProfileId,
ProfileName = server.Runtime.ProfileSnapshot.ProfileName,
MaxPlayers = server.Runtime.MaxPlayers,
- RconHost = server.Runtime.ProfileSnapshot.ServerIP,
+ RconHost = server.Runtime.ProfileSnapshot.ServerIPAddress.ToString(),
RconPort = server.Runtime.ProfileSnapshot.RconPort,
RconPassword = server.Runtime.ProfileSnapshot.RconPassword,
});
diff --git a/src/ConanServerManager/Windows/ServerMonitorWindow.xaml.cs b/src/ConanServerManager/Windows/ServerMonitorWindow.xaml.cs
index 7b7990c4..c9e0c693 100644
--- a/src/ConanServerManager/Windows/ServerMonitorWindow.xaml.cs
+++ b/src/ConanServerManager/Windows/ServerMonitorWindow.xaml.cs
@@ -182,7 +182,7 @@ namespace ServerManagerTool.Windows
var profile = ServerProfileSnapshot.Create(server.Profile);
- var exitCode = await Task.Run(() => app.PerformProfileBackup(profile));
+ var exitCode = await Task.Run(() => app.PerformProfileBackup(profile, CancellationToken.None));
if (exitCode != ServerApp.EXITCODE_NORMALEXIT && exitCode != ServerApp.EXITCODE_CANCELLED)
{
throw new ApplicationException($"An error occured during the backup process - ExitCode: {exitCode}");
@@ -620,7 +620,7 @@ namespace ServerManagerTool.Windows
await Task.Delay(1000);
- var branch = new BranchSnapshot() { BranchName = serverProfile.BranchName, BranchPassword = serverProfile.BranchPassword };
+ var branch = BranchSnapshot.Create(serverProfile);
return await server.UpgradeAsync(_upgradeCancellationSource.Token, updateServer, branch, true, updateMods, (p, m, n) => { TaskUtils.RunOnUIThreadAsync(() => { window?.AddMessage(m, n); }).DoNotWait(); });
}
else
diff --git a/src/ConanServerManager/Windows/ServerSettingsControl.xaml b/src/ConanServerManager/Windows/ServerSettingsControl.xaml
index b636e8b1..bfa7cb8f 100644
--- a/src/ConanServerManager/Windows/ServerSettingsControl.xaml
+++ b/src/ConanServerManager/Windows/ServerSettingsControl.xaml
@@ -282,13 +282,13 @@
-
+
-
+
@@ -1062,6 +1062,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ConanServerManager/Windows/ServerSettingsControl.xaml.cs b/src/ConanServerManager/Windows/ServerSettingsControl.xaml.cs
index 24825056..99e2ed60 100644
--- a/src/ConanServerManager/Windows/ServerSettingsControl.xaml.cs
+++ b/src/ConanServerManager/Windows/ServerSettingsControl.xaml.cs
@@ -834,7 +834,7 @@ namespace ServerManagerTool
var profile = ServerProfileSnapshot.Create(Server.Profile);
- var exitCode = await Task.Run(() => app.PerformProfileBackup(profile));
+ var exitCode = await Task.Run(() => app.PerformProfileBackup(profile, CancellationToken.None));
if (exitCode != ServerApp.EXITCODE_NORMALEXIT && exitCode != ServerApp.EXITCODE_CANCELLED)
throw new ApplicationException($"An error occured during the backup process - ExitCode: {exitCode}");
@@ -1449,7 +1449,7 @@ namespace ServerManagerTool
await Task.Delay(1000);
- var branch = new BranchSnapshot() { BranchName = this.Server.Profile.BranchName, BranchPassword = this.Server.Profile.BranchPassword };
+ var branch = BranchSnapshot.Create(this.Server.Profile);
return await this.Server.UpgradeAsync(_upgradeCancellationSource.Token, updateServer, branch, true, updateMods, (p, m, n) => { TaskUtils.RunOnUIThreadAsync(() => { window?.AddMessage(m, n); }).DoNotWait(); });
}
else
diff --git a/src/ServerManager.Discord/Delegates/HandleCommandDelegate.cs b/src/ServerManager.Discord/Delegates/HandleCommandDelegate.cs
new file mode 100644
index 00000000..e4301ce4
--- /dev/null
+++ b/src/ServerManager.Discord/Delegates/HandleCommandDelegate.cs
@@ -0,0 +1,8 @@
+using ServerManagerTool.DiscordBot.Enums;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace ServerManagerTool.DiscordBot.Delegates
+{
+ public delegate IList HandleCommandDelegate(CommandType commandType, string serverId, string channelId, string profileId, CancellationToken token);
+}
diff --git a/src/ServerManager.Discord/Delegates/HandleTranslationDelegate.cs b/src/ServerManager.Discord/Delegates/HandleTranslationDelegate.cs
new file mode 100644
index 00000000..181930ec
--- /dev/null
+++ b/src/ServerManager.Discord/Delegates/HandleTranslationDelegate.cs
@@ -0,0 +1,4 @@
+namespace ServerManagerTool.DiscordBot.Delegates
+{
+ public delegate string HandleTranslationDelegate(string translationKey);
+}
diff --git a/src/ServerManager.Discord/DiscordBot.cs b/src/ServerManager.Discord/DiscordBot.cs
new file mode 100644
index 00000000..63cc1c7a
--- /dev/null
+++ b/src/ServerManager.Discord/DiscordBot.cs
@@ -0,0 +1,9 @@
+using ServerManagerTool.DiscordBot.Delegates;
+
+namespace ServerManagerTool.DiscordBot
+{
+ public static class DiscordBot
+ {
+ public const string PREFIX_DELIMITER = "!";
+ }
+}
diff --git a/src/ServerManager.Discord/Enums/CommandType.cs b/src/ServerManager.Discord/Enums/CommandType.cs
new file mode 100644
index 00000000..1cc4be15
--- /dev/null
+++ b/src/ServerManager.Discord/Enums/CommandType.cs
@@ -0,0 +1,16 @@
+namespace ServerManagerTool.DiscordBot.Enums
+{
+ public enum CommandType
+ {
+ Info,
+ List,
+ Status,
+
+ Backup,
+ Restart,
+ Shutdown,
+ Start,
+ Stop,
+ Update,
+ }
+}
diff --git a/src/ServerManager.Discord/Interfaces/IServerManagerBot.cs b/src/ServerManager.Discord/Interfaces/IServerManagerBot.cs
new file mode 100644
index 00000000..f44c1878
--- /dev/null
+++ b/src/ServerManager.Discord/Interfaces/IServerManagerBot.cs
@@ -0,0 +1,13 @@
+using ServerManagerTool.DiscordBot.Delegates;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ServerManagerTool.DiscordBot.Interfaces
+{
+ public interface IServerManagerBot
+ {
+ CancellationToken Token { get; }
+
+ Task StartAsync(string discordToken, string commandPrefix, string dataDirectory, HandleCommandDelegate handleCommandCallback, HandleTranslationDelegate handleTranslationCallback, CancellationToken token);
+ }
+}
diff --git a/src/ServerManager.Discord/Modules/HelpModule.cs b/src/ServerManager.Discord/Modules/HelpModule.cs
new file mode 100644
index 00000000..003da7a1
--- /dev/null
+++ b/src/ServerManager.Discord/Modules/HelpModule.cs
@@ -0,0 +1,153 @@
+using Discord;
+using Discord.Commands;
+using Microsoft.Extensions.Configuration;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace ServerManagerTool.DiscordBot.Modules
+{
+ [Name("Help")]
+ public sealed class HelpModule : ModuleBase
+ {
+ private const int MAX_VALUE_LENGTH = 1024;
+
+ private readonly CommandService _service;
+ private readonly IConfigurationRoot _config;
+ private readonly IServiceProvider _services;
+
+ public HelpModule(CommandService service, IConfigurationRoot config, IServiceProvider services)
+ {
+ _service = service;
+ _config = config;
+ _services = services;
+ }
+
+ [Command("help")]
+ [Summary("Provides a list of available commands")]
+ [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)]
+ public async Task HelpAsync()
+ {
+ var prefix = _config["DiscordSettings:Prefix"];
+
+ var builder = new EmbedBuilder()
+ {
+ Color = new Color(114, 137, 218),
+ Description = "These are the commands you can use"
+ };
+
+ foreach (var module in _service.Modules)
+ {
+ var moduleName = module.Name;
+
+ // create the list of accessible commands
+ var commands = new List(module.Commands.Count);
+
+ foreach (var cmd in module.Commands)
+ {
+ var result = await cmd.CheckPreconditionsAsync(Context, _services);
+ if (!result.IsSuccess)
+ {
+ continue;
+ }
+
+ commands.Add($"{prefix}{cmd.Aliases.First()}");
+ }
+
+ // remove all duplicate commands
+ commands = commands.Distinct().ToList();
+
+ var commandString = string.Empty;
+
+ foreach (var command in commands)
+ {
+ if (string.IsNullOrWhiteSpace(command))
+ {
+ continue;
+ }
+
+ var commandToAdd = $"{command}\n";
+
+ if (commandString.Length + commandToAdd.Length > MAX_VALUE_LENGTH)
+ {
+ // force the output, string would be too long
+ builder.AddField(x =>
+ {
+ x.Name = moduleName;
+ x.Value = $"{commandString}\n";
+ x.IsInline = false;
+ });
+
+ // reset the module name and command string
+ moduleName = $"{module.Name} cont.";
+ commandString = string.Empty;
+ }
+
+ commandString += commandToAdd;
+ }
+
+ if (!string.IsNullOrWhiteSpace(commandString))
+ {
+ builder.AddField(x =>
+ {
+ x.Name = moduleName;
+ x.Value = $"{commandString}\n";
+ x.IsInline = false;
+ });
+ }
+ }
+
+ await ReplyAsync(string.Empty, false, builder.Build());
+ }
+
+ [Command("help")]
+ [Summary("Searches a list of available commands")]
+ [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)]
+ public async Task HelpAsync(string command)
+ {
+ var searchResults = _service.Search(Context, command);
+
+ if (!searchResults.IsSuccess)
+ {
+ await ReplyAsync($"Sorry, couldn't find a command like **{command}**.");
+ return;
+ }
+
+ var prefix = _config["DiscordSettings:Prefix"];
+
+ var builder = new EmbedBuilder()
+ {
+ Color = new Color(114, 137, 218),
+ Description = $"Here are some commands like **{command}**"
+ };
+
+ foreach (var match in searchResults.Commands)
+ {
+ var cmd = match.Command;
+
+ var result = await cmd.CheckPreconditionsAsync(Context, _services);
+ if (!result.IsSuccess)
+ {
+ continue;
+ }
+
+ var usage = $"{prefix}{cmd.Aliases.First()}";
+ if (cmd.Parameters.Count > 0)
+ {
+ usage += $" {string.Join(" ", cmd.Parameters.Select(p => p.Name))}";
+ }
+ usage += $"\n";
+
+ builder.AddField(x =>
+ {
+ x.Name = string.Join(", ", cmd.Aliases);
+ x.Value = $"Summary: {cmd.Summary}\nUsage: {usage}";
+ x.IsInline = false;
+ });
+ }
+
+ await ReplyAsync(string.Empty, false, builder.Build());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ServerManager.Discord/Modules/ServerCommandModule.cs b/src/ServerManager.Discord/Modules/ServerCommandModule.cs
new file mode 100644
index 00000000..ea993166
--- /dev/null
+++ b/src/ServerManager.Discord/Modules/ServerCommandModule.cs
@@ -0,0 +1,216 @@
+using Discord;
+using Discord.Addons.Interactive;
+using Discord.Commands;
+using Microsoft.Extensions.Configuration;
+using ServerManagerTool.DiscordBot.Delegates;
+using ServerManagerTool.DiscordBot.Enums;
+using ServerManagerTool.DiscordBot.Interfaces;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ServerManagerTool.DiscordBot.Modules
+{
+ [Name("Server Commands")]
+ public sealed class ServerCommandModule : InteractiveBase
+ {
+ private readonly IServerManagerBot _serverManagerBot;
+ private readonly CommandService _service;
+ private readonly HandleCommandDelegate _handleCommandCallback;
+ private readonly IConfigurationRoot _config;
+
+ public ServerCommandModule(IServerManagerBot serverManagerBot, CommandService service, HandleCommandDelegate handleCommandCallback, IConfigurationRoot config)
+ {
+ _serverManagerBot = serverManagerBot;
+ _service = service;
+ _handleCommandCallback = handleCommandCallback;
+ _config = config;
+ }
+
+ [Command("backup", RunMode = RunMode.Async)]
+ [Summary("Backup the server")]
+ [Remarks("backup profileId")]
+ [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)]
+ public async Task BackupServerAsync(string profileId)
+ {
+ try
+ {
+ var serverId = Context?.Guild?.Id.ToString() ?? string.Empty;
+ var channelId = Context?.Channel?.Id.ToString() ?? string.Empty;
+
+ var response = _handleCommandCallback?.Invoke(CommandType.Backup, serverId, channelId, profileId, _serverManagerBot.Token);
+ if (response is null)
+ {
+ await ReplyAsync("No servers associated with this channel.");
+ }
+ else
+ {
+ foreach (var output in response)
+ {
+ await ReplyAsync(output.Replace("&", "_"));
+ await Task.Delay(1000);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ await ReplyAsync($"'{Context.Message}' command sent and failed with exception ({ex.Message})");
+ }
+ }
+
+ [Command("restart", RunMode = RunMode.Async)]
+ [Summary("Restart the server")]
+ [Remarks("restart profileId")]
+ [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)]
+ public async Task RestartServerAsync(string profileId)
+ {
+ try
+ {
+ var serverId = Context?.Guild?.Id.ToString() ?? string.Empty;
+ var channelId = Context?.Channel?.Id.ToString() ?? string.Empty;
+
+ var response = _handleCommandCallback?.Invoke(CommandType.Restart, serverId, channelId, profileId, _serverManagerBot.Token);
+ if (response is null)
+ {
+ await ReplyAsync("No servers associated with this channel.");
+ }
+ else
+ {
+ foreach (var output in response)
+ {
+ await ReplyAsync(output.Replace("&", "_"));
+ await Task.Delay(1000);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ await ReplyAsync($"'{Context.Message}' command sent and failed with exception ({ex.Message})");
+ }
+ }
+
+ [Command("shutdown", RunMode = RunMode.Async)]
+ [Summary("Shuts down the server properly")]
+ [Remarks("shutdown profileId")]
+ [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)]
+ public async Task ShutdownServerAsync(string profileId)
+ {
+ try
+ {
+ var serverId = Context?.Guild?.Id.ToString() ?? string.Empty;
+ var channelId = Context?.Channel?.Id.ToString() ?? string.Empty;
+
+ var response = _handleCommandCallback?.Invoke(CommandType.Shutdown, serverId, channelId, profileId, _serverManagerBot.Token);
+ if (response is null)
+ {
+ await ReplyAsync("No servers associated with this channel.");
+ }
+ else
+ {
+ foreach (var output in response)
+ {
+ await ReplyAsync(output.Replace("&", "_"));
+ await Task.Delay(1000);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ await ReplyAsync($"'{Context.Message}' command sent and failed with exception ({ex.Message})");
+ }
+ }
+
+ [Command("start", RunMode = RunMode.Async)]
+ [Summary("Starts the server")]
+ [Remarks("start profileId")]
+ [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)]
+ public async Task StartServerAsync(string profileId)
+ {
+ try
+ {
+ var serverId = Context?.Guild?.Id.ToString() ?? string.Empty;
+ var channelId = Context?.Channel?.Id.ToString() ?? string.Empty;
+
+ var response = _handleCommandCallback?.Invoke(CommandType.Start, serverId, channelId, profileId, _serverManagerBot.Token);
+ if (response is null)
+ {
+ await ReplyAsync("No servers associated with this channel.");
+ }
+ else
+ {
+ foreach (var output in response)
+ {
+ await ReplyAsync(output.Replace("&", "_"));
+ await Task.Delay(1000);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ await ReplyAsync($"'{Context.Message}' command sent and failed with exception ({ex.Message})");
+ }
+ }
+
+ [Command("stop", RunMode = RunMode.Async)]
+ [Summary("Forcibly stops the server")]
+ [Remarks("stop profileId")]
+ [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)]
+ public async Task StopServerAsync(string profileId)
+ {
+ try
+ {
+ var serverId = Context?.Guild?.Id.ToString() ?? string.Empty;
+ var channelId = Context?.Channel?.Id.ToString() ?? string.Empty;
+
+ var response = _handleCommandCallback?.Invoke(CommandType.Stop, serverId, channelId, profileId, _serverManagerBot.Token);
+ if (response is null)
+ {
+ await ReplyAsync("No servers associated with this channel.");
+ }
+ else
+ {
+ foreach (var output in response)
+ {
+ await ReplyAsync(output.Replace("&", "_"));
+ await Task.Delay(1000);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ await ReplyAsync($"'{Context.Message}' command sent and failed with exception ({ex.Message})");
+ }
+ }
+
+ [Command("update", RunMode = RunMode.Async)]
+ [Summary("Updates the server")]
+ [Remarks("update profileId")]
+ [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)]
+ public async Task UpdateServerAsync(string profileId)
+ {
+ try
+ {
+ var serverId = Context?.Guild?.Id.ToString() ?? string.Empty;
+ var channelId = Context?.Channel?.Id.ToString() ?? string.Empty;
+
+ var response = _handleCommandCallback?.Invoke(CommandType.Update, serverId, channelId, profileId, _serverManagerBot.Token);
+ if (response is null)
+ {
+ await ReplyAsync("No servers associated with this channel.");
+ }
+ else
+ {
+ foreach (var output in response)
+ {
+ await ReplyAsync(output.Replace("&", "_"));
+ await Task.Delay(1000);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ await ReplyAsync($"'{Context.Message}' command sent and failed with exception ({ex.Message})");
+ }
+ }
+ }
+}
diff --git a/src/ServerManager.Discord/Modules/ServerQueryModule.cs b/src/ServerManager.Discord/Modules/ServerQueryModule.cs
new file mode 100644
index 00000000..a793c3b7
--- /dev/null
+++ b/src/ServerManager.Discord/Modules/ServerQueryModule.cs
@@ -0,0 +1,141 @@
+using Discord;
+using Discord.Addons.Interactive;
+using Discord.Commands;
+using Microsoft.Extensions.Configuration;
+using ServerManagerTool.DiscordBot.Delegates;
+using ServerManagerTool.DiscordBot.Enums;
+using ServerManagerTool.DiscordBot.Interfaces;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ServerManagerTool.DiscordBot.Modules
+{
+ [Name("Server Query")]
+ public sealed class ServerQueryModule : InteractiveBase
+ {
+ private readonly IServerManagerBot _serverManagerBot;
+ private readonly CommandService _service;
+ private readonly HandleCommandDelegate _handleCommandCallback;
+ private readonly IConfigurationRoot _config;
+
+ public ServerQueryModule(IServerManagerBot serverManagerBot, CommandService service, HandleCommandDelegate handleCommandCallback, IConfigurationRoot config)
+ {
+ _serverManagerBot = serverManagerBot;
+ _service = service;
+ _handleCommandCallback = handleCommandCallback;
+ _config = config;
+ }
+
+ [Command("info", RunMode = RunMode.Async)]
+ [Summary("Poll server for information")]
+ [Remarks("info")]
+ [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)]
+ public async Task ServerInfoAsync()
+ {
+ await ServerInfoAsync(null);
+ }
+
+ [Command("info", RunMode = RunMode.Async)]
+ [Summary("Poll server for information")]
+ [Remarks("info profileId")]
+ [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)]
+ public async Task ServerInfoAsync(string profileId)
+ {
+ try
+ {
+ var serverId = Context?.Guild?.Id.ToString() ?? string.Empty;
+ var channelId = Context?.Channel?.Id.ToString() ?? string.Empty;
+
+ var response = _handleCommandCallback?.Invoke(CommandType.Info, serverId, channelId, profileId, _serverManagerBot.Token);
+ if (response is null)
+ {
+ await ReplyAsync("No servers associated with this channel.");
+ }
+ else
+ {
+ foreach (var output in response)
+ {
+ await ReplyAsync(output.Replace("&", "_"));
+ await Task.Delay(1000);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ await ReplyAsync($"'{Context.Message}' command sent and failed with exception ({ex.Message})");
+ }
+ }
+
+ [Command("list", RunMode = RunMode.Async)]
+ [Summary("List of all servers associated with this channel")]
+ [Remarks("list")]
+ [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)]
+ public async Task ServerListAsync()
+ {
+ try
+ {
+ var serverId = Context?.Guild?.Id.ToString() ?? string.Empty;
+ var channelId = Context?.Channel?.Id.ToString() ?? string.Empty;
+
+ var response = _handleCommandCallback?.Invoke(CommandType.List, serverId, channelId, null, _serverManagerBot.Token);
+ if (response is null)
+ {
+ await ReplyAsync("No servers associated with this channel.");
+ }
+ else
+ {
+ foreach (var output in response)
+ {
+ await ReplyAsync(output.Replace("&", "_"));
+ await Task.Delay(1000);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ await ReplyAsync($"'{Context.Message}' command sent and failed with exception ({ex.Message})");
+ }
+ }
+
+ [Command("status", RunMode = RunMode.Async)]
+ [Summary("Poll server for status")]
+ [Remarks("status")]
+ [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)]
+ public async Task ServerStatusAsync()
+ {
+ await ServerStatusAsync(null);
+ }
+
+ [Command("status", RunMode = RunMode.Async)]
+ [Summary("Poll server for status")]
+ [Remarks("status profileId")]
+ [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)]
+ public async Task ServerStatusAsync(string profileId)
+ {
+ try
+ {
+ var serverId = Context?.Guild?.Id.ToString() ?? string.Empty;
+ var channelId = Context?.Channel?.Id.ToString() ?? string.Empty;
+
+ var response = _handleCommandCallback?.Invoke(CommandType.Status, serverId, channelId, profileId, _serverManagerBot.Token);
+ if (response is null)
+ {
+ await ReplyAsync("No servers associated with this channel.");
+ }
+ else
+ {
+ foreach (var output in response)
+ {
+ await ReplyAsync(output.Replace("&", "_"));
+ await Task.Delay(1000);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ await ReplyAsync($"'{Context.Message}' command sent and failed with exception ({ex.Message})");
+ }
+ }
+ }
+}
diff --git a/src/ServerManager.Discord/ServerManager.Discord.csproj b/src/ServerManager.Discord/ServerManager.Discord.csproj
index 3b657bf3..3a28fd00 100644
--- a/src/ServerManager.Discord/ServerManager.Discord.csproj
+++ b/src/ServerManager.Discord/ServerManager.Discord.csproj
@@ -1,15 +1,28 @@
-
+
Debug;Release;Debug - Beta
net462
false
- ServerManagerTool.Discord
+ ServerManagerTool.DiscordBot
none
false
-
+
+ $(DefineConstants);DEBUG
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ServerManager.Discord/ServerManagerBot.cs b/src/ServerManager.Discord/ServerManagerBot.cs
new file mode 100644
index 00000000..6c7f0d63
--- /dev/null
+++ b/src/ServerManager.Discord/ServerManagerBot.cs
@@ -0,0 +1,138 @@
+using Discord;
+using Discord.Addons.Interactive;
+using Discord.Commands;
+using Discord.Net.Providers.WS4Net;
+using Discord.WebSocket;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using ServerManagerTool.DiscordBot.Delegates;
+using ServerManagerTool.DiscordBot.Interfaces;
+using ServerManagerTool.DiscordBot.Services;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ServerManagerTool.DiscordBot
+{
+ public sealed class ServerManagerBot : IServerManagerBot
+ {
+ internal ServerManagerBot()
+ {
+ Started = false;
+ }
+
+ public CancellationToken Token { get; private set; }
+ public bool Started { get; private set; }
+
+ public async Task StartAsync(string discordToken, string commandPrefix, string dataDirectory, HandleCommandDelegate handleCommandCallback, HandleTranslationDelegate handleTranslationCallback, CancellationToken token)
+ {
+ if (Started)
+ {
+ return;
+ }
+ Started = true;
+
+ if (string.IsNullOrWhiteSpace(commandPrefix) || string.IsNullOrWhiteSpace(discordToken) || handleTranslationCallback is null || handleCommandCallback is null)
+ {
+ return;
+ }
+
+ Token = token;
+
+ if (commandPrefix.Any(c => !char.IsLetterOrDigit(c)))
+ {
+ throw new Exception("#DiscordBot_InvalidPrefixError");
+ }
+
+ if (!commandPrefix.EndsWith(DiscordBot.PREFIX_DELIMITER))
+ {
+ commandPrefix += DiscordBot.PREFIX_DELIMITER;
+ }
+
+ var settings = new Dictionary
+ {
+ { "DiscordSettings:Token", discordToken },
+ { "DiscordSettings:Prefix", commandPrefix },
+ { "ServerManager:DataDirectory", dataDirectory },
+ };
+
+ // Begin building the configuration file
+ var config = new ConfigurationBuilder()
+ .AddInMemoryCollection(settings)
+ .Build();
+
+ var socketConfig = new DiscordSocketConfig
+ {
+#if DEBUG
+ LogLevel = LogSeverity.Verbose,
+#else
+ LogLevel = LogSeverity.Info,
+#endif
+ // Tell Discord.Net to cache 1000 messages per channel
+ MessageCacheSize = 1000,
+ };
+ if (Environment.OSVersion.Version < new Version(6, 2))
+ {
+ // windows 7 or early
+ socketConfig.WebSocketProvider = WS4NetProvider.Instance;
+ }
+
+ var commandConfig = new CommandServiceConfig
+ {
+ // Force all commands to run async
+ DefaultRunMode = RunMode.Async,
+#if DEBUG
+ LogLevel = LogSeverity.Verbose,
+#else
+ LogLevel = LogSeverity.Info,
+#endif
+ };
+
+ // Build the service provider
+ var services = new ServiceCollection()
+ // Add the discord client to the service provider
+ .AddSingleton(new DiscordSocketClient(socketConfig))
+ // Add the command service to the service provider
+ .AddSingleton(new CommandService(commandConfig))
+ // Add remaining services to the provider
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton(config)
+ .AddSingleton(handleCommandCallback)
+ .AddSingleton(handleTranslationCallback)
+ .AddSingleton(this);
+
+ // Create the service provider
+ using (var provider = services.BuildServiceProvider())
+ {
+ // Initialize the logging service, startup service, and command handler
+ provider?.GetRequiredService();
+ await provider?.GetRequiredService().StartAsync();
+ provider?.GetRequiredService();
+
+ try
+ {
+ // Prevent the application from closing
+ await Task.Delay(Timeout.Infinite, token);
+ }
+ catch (TaskCanceledException)
+ {
+ Debug.WriteLine("Task Canceled");
+ }
+ catch (OperationCanceledException)
+ {
+ Debug.WriteLine("Operation Canceled");
+ }
+
+ await provider?.GetRequiredService().StopAsync();
+ }
+ }
+ }
+}
diff --git a/src/ServerManager.Discord/ServerManagerBotFactory.cs b/src/ServerManager.Discord/ServerManagerBotFactory.cs
new file mode 100644
index 00000000..f4b78128
--- /dev/null
+++ b/src/ServerManager.Discord/ServerManagerBotFactory.cs
@@ -0,0 +1,19 @@
+using ServerManagerTool.DiscordBot.Interfaces;
+
+namespace ServerManagerTool.DiscordBot
+{
+ public static class ServerManagerBotFactory
+ {
+ private static IServerManagerBot _serverManagerBot;
+
+ public static IServerManagerBot GetServerManagerBot()
+ {
+ if (_serverManagerBot is null)
+ {
+ _serverManagerBot = new ServerManagerBot();
+ }
+
+ return _serverManagerBot;
+ }
+ }
+}
diff --git a/src/ServerManager.Discord/Services/CommandHandlerService.cs b/src/ServerManager.Discord/Services/CommandHandlerService.cs
new file mode 100644
index 00000000..dcb2f917
--- /dev/null
+++ b/src/ServerManager.Discord/Services/CommandHandlerService.cs
@@ -0,0 +1,65 @@
+using Discord.Commands;
+using Discord.WebSocket;
+using Microsoft.Extensions.Configuration;
+using System;
+using System.Threading.Tasks;
+
+namespace ServerManagerTool.DiscordBot.Services
+{
+ public class CommandHandlerService
+ {
+ private readonly DiscordSocketClient _discord;
+ private readonly CommandService _commands;
+ private readonly IConfigurationRoot _config;
+ private readonly IServiceProvider _provider;
+
+ public CommandHandlerService(DiscordSocketClient discord, CommandService commands, IConfigurationRoot config, IServiceProvider provider)
+ {
+ _discord = discord;
+ _commands = commands;
+ _config = config;
+ _provider = provider;
+
+ _discord.MessageReceived += OnMessageReceivedAsync;
+ }
+
+ private async Task OnMessageReceivedAsync(SocketMessage s)
+ {
+ // Ensure the message is from a user/bot
+ var msg = s as SocketUserMessage;
+ if (msg is null)
+ {
+ return;
+ }
+
+ // Ignore self when checking commands
+ if (msg.Author == _discord.CurrentUser)
+ {
+ return;
+ }
+
+ //Tell bot to ignore itself.
+ if (msg.Author.IsBot)
+ {
+ return;
+ }
+
+ // Create the command context
+ var context = new SocketCommandContext(_discord, msg);
+
+ // Check if the message has a valid command prefix
+ var argPos = 0;
+ if (msg.HasStringPrefix(_config["DiscordSettings:Prefix"], ref argPos) || msg.HasMentionPrefix(_discord.CurrentUser, ref argPos))
+ {
+ // Execute the command
+ var result = await _commands.ExecuteAsync(context, argPos, _provider);
+
+ if (!result.IsSuccess)
+ {
+ // If not successful, reply with the error.
+ await context.Channel.SendMessageAsync(result.ToString());
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ServerManager.Discord/Services/LoggingService.cs b/src/ServerManager.Discord/Services/LoggingService.cs
new file mode 100644
index 00000000..03b0d34e
--- /dev/null
+++ b/src/ServerManager.Discord/Services/LoggingService.cs
@@ -0,0 +1,57 @@
+using Discord;
+using Discord.Commands;
+using Discord.WebSocket;
+using Microsoft.Extensions.Configuration;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace ServerManagerTool.DiscordBot.Services
+{
+ public class LoggingService
+ {
+ private readonly DiscordSocketClient _discord;
+ private readonly CommandService _commands;
+ private readonly IConfigurationRoot _config;
+
+ private string LogDirectory { get; }
+ private string LogFile => Path.Combine(LogDirectory, $"ServerManager_DiscordBot.{DateTime.Now:yyyyMMdd}.log");
+
+ public LoggingService(DiscordSocketClient discord, CommandService commands, IConfigurationRoot config)
+ {
+ _discord = discord;
+ _commands = commands;
+ _config = config;
+
+ // Get the data directory from the config file
+ var rootDirectory = _config["ServerManager:DataDirectory"] ?? AppContext.BaseDirectory;
+ LogDirectory = Path.Combine(rootDirectory, "logs");
+
+ _discord.Log += OnLogAsync;
+ _commands.Log += OnLogAsync;
+ }
+
+ private async Task OnLogAsync(LogMessage message)
+ {
+ // Create the log directory if it doesn't exist
+ if (!Directory.Exists(LogDirectory))
+ {
+ Directory.CreateDirectory(LogDirectory);
+ }
+
+ // Create today's log file if it doesn't exist
+ if (!File.Exists(LogFile))
+ {
+ File.Create(LogFile).Dispose();
+ }
+
+ var logText = $"{DateTime.Now:HH:mm:ss:ffff} [{message.Severity}] {message.Source}: {message.Exception?.ToString() ?? message.Message}";
+
+ // Write the log text to a file
+ File.AppendAllText(LogFile, logText + "\n");
+
+ // Write the log text to the console
+ await Console.Out.WriteLineAsync(logText);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ServerManager.Discord/Services/ShutdownService.cs b/src/ServerManager.Discord/Services/ShutdownService.cs
new file mode 100644
index 00000000..d940c57d
--- /dev/null
+++ b/src/ServerManager.Discord/Services/ShutdownService.cs
@@ -0,0 +1,21 @@
+using Discord.WebSocket;
+using System.Threading.Tasks;
+
+namespace ServerManagerTool.DiscordBot.Services
+{
+ public class ShutdownService
+ {
+ private readonly DiscordSocketClient _discord;
+
+ public ShutdownService(DiscordSocketClient discord)
+ {
+ _discord = discord;
+ }
+
+ public async Task StopAsync()
+ {
+ await _discord.StopAsync();
+ await _discord.LogoutAsync();
+ }
+ }
+}
diff --git a/src/ServerManager.Discord/Services/StartupService.cs b/src/ServerManager.Discord/Services/StartupService.cs
new file mode 100644
index 00000000..aee2f846
--- /dev/null
+++ b/src/ServerManager.Discord/Services/StartupService.cs
@@ -0,0 +1,45 @@
+using Discord;
+using Discord.Commands;
+using Discord.WebSocket;
+using Microsoft.Extensions.Configuration;
+using System;
+using System.Reflection;
+using System.Threading.Tasks;
+
+namespace ServerManagerTool.DiscordBot.Services
+{
+ public class StartupService
+ {
+ private readonly DiscordSocketClient _discord;
+ private readonly CommandService _commands;
+ private readonly IConfigurationRoot _config;
+ private readonly IServiceProvider _provider;
+
+ public StartupService(DiscordSocketClient discord, CommandService commands, IConfigurationRoot config, IServiceProvider provider)
+ {
+ _discord = discord;
+ _commands = commands;
+ _config = config;
+ _provider = provider;
+ }
+
+ public async Task StartAsync()
+ {
+ // Get the discord token from the config file
+ var discordToken = _config["DiscordSettings:Token"];
+
+ if (string.IsNullOrWhiteSpace(discordToken))
+ {
+ throw new Exception("#DiscordBot_MissingTokenError");
+ }
+
+ // Login to discord
+ await _discord.LoginAsync(TokenType.Bot, discordToken);
+ // Connect to the websocket
+ await _discord.StartAsync();
+
+ // Load commands and modules into the command service
+ await _commands.AddModulesAsync(Assembly.GetExecutingAssembly(), _provider);
+ }
+ }
+}
\ No newline at end of file