Merge pull request #14 from Bletch1971/DiscordBot

Discord bot
This commit is contained in:
Brett Hewitson 2021-12-06 00:02:43 +10:00 committed by GitHub
commit af7f3e8c64
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
67 changed files with 3908 additions and 358 deletions

View file

@ -173,6 +173,7 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="Delegates\ServerStatusChangeDelegate.cs" />
<Compile Include="Enums\LevelProgression.cs" />
<Compile Include="Enums\LogEventType.cs" />
<Compile Include="Enums\NPCSpawnContainerType.cs" />
@ -196,13 +197,15 @@
<Compile Include="Lib\Serialization\IniSections.cs" />
<Compile Include="Lib\Serialization\SystemIniFile.cs" />
<Compile Include="Lib\Model\PlayerListParameters.cs" />
<Compile Include="Lib\ServerBranchSnapshot.cs" />
<Compile Include="Lib\BranchSnapshot.cs" />
<Compile Include="Lib\ServerPlayers.cs" />
<Compile Include="Lib\ServerProfileSnapshot.cs" />
<Compile Include="Lib\ViewConverters\EnumDescriptionTypeConverter.cs" />
<Compile Include="Lib\ViewConverters\MapNameValueConverter.cs" />
<Compile Include="Lib\ViewModel\Engram.cs" />
<Compile Include="Lib\ViewModel\EngramSettings.cs" />
<Compile Include="Utils\DiscordBotHelper.cs" />
<Compile Include="Utils\DiscordPluginHelper.cs" />
<Compile Include="Utils\ModUtils.cs" />
<Compile Include="Windows\AddUserWindow.xaml.cs">
<DependentUpon>AddUserWindow.xaml</DependentUpon>

View file

@ -347,6 +347,12 @@
<setting name="DefaultDataDirectoryName" serializeAs="String">
<value>asmdata</value>
</setting>
<setting name="DiscordBotApplyUrl" serializeAs="String">
<value>https://discord.com/developers/applications</value>
</setting>
<setting name="DiscordBotHelpUrl" serializeAs="String">
<value>https://arkservermanager.freeforums.net/thread/8764/get-own-discord-bot</value>
</setting>
</ServerManagerTool.Config>
<ServerManagerTool.Common.CommonConfig>
<setting name="DefaultSteamAPIKey" serializeAs="String">
@ -804,6 +810,39 @@
<setting name="MainWindow_Top" serializeAs="String">
<value>50</value>
</setting>
<setting name="DiscordBotEnabled" serializeAs="String">
<value>False</value>
</setting>
<setting name="DiscordBotPrefix" serializeAs="String">
<value>asm</value>
</setting>
<setting name="DiscordBotToken" serializeAs="String">
<value />
</setting>
<setting name="DiscordBotServerId" serializeAs="String">
<value />
</setting>
<setting name="SectionDiscordBotIsExpanded" serializeAs="String">
<value>False</value>
</setting>
<setting name="AllowDiscordBackup" serializeAs="String">
<value>True</value>
</setting>
<setting name="AllowDiscordUpdate" serializeAs="String">
<value>True</value>
</setting>
<setting name="AllowDiscordStart" serializeAs="String">
<value>True</value>
</setting>
<setting name="AllowDiscordRestart" serializeAs="String">
<value>True</value>
</setting>
<setting name="AllowDiscordShutdown" serializeAs="String">
<value>True</value>
</setting>
<setting name="AllowDiscordStop" serializeAs="String">
<value>True</value>
</setting>
</ServerManagerTool.Config>
</userSettings>
</configuration>

View file

@ -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<Plugin.Common.Lib.Profile> 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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

View file

@ -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;
}
}
}
}

View file

@ -779,5 +779,44 @@
<Setting Name="DefaultDataDirectoryName" Type="System.String" Scope="Application">
<Value Profile="(Default)">asmdata</Value>
</Setting>
<Setting Name="DiscordBotEnabled" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="DiscordBotPrefix" Type="System.String" Scope="User">
<Value Profile="(Default)">asm</Value>
</Setting>
<Setting Name="DiscordBotToken" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="DiscordBotServerId" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="DiscordBotApplyUrl" Type="System.String" Scope="Application">
<Value Profile="(Default)">https://discord.com/developers/applications</Value>
</Setting>
<Setting Name="DiscordBotHelpUrl" Type="System.String" Scope="Application">
<Value Profile="(Default)">https://arkservermanager.freeforums.net/thread/8764/get-own-discord-bot</Value>
</Setting>
<Setting Name="SectionDiscordBotIsExpanded" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="AllowDiscordBackup" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="AllowDiscordUpdate" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="AllowDiscordStart" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="AllowDiscordRestart" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="AllowDiscordShutdown" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="AllowDiscordStop" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
</Settings>
</SettingsFile>

View file

@ -0,0 +1,6 @@
using ServerManagerTool.Enums;
namespace ServerManagerTool.Delegates
{
public delegate void ServerStatusChangeDelegate(ServerStatus serverStatus);
}

View file

@ -3,9 +3,9 @@
public enum AvailabilityStatus
{
Unknown,
NeedPublicIP,
SetPublicIP,
Unavailable,
WaitingForPublication,
Waiting,
Available
}
}

View file

@ -10,5 +10,6 @@
Backup,
Shutdown,
Restart,
Update,
}
}

View file

@ -610,7 +610,24 @@
<sys:String x:Key="GlobalSettings_ShutdownCancelTooltip">This message will be displayed when the server shutdown has been cancelled.</sys:String>
<sys:String x:Key="GlobalSettings_ShutdownAllMessagesShowReasonLabel">Show shutdown reason with ALL shutdown messages</sys:String>
<sys:String x:Key="GlobalSettings_ShutdownAllMessagesShowReasonTooltip">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.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotLabel">Enable Discord Bot</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotInformationLabel">You will need to restart the server manager if you change any settings for the Discord Bot.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotTokenLabel">Token:</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotTokenTooltip">The token associated with the discord bot.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotServerLabel">Server Id:</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotServerTooltip">The id of the discord server the bot will listen to.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotPrefixLabel">Prefix:</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotPrefixTooltip">The prefix that must be used when sending a command via discord.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotApplyButtonLabel">Get Token...</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotHelpButtonLabel">Help...</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotAllowBackupTooltip">If enabled, the backup command can be sent from discord.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotAllowRestartTooltip">If enabled, the restart command can be sent from discord.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotAllowShutdownTooltip">If enabled, the shutdown command can be sent from discord.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotAllowStartTooltip">If enabled, the start command can be sent from discord.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotAllowStopTooltip">If enabled, the stop command can be sent from discord.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotAllowUpdateTooltip">If enabled, the update command can be sent from discord.</sys:String>
<sys:String x:Key="GlobalSettings_EmailSettingsLabel">SMTP Email Settings</sys:String>
<sys:String x:Key="GlobalSettings_EmailHostLabel">Host:</sys:String>
<sys:String x:Key="GlobalSettings_EmailHostTooltip">The name or IP address of the host used for SMTP transmissions.</sys:String>
@ -775,6 +792,9 @@
<sys:String x:Key="MainWindow_SteamCmd_FailedTitle">Reinstall SteamCMD Error</sys:String>
<sys:String x:Key="MainWindow_SteamCmd_FailedLabel">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}</sys:String>
<sys:String x:Key="MainWindow_DiscordBot_RunningCommandsTitle">Discord Bot Running Commands</sys:String>
<sys:String x:Key="MainWindow_DiscordBot_RunningCommandsLabel">The discord bot has one or more running commands, do you want to continue shutting down the server manager?</sys:String>
<sys:String x:Key="MainWindow_ServerStatus_StartServerActionTitle">Start Server Confirmation</sys:String>
<sys:String x:Key="MainWindow_ServerStatus_StartServerActionLabel">You are about to start the server, do you want to continue?</sys:String>
<sys:String x:Key="MainWindow_ServerStatus_ShutdownServerActionTitle">Shutdown Server Confirmation</sys:String>
@ -1179,6 +1199,24 @@
<sys:String x:Key="ServerSettings_RestartIfShutdownTooltip">If enabled, the server will be restarted even if shutdown for Auto-Restarts and Auto-Updates.</sys:String>
<!--#endregion-->
<!--#region Server Settings - Discord Bot Details -->
<sys:String x:Key="ServerSettings_DiscordBotLabel">Discord Bot Details</sys:String>
<sys:String x:Key="ServerSettings_DiscordBotChannelLabel">Channel Id:</sys:String>
<sys:String x:Key="ServerSettings_DiscordBotChannelTooltip">The id of the discord server channel this profile will listen to.</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordBackupLabel">Allow Backup</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordBackupTooltip">If enabled, the profile will listen for backup commands from discord.</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordRestartLabel">Allow Restart</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordRestartTooltip">If enabled, the profile will listen for restart commands from discord.</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordShutdownLabel">Allow Shutdown</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordShutdownTooltip">If enabled, the profile will listen for shutdown commands from discord.</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordStartLabel">Allow Start</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordStartTooltip">If enabled, the profile will listen for start commands from discord.</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordStopLabel">Allow Stop</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordStopTooltip">If enabled, the profile will listen for stop commands from discord.</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordUpdateLabel">Allow Update</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordUpdateTooltip">If enabled, the profile will listen for update commands from discord.</sys:String>
<!--#endregion-->
<!--#region Server Settings - Rules -->
<sys:String x:Key="ServerSettings_RulesLabel">Rules</sys:String>
<sys:String x:Key="ServerSettings_EnableHardcoreLabel">Enable Hardcore Mode</sys:String>
@ -5523,4 +5561,32 @@
<sys:String x:Key="ServerUpdate_WarningLabel">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?</sys:String>
<!--#endregion-->
<!--#region Discord Bot -->
<sys:String x:Key="DiscordBot_ErrorTitle">Discord Bot Error</sys:String>
<sys:String x:Key="DiscordBot_MissingTokenError">The discord bot requires a valid token so it can log into the discord server\r\nThis can be set in the global settings.</sys:String>
<sys:String x:Key="DiscordBot_InvalidPrefixError">The discord bot prefix contains invalid characters. Only letters and numbers are allowed.</sys:String>
<sys:String x:Key="DiscordBot_CommandNotEnabled">Command '{0}' has not been enabled.</sys:String>
<sys:String x:Key="DiscordBot_CommandUnknown">Unknown command '{0}'.</sys:String>
<sys:String x:Key="DiscordBot_CommandRunning">Another command is currently being processed.</sys:String>
<sys:String x:Key="DiscordBot_CommandRunningProfile">Another command '{0}' is currently running against profile '{1}'.</sys:String>
<sys:String x:Key="DiscordBot_CommandDisabledProfile">Command '{0}' has been disabled for profile '{1}'.</sys:String>
<sys:String x:Key="DiscordBot_ProfileMissing">The '{0}' command requires a profile id.</sys:String>
<sys:String x:Key="DiscordBot_ProfileNotFound">Profile '{0}' was not found or is not associated with the channel.</sys:String>
<sys:String x:Key="DiscordBot_ProfileBadStatus">Profile '{0}' is in a state '{1}' that cannot run this command.</sys:String>
<sys:String x:Key="DiscordBot_ProfileUpdating">Profile '{0}' is currently being updated.</sys:String>
<sys:String x:Key="DiscordBot_InfoFailed">Call to server '{0}' failed.</sys:String>
<sys:String x:Key="DiscordBot_BackupRequested">A backup request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_RestartRequested">A restart request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_ShutdownRequested">A shutdown request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_StartRequested">A start request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_StopRequested">A stop request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_UpdateRequested">An update request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_CountLabel">Count:</sys:String>
<sys:String x:Key="DiscordBot_MapLabel">Map:</sys:String>
<!--#endregion-->
</Globalization:GlobalizationResourceDictionary>

View file

@ -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<ServerBranchSnapshot>
public class BranchSnapshotComparer : IEqualityComparer<BranchSnapshot>
{
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;

View file

@ -94,7 +94,7 @@ namespace ServerManagerTool.Lib
await this.Runtime.StopAsync();
}
public async Task<bool> UpgradeAsync(CancellationToken cancellationToken, bool updateServer, ServerBranchSnapshot branch, bool validate, bool updateMods, ProgressDelegate progressCallback)
public async Task<bool> 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);

View file

@ -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<ServerBranchSnapshot, int>();
var branches = _profiles.Keys.Where(p => p.EnableAutoUpdate).Select(p => BranchSnapshot.Create(p)).Distinct(new BranchSnapshotComparer()).ToArray();
var exitCodes = new ConcurrentDictionary<BranchSnapshot, int>();
// update the server cache for each branch
if (Config.Default.AutoUpdate_ParallelUpdate)

View file

@ -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")]

View file

@ -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,

View file

@ -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<PropertyChangeNotifier> profileNotifiers = new List<PropertyChangeNotifier>();
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<bool> UpgradeAsync(CancellationToken cancellationToken, bool updateServer, ServerBranchSnapshot branch, bool validate, bool updateMods, ProgressDelegate progressCallback)
public async Task<bool> 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<bool> UpgradeAsync(CancellationToken cancellationToken, bool updateServer, ServerBranchSnapshot branch, bool validate, bool updateMods, string[] updateModIds, ProgressDelegate progressCallback)
public async Task<bool> 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)

View file

@ -713,5 +713,15 @@
<Label Content="{DynamicResource GlobalSettings_SetLocationButtonLabel}" VerticalAlignment="Center" Margin="0,-3,0,-3"/>
</StackPanel>
</ContentControl>
<ContentControl x:Key="DiscordBotApplyButtonContent">
<StackPanel Orientation="Horizontal">
<Label Content="{DynamicResource GlobalSettings_DiscordBotApplyButtonLabel}" VerticalAlignment="Center" Margin="0,-2,0,-2"/>
</StackPanel>
</ContentControl>
<ContentControl x:Key="DiscordBotHelpButtonContent">
<StackPanel Orientation="Horizontal">
<Label Content="{DynamicResource GlobalSettings_DiscordBotHelpButtonLabel}" VerticalAlignment="Center" Margin="0,-2,0,-2"/>
</StackPanel>
</ContentControl>
</Globalization:StyleResourceDictionary>

View file

@ -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<string, CommandType> _currentProfileCommands = new Dictionary<string, CommandType>();
public static bool HasRunningCommands => _currentProfileCommands.Count > 0;
public static IList<string> 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<string>();
if (_runningCommand)
return new List<string> { _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> { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
case CommandType.Restart:
if (Config.Default.AllowDiscordRestart)
return RestartServer(channelId, profileId, token);
return new List<string> { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
case CommandType.Shutdown:
if (Config.Default.AllowDiscordShutdown)
return ShutdownServer(channelId, profileId, token);
return new List<string> { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
case CommandType.Stop:
if (Config.Default.AllowDiscordStop)
return StopServer(channelId, profileId, token);
return new List<string> { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
case CommandType.Start:
if (Config.Default.AllowDiscordStart)
return StartServer(channelId, profileId, token);
return new List<string> { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
case CommandType.Update:
if (Config.Default.AllowDiscordUpdate)
return UpdateServer(channelId, profileId, token);
return new List<string> { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
default:
return new List<string> { 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<string> GetServerInfo(string channelId, string profileId)
{
if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> GetServerList(string channelId)
{
List<string> response = new List<string>();
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<string> GetServerStatus(string channelId, string profileId)
{
List<string> response = new List<string>();
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<string> BackupServer(string channelId, string profileId, CancellationToken token)
{
if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> RestartServer(string channelId, string profileId, CancellationToken token)
{
if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> ShutdownServer(string channelId, string profileId, CancellationToken token)
{
if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> StopServer(string channelId, string profileId, CancellationToken token)
{
if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> StartServer(string channelId, string profileId, CancellationToken token)
{
if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> UpdateServer(string channelId, string profileId, CancellationToken token)
{
if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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);
}
}
}
}
}

View file

@ -0,0 +1,18 @@
using ServerManagerTool.Lib;
using System.Collections.Generic;
using System.Linq;
namespace ServerManagerTool.Utils
{
internal static class DiscordPluginHelper
{
public static IList<Plugin.Common.Lib.Profile> 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();
}
}
}

View file

@ -579,6 +579,55 @@
</GroupBox>
<GroupBox Grid.Row="19" Grid.Column="0" Grid.ColumnSpan="4" Style="{StaticResource GroupBoxStyle}">
<GroupBox.Header>
<CheckBox IsChecked="{Binding CurrentConfig.DiscordBotEnabled}" Content="{DynamicResource GlobalSettings_DiscordBotLabel}" VerticalAlignment="Center"/>
</GroupBox.Header>
<Grid IsEnabled="{Binding CurrentConfig.DiscordBotEnabled}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="60"/>
<ColumnDefinition/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto" MinWidth="60"/>
<ColumnDefinition/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto" MinWidth="60"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="8" Margin="5" Text="{DynamicResource GlobalSettings_DiscordBotInformationLabel}" TextWrapping="Wrap" VerticalAlignment="Center" FontWeight="Bold" Foreground="{DynamicResource WarningMessage}"/>
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource GlobalSettings_DiscordBotTokenLabel}" VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="6" Margin="1" Name="HideDiscordBotTokenTextBox" Text="{DynamicResource ServerSettings_HidePasswordText}" ToolTip="{DynamicResource ServerSettings_HidePasswordTooltip}" GotFocus="HiddenField_GotFocus" Style="{StaticResource HiddenTextBoxStyle}"/>
<TextBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="6" Margin="1" Name="DiscordBotTokenTextBox" Text="{Binding CurrentConfig.DiscordBotToken}" IsReadOnlyCaretVisible="True" VerticalContentAlignment="Center" ToolTip="{DynamicResource GlobalSettings_DiscordBotTokenTooltip}" LostFocus="HiddenField_LostFocus" Visibility="Collapsed"/>
<StackPanel Grid.Row="1" Grid.Column="7" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Margin="1" Content="{DynamicResource DiscordBotApplyButtonContent}" Click="DiscordBotApply_Click"/>
<Button Margin="1" Content="{DynamicResource DiscordBotHelpButtonContent}" Click="DiscordBotHelp_Click"/>
</StackPanel>
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource GlobalSettings_DiscordBotServerLabel}" VerticalAlignment="Center"/>
<TextBox Grid.Row="2" Grid.Column="1" Margin="1" Text="{Binding CurrentConfig.DiscordBotServerId}" IsReadOnlyCaretVisible="True" VerticalContentAlignment="Center" ToolTip="{DynamicResource GlobalSettings_DiscordBotServerTooltip}"/>
<Label Grid.Row="2" Grid.Column="3" Content="{DynamicResource GlobalSettings_DiscordBotPrefixLabel}" VerticalAlignment="Center"/>
<TextBox Grid.Row="2" Grid.Column="4" Margin="1" Text="{Binding CurrentConfig.DiscordBotPrefix}" IsReadOnlyCaretVisible="True" VerticalContentAlignment="Center" ToolTip="{DynamicResource GlobalSettings_DiscordBotPrefixTooltip}"/>
<CheckBox Grid.Row="3" Grid.Column="1" Margin="0,5,0,0" IsChecked="{Binding CurrentConfig.AllowDiscordBackup}" Content="{DynamicResource ServerSettings_AllowDiscordBackupLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource GlobalSettings_DiscordBotAllowBackupTooltip}"/>
<CheckBox Grid.Row="3" Grid.Column="4" Margin="0,5,0,0" IsChecked="{Binding CurrentConfig.AllowDiscordUpdate}" Content="{DynamicResource ServerSettings_AllowDiscordUpdateLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource GlobalSettings_DiscordBotAllowUpdateTooltip}"/>
<CheckBox Grid.Row="3" Grid.Column="7" Margin="0,5,0,0" IsChecked="{Binding CurrentConfig.AllowDiscordStart}" Content="{DynamicResource ServerSettings_AllowDiscordStartLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource GlobalSettings_DiscordBotAllowStartTooltip}"/>
<CheckBox Grid.Row="4" Grid.Column="1" Margin="0,5,0,5" IsChecked="{Binding CurrentConfig.AllowDiscordRestart}" Content="{DynamicResource ServerSettings_AllowDiscordRestartLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource GlobalSettings_DiscordBotAllowRestartTooltip}"/>
<CheckBox Grid.Row="4" Grid.Column="4" Margin="0,5,0,5" IsChecked="{Binding CurrentConfig.AllowDiscordShutdown}" Content="{DynamicResource ServerSettings_AllowDiscordShutdownLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource GlobalSettings_DiscordBotAllowShutdownTooltip}"/>
<CheckBox Grid.Row="4" Grid.Column="7" Margin="0,5,0,5" IsChecked="{Binding CurrentConfig.AllowDiscordStop}" Content="{DynamicResource ServerSettings_AllowDiscordStopLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource GlobalSettings_DiscordBotAllowStopTooltip}"/>
</Grid>
</GroupBox>
<GroupBox Grid.Row="20" Grid.Column="0" Grid.ColumnSpan="4" Style="{StaticResource GroupBoxStyle}">
<GroupBox.Header>
<Label Content="{DynamicResource GlobalSettings_EmailSettingsLabel}"/>
</GroupBox.Header>
@ -621,7 +670,7 @@
</Grid>
</GroupBox>
<GroupBox Grid.Row="20" Grid.Column="0" Grid.ColumnSpan="4" Style="{StaticResource GroupBoxStyle}">
<GroupBox Grid.Row="21" Grid.Column="0" Grid.ColumnSpan="4" Style="{StaticResource GroupBoxStyle}">
<GroupBox.Header>
<Label Content="{DynamicResource GlobalSettings_EmailNotifySettingsLabel}"/>
</GroupBox.Header>
@ -645,7 +694,7 @@
</Grid>
</GroupBox>
<GroupBox Grid.Row="21" Grid.Column="0" Grid.ColumnSpan="4" Style="{StaticResource GroupBoxStyle}">
<GroupBox Grid.Row="22" Grid.Column="0" Grid.ColumnSpan="4" Style="{StaticResource GroupBoxStyle}">
<GroupBox.Header>
<Label Content="{DynamicResource GlobalSettings_AdvancedSettingsLabel}"/>
</GroupBox.Header>

View file

@ -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)
{

View file

@ -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}">
<Window.Resources>
<ResourceDictionary>

View file

@ -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();

View file

@ -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,

View file

@ -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

View file

@ -420,13 +420,13 @@
<DataTrigger Binding="{Binding Availability}" Value="{x:Static enum:AvailabilityStatus.Unknown}">
<Setter Property="Content" Value="{DynamicResource ServerSettings_Availability_Unknown}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Availability}" Value="{x:Static enum:AvailabilityStatus.NeedPublicIP}">
<DataTrigger Binding="{Binding Availability}" Value="{x:Static enum:AvailabilityStatus.SetPublicIP}">
<Setter Property="Content" Value="{DynamicResource ServerSettings_Availability_SetPublicIP}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Availability}" Value="{x:Static enum:AvailabilityStatus.Unavailable}">
<Setter Property="Content" Value="{DynamicResource ServerSettings_Availability_Unavailable}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Availability}" Value="{x:Static enum:AvailabilityStatus.WaitingForPublication}">
<DataTrigger Binding="{Binding Availability}" Value="{x:Static enum:AvailabilityStatus.Waiting}">
<Setter Property="Content" Value="{DynamicResource ServerSettings_Availability_Waiting}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Availability}" Value="{x:Static enum:AvailabilityStatus.Available}">
@ -1579,6 +1579,55 @@
</StackPanel>
</Expander>
<Expander Name="SectionDiscordBot" IsExpanded="{Binding CurrentConfig.SectionDiscordBotIsExpanded, ElementName=SettingsControl, FallbackValue=True, Mode=TwoWay}" Visibility="{Binding CurrentConfig.DiscordBotEnabled, ElementName=SettingsControl, Converter={StaticResource BooleanToVisibilityConverter}}">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{DynamicResource ServerSettings_DiscordBotLabel}" Style="{StaticResource ExpanderHeaderTextStyle}"/>
</StackPanel>
</Expander.Header>
<Expander.Style>
<Style BasedOn="{StaticResource ExpanderStyle1}" TargetType="{x:Type Expander}">
<Setter Property="Template" Value="{StaticResource ExpanderTemplateSE}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=EnableSOTFCheckbox, Path=IsChecked}" Value="True">
<Setter Property="Template" Value="{StaticResource ExpanderTemplateSotF}"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=EnablePGMCheckbox, Path=IsChecked}" Value="True">
<Setter Property="Template" Value="{StaticResource ExpanderTemplatePGM}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Expander.Style>
<Grid Margin="-8,0,2,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="100"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" MinWidth="100"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" MinWidth="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource ServerSettings_DiscordBotChannelLabel}" ToolTip="{DynamicResource ServerSettings_DiscordBotChannelTooltip}" VerticalAlignment="Center"/>
<TextBox Grid.Row="0" Grid.Column="1" Margin="1" Text="{Binding DiscordChannelId, Mode=TwoWay}" ToolTip="{DynamicResource ServerSettings_DiscordBotChannelTooltip}" VerticalContentAlignment="Center" />
<CheckBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Margin="5,5,5,0" IsChecked="{Binding AllowDiscordBackup, Mode=TwoWay}" Content="{DynamicResource ServerSettings_AllowDiscordBackupLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource ServerSettings_AllowDiscordBackupTooltip}"/>
<CheckBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2" Margin="5,5,5,0" IsChecked="{Binding AllowDiscordUpdate, Mode=TwoWay}" Content="{DynamicResource ServerSettings_AllowDiscordUpdateLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource ServerSettings_AllowDiscordUpdateTooltip}"/>
<CheckBox Grid.Row="1" Grid.Column="4" Grid.ColumnSpan="2" Margin="5,5,5,0" IsChecked="{Binding AllowDiscordStart, Mode=TwoWay}" Content="{DynamicResource ServerSettings_AllowDiscordStartLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource ServerSettings_AllowDiscordStartTooltip}"/>
<CheckBox Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Margin="5" IsChecked="{Binding AllowDiscordRestart, Mode=TwoWay}" Content="{DynamicResource ServerSettings_AllowDiscordRestartLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource ServerSettings_AllowDiscordRestartTooltip}"/>
<CheckBox Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="2" Margin="5" IsChecked="{Binding AllowDiscordShutdown, Mode=TwoWay}" Content="{DynamicResource ServerSettings_AllowDiscordShutdownLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource ServerSettings_AllowDiscordShutdownTooltip}"/>
<CheckBox Grid.Row="2" Grid.Column="4" Grid.ColumnSpan="2" Margin="5" IsChecked="{Binding AllowDiscordStop, Mode=TwoWay}" Content="{DynamicResource ServerSettings_AllowDiscordStopLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource ServerSettings_AllowDiscordStopTooltip}"/>
</Grid>
</Expander>
<Expander Name="SectionRules" IsExpanded="{Binding CurrentConfig.SectionRulesIsExpanded, ElementName=SettingsControl, FallbackValue=True, Mode=TwoWay}">
<Expander.Header>
<StackPanel Orientation="Horizontal">

View file

@ -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

View file

@ -257,6 +257,12 @@
<setting name="DefaultDataDirectoryName" serializeAs="String">
<value>csmdata</value>
</setting>
<setting name="DiscordBotApplyUrl" serializeAs="String">
<value>https://discord.com/developers/applications</value>
</setting>
<setting name="DiscordBotHelpUrl" serializeAs="String">
<value>https://servermanagers.freeforums.net/thread/99/get-own-discord-bot</value>
</setting>
</ServerManagerTool.Config>
<ServerManagerTool.Common.CommonConfig>
<setting name="DefaultSteamAPIKey" serializeAs="String">
@ -570,6 +576,39 @@
<setting name="ServerFilesGridHeight" serializeAs="String">
<value>250</value>
</setting>
<setting name="DiscordBotEnabled" serializeAs="String">
<value>False</value>
</setting>
<setting name="DiscordBotPrefix" serializeAs="String">
<value>csm</value>
</setting>
<setting name="DiscordBotToken" serializeAs="String">
<value />
</setting>
<setting name="DiscordBotServerId" serializeAs="String">
<value />
</setting>
<setting name="SectionDiscordBotIsExpanded" serializeAs="String">
<value>False</value>
</setting>
<setting name="AllowDiscordBackup" serializeAs="String">
<value>True</value>
</setting>
<setting name="AllowDiscordUpdate" serializeAs="String">
<value>True</value>
</setting>
<setting name="AllowDiscordStart" serializeAs="String">
<value>True</value>
</setting>
<setting name="AllowDiscordRestart" serializeAs="String">
<value>True</value>
</setting>
<setting name="AllowDiscordShutdown" serializeAs="String">
<value>True</value>
</setting>
<setting name="AllowDiscordStop" serializeAs="String">
<value>True</value>
</setting>
</ServerManagerTool.Config>
</userSettings>
</configuration>

View file

@ -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<Plugin.Common.Lib.Profile> 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)

View file

@ -160,6 +160,7 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="Delegates\ServerStatusChangeDelegate.cs" />
<Compile Include="Lib\Model\PlayerListParameters.cs" />
<Compile Include="Lib\Model\RconParameters.cs" />
<Compile Include="Lib\Serialization\IniFileEntryAttribute.cs" />
@ -171,6 +172,8 @@
<Compile Include="Lib\ViewConverters\EnumDescriptionTypeConverter.cs" />
<Compile Include="Lib\ViewConverters\MapNameValueConverter.cs" />
<Compile Include="Lib\ViewModel\PlayerInfo.cs" />
<Compile Include="Utils\DiscordBotHelper.cs" />
<Compile Include="Utils\DiscordPluginHelper.cs" />
<Compile Include="Windows\AddUserWindow.xaml.cs">
<DependentUpon>AddUserWindow.xaml</DependentUpon>
</Compile>

View file

@ -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;
}
}
}
}

View file

@ -545,5 +545,44 @@
<Setting Name="DefaultDataDirectoryName" Type="System.String" Scope="Application">
<Value Profile="(Default)">csmdata</Value>
</Setting>
<Setting Name="DiscordBotEnabled" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="DiscordBotPrefix" Type="System.String" Scope="User">
<Value Profile="(Default)">csm</Value>
</Setting>
<Setting Name="DiscordBotToken" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="DiscordBotServerId" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="DiscordBotApplyUrl" Type="System.String" Scope="Application">
<Value Profile="(Default)">https://discord.com/developers/applications</Value>
</Setting>
<Setting Name="DiscordBotHelpUrl" Type="System.String" Scope="Application">
<Value Profile="(Default)">https://servermanagers.freeforums.net/thread/99/get-own-discord-bot</Value>
</Setting>
<Setting Name="SectionDiscordBotIsExpanded" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="AllowDiscordBackup" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="AllowDiscordUpdate" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="AllowDiscordStart" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="AllowDiscordRestart" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="AllowDiscordShutdown" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="AllowDiscordStop" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
</Settings>
</SettingsFile>

View file

@ -0,0 +1,6 @@
using ServerManagerTool.Enums;
namespace ServerManagerTool.Delegates
{
public delegate void ServerStatusChangeDelegate(ServerStatus serverStatus);
}

View file

@ -3,9 +3,9 @@
public enum AvailabilityStatus
{
Unknown,
NeedPublicIP,
SetPublicIP,
Unavailable,
WaitingForPublication,
Waiting,
Available
}
}

View file

@ -10,5 +10,6 @@
Backup,
Shutdown,
Restart,
Update,
}
}

View file

@ -628,6 +628,9 @@
<sys:String x:Key="MainWindow_SteamCmd_FailedTitle">Reinstall SteamCMD Error</sys:String>
<sys:String x:Key="MainWindow_SteamCmd_FailedLabel">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}</sys:String>
<sys:String x:Key="MainWindow_DiscordBot_RunningCommandsTitle">Discord Bot Running Commands</sys:String>
<sys:String x:Key="MainWindow_DiscordBot_RunningCommandsLabel">The discord bot has one or more running commands, do you want to continue shutting down the server manager?</sys:String>
<sys:String x:Key="MainWindow_ServerStatus_StartServerActionTitle">Start Server Confirmation</sys:String>
<sys:String x:Key="MainWindow_ServerStatus_StartServerActionLabel">You are about to start the server, do you want to continue?</sys:String>
<sys:String x:Key="MainWindow_ServerStatus_ShutdownServerActionTitle">Shutdown Server Confirmation</sys:String>
@ -768,6 +771,23 @@
<sys:String x:Key="GlobalSettings_ShutdownAllMessagesShowReasonLabel">Show shutdown reason with ALL shutdown messages</sys:String>
<sys:String x:Key="GlobalSettings_ShutdownAllMessagesShowReasonTooltip">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.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotLabel">Enable Discord Bot</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotInformationLabel">You will need to restart the server manager if you change any settings for the Discord Bot.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotTokenLabel">Token:</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotTokenTooltip">The token associated with the discord bot.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotServerLabel">Server Id:</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotServerTooltip">The id of the discord server the bot will listen to.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotPrefixLabel">Prefix:</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotPrefixTooltip">The prefix that must be used when sending a command via discord.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotApplyButtonLabel">Get Token...</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotHelpButtonLabel">Help...</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotAllowBackupTooltip">If enabled, the backup command can be sent from discord.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotAllowRestartTooltip">If enabled, the restart command can be sent from discord.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotAllowShutdownTooltip">If enabled, the shutdown command can be sent from discord.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotAllowStartTooltip">If enabled, the start command can be sent from discord.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotAllowStopTooltip">If enabled, the stop command can be sent from discord.</sys:String>
<sys:String x:Key="GlobalSettings_DiscordBotAllowUpdateTooltip">If enabled, the update command can be sent from discord.</sys:String>
<sys:String x:Key="GlobalSettings_EmailSettingsLabel">SMTP Email Settings</sys:String>
<sys:String x:Key="GlobalSettings_EmailHostLabel">Host:</sys:String>
<sys:String x:Key="GlobalSettings_EmailHostTooltip">The name or IP address of the host used for SMTP transmissions.</sys:String>
@ -1121,6 +1141,24 @@
<sys:String x:Key="ServerSettings_RestartIfShutdownTooltip">If enabled, the server will be restarted even if shutdown for Auto-Restarts and Auto-Updates.</sys:String>
<!--#endregion-->
<!--#region Server Settings - Discord Bot Details -->
<sys:String x:Key="ServerSettings_DiscordBotLabel">Discord Bot Details</sys:String>
<sys:String x:Key="ServerSettings_DiscordBotChannelLabel">Channel Id:</sys:String>
<sys:String x:Key="ServerSettings_DiscordBotChannelTooltip">The id of the discord server channel this profile will listen to.</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordBackupLabel">Allow Backup</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordBackupTooltip">If enabled, the profile will listen for backup commands from discord.</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordRestartLabel">Allow Restart</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordRestartTooltip">If enabled, the profile will listen for restart commands from discord.</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordShutdownLabel">Allow Shutdown</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordShutdownTooltip">If enabled, the profile will listen for shutdown commands from discord.</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordStartLabel">Allow Start</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordStartTooltip">If enabled, the profile will listen for start commands from discord.</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordStopLabel">Allow Stop</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordStopTooltip">If enabled, the profile will listen for stop commands from discord.</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordUpdateLabel">Allow Update</sys:String>
<sys:String x:Key="ServerSettings_AllowDiscordUpdateTooltip">If enabled, the profile will listen for update commands from discord.</sys:String>
<!--#endregion-->
<!--#region Server Settings - Server File Details -->
<sys:String x:Key="ServerSettings_ServerFilesLabel">Server File Details</sys:String>
<sys:String x:Key="ServerSettings_ServerFilesWarningLabel">NOTE: Any changes will require a server restart to take effect.</sys:String>
@ -1193,4 +1231,32 @@
<sys:String x:Key="ServerUpdate_WarningLabel">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?</sys:String>
<!--#endregion-->
<!--#region Discord Bot -->
<sys:String x:Key="DiscordBot_ErrorTitle">Discord Bot Error</sys:String>
<sys:String x:Key="DiscordBot_MissingTokenError">The discord bot requires a valid token so it can log into the discord server\r\nThis can be set in the global settings.</sys:String>
<sys:String x:Key="DiscordBot_InvalidPrefixError">The discord bot prefix contains invalid characters. Only letters and numbers are allowed.</sys:String>
<sys:String x:Key="DiscordBot_CommandNotEnabled">Command '{0}' has not been enabled.</sys:String>
<sys:String x:Key="DiscordBot_CommandUnknown">Unknown command '{0}'.</sys:String>
<sys:String x:Key="DiscordBot_CommandRunning">Another command is currently being processed.</sys:String>
<sys:String x:Key="DiscordBot_CommandRunningProfile">Another command '{0}' is currently running against profile '{1}'.</sys:String>
<sys:String x:Key="DiscordBot_CommandDisabledProfile">Command '{0}' has been disabled for profile '{1}'.</sys:String>
<sys:String x:Key="DiscordBot_ProfileMissing">The '{0}' command requires a profile id.</sys:String>
<sys:String x:Key="DiscordBot_ProfileNotFound">Profile '{0}' was not found or is not associated with the channel.</sys:String>
<sys:String x:Key="DiscordBot_ProfileBadStatus">Profile '{0}' is in a state '{1}' that cannot run this command.</sys:String>
<sys:String x:Key="DiscordBot_ProfileUpdating">Profile '{0}' is currently being updated.</sys:String>
<sys:String x:Key="DiscordBot_InfoFailed">Call to server '{0}' failed.</sys:String>
<sys:String x:Key="DiscordBot_BackupRequested">A backup request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_RestartRequested">A restart request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_ShutdownRequested">A shutdown request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_StartRequested">A start request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_StopRequested">A stop request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_UpdateRequested">An update request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_CountLabel">Count:</sys:String>
<sys:String x:Key="DiscordBot_MapLabel">Map:</sys:String>
<!--#endregion-->
</Globalization:GlobalizationResourceDictionary>

View file

@ -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<BranchSnapshot>

View file

@ -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<BranchSnapshot, int>();
// update the server cache for each branch

View file

@ -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]

View file

@ -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,

View file

@ -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<PropertyChangeNotifier> profileNotifiers = new List<PropertyChangeNotifier>();
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)

View file

@ -671,5 +671,15 @@
<Label Content="{DynamicResource GlobalSettings_CacheDirectoryButtonLabel}" VerticalAlignment="Center" Margin="0,-3,0,-3"/>
</StackPanel>
</ContentControl>
<ContentControl x:Key="DiscordBotApplyButtonContent">
<StackPanel Orientation="Horizontal">
<Label Content="{DynamicResource GlobalSettings_DiscordBotApplyButtonLabel}" VerticalAlignment="Center" Margin="0,-2,0,-2"/>
</StackPanel>
</ContentControl>
<ContentControl x:Key="DiscordBotHelpButtonContent">
<StackPanel Orientation="Horizontal">
<Label Content="{DynamicResource GlobalSettings_DiscordBotHelpButtonLabel}" VerticalAlignment="Center" Margin="0,-2,0,-2"/>
</StackPanel>
</ContentControl>
</Globalization:StyleResourceDictionary>

View file

@ -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<string, CommandType> _currentProfileCommands = new Dictionary<string, CommandType>();
public static bool HasRunningCommands => _currentProfileCommands.Count > 0;
public static IList<string> 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<string>();
if (_runningCommand)
return new List<string> { _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> { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
case CommandType.Restart:
if (Config.Default.AllowDiscordRestart)
return RestartServer(channelId, profileId, token);
return new List<string> { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
case CommandType.Shutdown:
if (Config.Default.AllowDiscordShutdown)
return ShutdownServer(channelId, profileId, token);
return new List<string> { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
case CommandType.Stop:
if (Config.Default.AllowDiscordStop)
return StopServer(channelId, profileId, token);
return new List<string> { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
case CommandType.Start:
if (Config.Default.AllowDiscordStart)
return StartServer(channelId, profileId, token);
return new List<string> { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
case CommandType.Update:
if (Config.Default.AllowDiscordUpdate)
return UpdateServer(channelId, profileId, token);
return new List<string> { string.Format(_globalizer.GetResourceString("DiscordBot_CommandNotEnabled"), commandType) };
default:
return new List<string> { 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<string> GetServerInfo(string channelId, string profileId)
{
if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> GetServerList(string channelId)
{
List<string> response = new List<string>();
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<string> GetServerStatus(string channelId, string profileId)
{
List<string> response = new List<string>();
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<string> BackupServer(string channelId, string profileId, CancellationToken token)
{
if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> RestartServer(string channelId, string profileId, CancellationToken token)
{
if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> ShutdownServer(string channelId, string profileId, CancellationToken token)
{
if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> StopServer(string channelId, string profileId, CancellationToken token)
{
if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> StartServer(string channelId, string profileId, CancellationToken token)
{
if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> UpdateServer(string channelId, string profileId, CancellationToken token)
{
if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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);
}
}
}
}
}

View file

@ -0,0 +1,18 @@
using ServerManagerTool.Lib;
using System.Collections.Generic;
using System.Linq;
namespace ServerManagerTool.Utils
{
internal static class DiscordPluginHelper
{
public static IList<Plugin.Common.Lib.Profile> 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();
}
}
}

View file

@ -527,6 +527,55 @@
</GroupBox>
<GroupBox Grid.Row="19" Grid.Column="0" Grid.ColumnSpan="4" Style="{StaticResource GroupBoxStyle}">
<GroupBox.Header>
<CheckBox IsChecked="{Binding Config.DiscordBotEnabled}" Content="{DynamicResource GlobalSettings_DiscordBotLabel}" VerticalAlignment="Center"/>
</GroupBox.Header>
<Grid IsEnabled="{Binding Config.DiscordBotEnabled}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="60"/>
<ColumnDefinition/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto" MinWidth="60"/>
<ColumnDefinition/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto" MinWidth="60"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="8" Margin="5" Text="{DynamicResource GlobalSettings_DiscordBotInformationLabel}" TextWrapping="Wrap" VerticalAlignment="Center" FontWeight="Bold" Foreground="{DynamicResource WarningMessage}"/>
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource GlobalSettings_DiscordBotTokenLabel}" VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="6" Margin="1" Name="HideDiscordBotTokenTextBox" Text="{DynamicResource ServerSettings_HidePasswordText}" ToolTip="{DynamicResource ServerSettings_HidePasswordTooltip}" GotFocus="HiddenField_GotFocus" Style="{StaticResource HiddenTextBoxStyle}"/>
<TextBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="6" Margin="1" Name="DiscordBotTokenTextBox" Text="{Binding Config.DiscordBotToken}" IsReadOnlyCaretVisible="True" VerticalContentAlignment="Center" ToolTip="{DynamicResource GlobalSettings_DiscordBotTokenTooltip}" LostFocus="HiddenField_LostFocus" Visibility="Collapsed"/>
<StackPanel Grid.Row="1" Grid.Column="7" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Margin="1" Content="{DynamicResource DiscordBotApplyButtonContent}" Click="DiscordBotApply_Click"/>
<Button Margin="1" Content="{DynamicResource DiscordBotHelpButtonContent}" Click="DiscordBotHelp_Click"/>
</StackPanel>
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource GlobalSettings_DiscordBotServerLabel}" VerticalAlignment="Center"/>
<TextBox Grid.Row="2" Grid.Column="1" Margin="1" Text="{Binding Config.DiscordBotServerId}" IsReadOnlyCaretVisible="True" VerticalContentAlignment="Center" ToolTip="{DynamicResource GlobalSettings_DiscordBotServerTooltip}"/>
<Label Grid.Row="2" Grid.Column="3" Content="{DynamicResource GlobalSettings_DiscordBotPrefixLabel}" VerticalAlignment="Center"/>
<TextBox Grid.Row="2" Grid.Column="4" Margin="1" Text="{Binding Config.DiscordBotPrefix}" IsReadOnlyCaretVisible="True" VerticalContentAlignment="Center" ToolTip="{DynamicResource GlobalSettings_DiscordBotPrefixTooltip}"/>
<CheckBox Grid.Row="3" Grid.Column="1" Margin="0,5,0,0" IsChecked="{Binding Config.AllowDiscordBackup}" Content="{DynamicResource ServerSettings_AllowDiscordBackupLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource GlobalSettings_DiscordBotAllowBackupTooltip}"/>
<CheckBox Grid.Row="3" Grid.Column="4" Margin="0,5,0,0" IsChecked="{Binding Config.AllowDiscordUpdate}" Content="{DynamicResource ServerSettings_AllowDiscordUpdateLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource GlobalSettings_DiscordBotAllowUpdateTooltip}"/>
<CheckBox Grid.Row="3" Grid.Column="7" Margin="0,5,0,0" IsChecked="{Binding Config.AllowDiscordStart}" Content="{DynamicResource ServerSettings_AllowDiscordStartLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource GlobalSettings_DiscordBotAllowStartTooltip}"/>
<CheckBox Grid.Row="4" Grid.Column="1" Margin="0,5,0,5" IsChecked="{Binding Config.AllowDiscordRestart}" Content="{DynamicResource ServerSettings_AllowDiscordRestartLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource GlobalSettings_DiscordBotAllowRestartTooltip}"/>
<CheckBox Grid.Row="4" Grid.Column="4" Margin="0,5,0,5" IsChecked="{Binding Config.AllowDiscordShutdown}" Content="{DynamicResource ServerSettings_AllowDiscordShutdownLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource GlobalSettings_DiscordBotAllowShutdownTooltip}"/>
<CheckBox Grid.Row="4" Grid.Column="7" Margin="0,5,0,5" IsChecked="{Binding Config.AllowDiscordStop}" Content="{DynamicResource ServerSettings_AllowDiscordStopLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource GlobalSettings_DiscordBotAllowStopTooltip}"/>
</Grid>
</GroupBox>
<GroupBox Grid.Row="20" Grid.Column="0" Grid.ColumnSpan="4" Style="{StaticResource GroupBoxStyle}">
<GroupBox.Header>
<Label Content="{DynamicResource GlobalSettings_EmailSettingsLabel}"/>
</GroupBox.Header>
@ -538,13 +587,13 @@
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" MinWidth="60"/>
<ColumnDefinition/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" MinWidth="60"/>
<ColumnDefinition/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" MinWidth="60"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
@ -569,7 +618,7 @@
</Grid>
</GroupBox>
<GroupBox Grid.Row="20" Grid.Column="0" Grid.ColumnSpan="4" Style="{StaticResource GroupBoxStyle}">
<GroupBox Grid.Row="21" Grid.Column="0" Grid.ColumnSpan="4" Style="{StaticResource GroupBoxStyle}">
<GroupBox.Header>
<Label Content="{DynamicResource GlobalSettings_EmailNotifySettingsLabel}"/>
</GroupBox.Header>
@ -593,7 +642,7 @@
</Grid>
</GroupBox>
<GroupBox Grid.Row="21" Grid.Column="0" Grid.ColumnSpan="4" Style="{StaticResource GroupBoxStyle}">
<GroupBox Grid.Row="22" Grid.Column="0" Grid.ColumnSpan="4" Style="{StaticResource GroupBoxStyle}">
<GroupBox.Header>
<Label Content="{DynamicResource GlobalSettings_AdvancedSettingsLabel}"/>
</GroupBox.Header>

View file

@ -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)
{

View file

@ -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}">
<Window.Resources>
<ResourceDictionary>

View file

@ -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();

View file

@ -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,
});

View file

@ -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

View file

@ -282,13 +282,13 @@
<DataTrigger Binding="{Binding Availability}" Value="{x:Static enum:AvailabilityStatus.Unknown}">
<Setter Property="Content" Value="{DynamicResource ServerSettings_Availability_Unknown}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Availability}" Value="{x:Static enum:AvailabilityStatus.NeedPublicIP}">
<DataTrigger Binding="{Binding Availability}" Value="{x:Static enum:AvailabilityStatus.SetPublicIP}">
<Setter Property="Content" Value="{DynamicResource ServerSettings_Availability_SetPublicIP}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Availability}" Value="{x:Static enum:AvailabilityStatus.Unavailable}">
<Setter Property="Content" Value="{DynamicResource ServerSettings_Availability_Unavailable}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Availability}" Value="{x:Static enum:AvailabilityStatus.WaitingForPublication}">
<DataTrigger Binding="{Binding Availability}" Value="{x:Static enum:AvailabilityStatus.Waiting}">
<Setter Property="Content" Value="{DynamicResource ServerSettings_Availability_Waiting}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Availability}" Value="{x:Static enum:AvailabilityStatus.Available}">
@ -1062,6 +1062,47 @@
</StackPanel>
</Expander>
<Expander Name="SectionDiscordBot" IsExpanded="{Binding Config.SectionDiscordBotIsExpanded, ElementName=SettingsControl, FallbackValue=True, Mode=TwoWay}" Visibility="{Binding Config.DiscordBotEnabled, ElementName=SettingsControl, Converter={StaticResource BooleanToVisibilityConverter}}">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{DynamicResource ServerSettings_DiscordBotLabel}" Style="{StaticResource ExpanderHeaderTextStyle}"/>
</StackPanel>
</Expander.Header>
<Expander.Style>
<Style BasedOn="{StaticResource ExpanderStyle1}" TargetType="{x:Type Expander}">
<Setter Property="Template" Value="{StaticResource ExpanderTemplateSE}"/>
</Style>
</Expander.Style>
<Grid Margin="-8,0,2,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="100"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" MinWidth="100"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" MinWidth="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource ServerSettings_DiscordBotChannelLabel}" ToolTip="{DynamicResource ServerSettings_DiscordBotChannelTooltip}" VerticalAlignment="Center"/>
<TextBox Grid.Row="0" Grid.Column="1" Margin="1" Text="{Binding DiscordChannelId, Mode=TwoWay}" ToolTip="{DynamicResource ServerSettings_DiscordBotChannelTooltip}" VerticalContentAlignment="Center" />
<CheckBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Margin="5,5,5,0" IsChecked="{Binding AllowDiscordBackup, Mode=TwoWay}" Content="{DynamicResource ServerSettings_AllowDiscordBackupLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource ServerSettings_AllowDiscordBackupTooltip}"/>
<CheckBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2" Margin="5,5,5,0" IsChecked="{Binding AllowDiscordUpdate, Mode=TwoWay}" Content="{DynamicResource ServerSettings_AllowDiscordUpdateLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource ServerSettings_AllowDiscordUpdateTooltip}"/>
<CheckBox Grid.Row="1" Grid.Column="4" Grid.ColumnSpan="2" Margin="5,5,5,0" IsChecked="{Binding AllowDiscordStart, Mode=TwoWay}" Content="{DynamicResource ServerSettings_AllowDiscordStartLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource ServerSettings_AllowDiscordStartTooltip}"/>
<CheckBox Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Margin="5" IsChecked="{Binding AllowDiscordRestart, Mode=TwoWay}" Content="{DynamicResource ServerSettings_AllowDiscordRestartLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource ServerSettings_AllowDiscordRestartTooltip}"/>
<CheckBox Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="2" Margin="5" IsChecked="{Binding AllowDiscordShutdown, Mode=TwoWay}" Content="{DynamicResource ServerSettings_AllowDiscordShutdownLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource ServerSettings_AllowDiscordShutdownTooltip}"/>
<CheckBox Grid.Row="2" Grid.Column="4" Grid.ColumnSpan="2" Margin="5" IsChecked="{Binding AllowDiscordStop, Mode=TwoWay}" Content="{DynamicResource ServerSettings_AllowDiscordStopLabel}" HorizontalAlignment="Left" ToolTip="{DynamicResource ServerSettings_AllowDiscordStopTooltip}"/>
</Grid>
</Expander>
<Expander Name="SectionServerFiles" IsExpanded="{Binding Config.SectionServerFilesIsExpanded, ElementName=SettingsControl, FallbackValue=True, Mode=TwoWay}">
<Expander.Header>
<StackPanel Orientation="Horizontal">

View file

@ -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

View file

@ -0,0 +1,8 @@
using ServerManagerTool.DiscordBot.Enums;
using System.Collections.Generic;
using System.Threading;
namespace ServerManagerTool.DiscordBot.Delegates
{
public delegate IList<string> HandleCommandDelegate(CommandType commandType, string serverId, string channelId, string profileId, CancellationToken token);
}

View file

@ -0,0 +1,4 @@
namespace ServerManagerTool.DiscordBot.Delegates
{
public delegate string HandleTranslationDelegate(string translationKey);
}

View file

@ -0,0 +1,9 @@
using ServerManagerTool.DiscordBot.Delegates;
namespace ServerManagerTool.DiscordBot
{
public static class DiscordBot
{
public const string PREFIX_DELIMITER = "!";
}
}

View file

@ -0,0 +1,16 @@
namespace ServerManagerTool.DiscordBot.Enums
{
public enum CommandType
{
Info,
List,
Status,
Backup,
Restart,
Shutdown,
Start,
Stop,
Update,
}
}

View file

@ -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);
}
}

View file

@ -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<SocketCommandContext>
{
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<string>(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());
}
}
}

View file

@ -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})");
}
}
}
}

View file

@ -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})");
}
}
}
}

View file

@ -1,15 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Globals">
<Configurations>Debug;Release;Debug - Beta</Configurations>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net462</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<RootNamespace>ServerManagerTool.Discord</RootNamespace>
<RootNamespace>ServerManagerTool.DiscordBot</RootNamespace>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug - Beta|AnyCPU'">
<DefineConstants>$(DefineConstants);DEBUG</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Discord.Addons.Interactive" Version="2.0.0" />
<PackageReference Include="Discord.Net" Version="2.4.0" />
<PackageReference Include="Discord.Net.Providers.WS4Net" Version="2.4.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Services\" />
</ItemGroup>
</Project>

View file

@ -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<string, string>
{
{ "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<CommandHandlerService>()
.AddSingleton<InteractiveService>()
.AddSingleton<LoggingService>()
.AddSingleton<StartupService>()
.AddSingleton<ShutdownService>()
.AddSingleton<Random>()
.AddSingleton(config)
.AddSingleton(handleCommandCallback)
.AddSingleton(handleTranslationCallback)
.AddSingleton<IServerManagerBot>(this);
// Create the service provider
using (var provider = services.BuildServiceProvider())
{
// Initialize the logging service, startup service, and command handler
provider?.GetRequiredService<LoggingService>();
await provider?.GetRequiredService<StartupService>().StartAsync();
provider?.GetRequiredService<CommandHandlerService>();
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<ShutdownService>().StopAsync();
}
}
}
}

View file

@ -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;
}
}
}

View file

@ -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());
}
}
}
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}
}
}

View file

@ -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);
}
}
}