diff --git a/.editorconfig b/.editorconfig index e84e194..2db502b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,3 +2,89 @@ # CS8618: Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. dotnet_diagnostic.CS8618.severity = suggestion +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_throw_expression = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_indent_labels = one_less_than_current +csharp_space_around_binary_operators = before_and_after + +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_namespace_match_folder = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf diff --git a/DiscordPlayerCountBot/Bot/Bot.cs b/DiscordPlayerCountBot/Bot/Bot.cs index 8a10036..89e488c 100644 --- a/DiscordPlayerCountBot/Bot/Bot.cs +++ b/DiscordPlayerCountBot/Bot/Bot.cs @@ -29,6 +29,8 @@ public void InitDataProviders() DataProviders.Add((int)DataProvider.CFX, new CFXProvider(Information!)); DataProviders.Add((int)DataProvider.MINECRAFT, new MinecraftProvider(Information!)); DataProviders.Add((int)DataProvider.BATTLEMETRICS, new BattleMetricsProvider(Information!)); + DataProviders.Add((int)DataProvider.RCONClient, new RconProvider(Information!)); + DataProviders.Add((int)DataProvider.SteamQuery, new SteamQueryProvider(Information!)); } public async Task StartAsync(bool shouldStart) diff --git a/DiscordPlayerCountBot/Bot/BotInformation.cs b/DiscordPlayerCountBot/Bot/BotInformation.cs index 4b4b345..66e55c0 100644 --- a/DiscordPlayerCountBot/Bot/BotInformation.cs +++ b/DiscordPlayerCountBot/Bot/BotInformation.cs @@ -14,6 +14,7 @@ public class BotInformation public string? StatusFormat { get; set; } public int? SunriseHour { get; set; } public int? SunsetHour { get; set; } + public string? RconServiceName { get; set; } public Tuple GetAddressAndPort() { diff --git a/DiscordPlayerCountBot/DiscordPlayerCountBot.csproj b/DiscordPlayerCountBot/DiscordPlayerCountBot.csproj index 94d7fda..71a54fe 100644 --- a/DiscordPlayerCountBot/DiscordPlayerCountBot.csproj +++ b/DiscordPlayerCountBot/DiscordPlayerCountBot.csproj @@ -16,7 +16,8 @@ - + + diff --git a/DiscordPlayerCountBot/Enum/DataProvider.cs b/DiscordPlayerCountBot/Enums/DataProvider.cs similarity index 52% rename from DiscordPlayerCountBot/Enum/DataProvider.cs rename to DiscordPlayerCountBot/Enums/DataProvider.cs index 51fd4a9..66f7585 100644 --- a/DiscordPlayerCountBot/Enum/DataProvider.cs +++ b/DiscordPlayerCountBot/Enums/DataProvider.cs @@ -1,4 +1,4 @@ -namespace PlayerCountBot.Enum +namespace PlayerCountBot.Enums { public enum DataProvider { @@ -6,6 +6,8 @@ public enum DataProvider CFX, SCUM, MINECRAFT, - BATTLEMETRICS + BATTLEMETRICS, + RCONClient, + SteamQuery } } diff --git a/DiscordPlayerCountBot/Enum/EnumHelper.cs b/DiscordPlayerCountBot/Enums/EnumHelper.cs similarity index 94% rename from DiscordPlayerCountBot/Enum/EnumHelper.cs rename to DiscordPlayerCountBot/Enums/EnumHelper.cs index fdc9946..bc7eca2 100644 --- a/DiscordPlayerCountBot/Enum/EnumHelper.cs +++ b/DiscordPlayerCountBot/Enums/EnumHelper.cs @@ -1,4 +1,4 @@ -namespace PlayerCountBot.Enum +namespace PlayerCountBot.Enums { public static class EnumHelper { diff --git a/DiscordPlayerCountBot/Enum/HostEnviroment.cs b/DiscordPlayerCountBot/Enums/HostEnviroment.cs similarity index 70% rename from DiscordPlayerCountBot/Enum/HostEnviroment.cs rename to DiscordPlayerCountBot/Enums/HostEnviroment.cs index 1fa9e0f..f1bdfde 100644 --- a/DiscordPlayerCountBot/Enum/HostEnviroment.cs +++ b/DiscordPlayerCountBot/Enums/HostEnviroment.cs @@ -1,4 +1,4 @@ -namespace PlayerCountBot.Enum +namespace PlayerCountBot.Enums { public enum HostEnvironment { diff --git a/DiscordPlayerCountBot/Enums/RconServiceType.cs b/DiscordPlayerCountBot/Enums/RconServiceType.cs new file mode 100644 index 0000000..e04becd --- /dev/null +++ b/DiscordPlayerCountBot/Enums/RconServiceType.cs @@ -0,0 +1,9 @@ +namespace DiscordPlayerCountBot.Enums +{ + public enum RconServiceType + { + CSGO, + Minecraft, + Ark + } +} diff --git a/DiscordPlayerCountBot/Exceptions/ConfigurationException.cs b/DiscordPlayerCountBot/Exceptions/ConfigurationException.cs new file mode 100644 index 0000000..d4ffa67 --- /dev/null +++ b/DiscordPlayerCountBot/Exceptions/ConfigurationException.cs @@ -0,0 +1,10 @@ +namespace DiscordPlayerCountBot.Exceptions +{ + internal class ConfigurationException : Exception + { + public ConfigurationException(string? message) : base(message) + { + + } + } +} diff --git a/DiscordPlayerCountBot/Exceptions/ParsingException.cs b/DiscordPlayerCountBot/Exceptions/ParsingException.cs new file mode 100644 index 0000000..dcc7c60 --- /dev/null +++ b/DiscordPlayerCountBot/Exceptions/ParsingException.cs @@ -0,0 +1,10 @@ +namespace DiscordPlayerCountBot.Exceptions +{ + public class ParsingException : Exception + { + public ParsingException(string? message) : base(message) + { + + } + } +} diff --git a/DiscordPlayerCountBot/Exceptions/SteamQueryAuthenticationException.cs b/DiscordPlayerCountBot/Exceptions/SteamQueryAuthenticationException.cs new file mode 100644 index 0000000..a5309d8 --- /dev/null +++ b/DiscordPlayerCountBot/Exceptions/SteamQueryAuthenticationException.cs @@ -0,0 +1,9 @@ +namespace DiscordPlayerCountBot.Exceptions +{ + internal class RconAuthenticationException : Exception + { + public RconAuthenticationException(string? message) : base(message) + { + } + } +} diff --git a/DiscordPlayerCountBot/Program.cs b/DiscordPlayerCountBot/Program.cs index 92fa7e0..d25e85e 100644 --- a/DiscordPlayerCountBot/Program.cs +++ b/DiscordPlayerCountBot/Program.cs @@ -2,7 +2,7 @@ global using PlayerCountBot.Attributes; global using PlayerCountBot.Configuration.Base; global using PlayerCountBot.Data; -global using PlayerCountBot.Enum; +global using PlayerCountBot.Enums; global using PlayerCountBot.Http; global using PlayerCountBot.Json; global using PlayerCountBot.Services; diff --git a/DiscordPlayerCountBot/Providers/Base/ServerInformationProvider.cs b/DiscordPlayerCountBot/Providers/Base/ServerInformationProvider.cs index 145bc97..77e67dc 100644 --- a/DiscordPlayerCountBot/Providers/Base/ServerInformationProvider.cs +++ b/DiscordPlayerCountBot/Providers/Base/ServerInformationProvider.cs @@ -1,4 +1,5 @@ -using System.Net; +using SteamServerQuery; +using System.Net; using System.Net.Http; namespace PlayerCountBot.Providers.Base @@ -17,7 +18,6 @@ protected void HandleLastException(BotInformation information) { if (WasLastExecutionAFailure) { - Info($"Bot named: {information.Name} at address: {information.Address} successfully fetched data after failure."); LastException = null; WasLastExecutionAFailure = false; @@ -76,6 +76,12 @@ protected void HandleException(Exception e) return; } + if (e is SteamException steamException) + { + Error($"There was an issue speaking with Steam Query Server.", e); + return; + } + Error($"There was an error speaking with {Label}.", e); throw e; } diff --git a/DiscordPlayerCountBot/Providers/RconProvider.cs b/DiscordPlayerCountBot/Providers/RconProvider.cs new file mode 100644 index 0000000..d540515 --- /dev/null +++ b/DiscordPlayerCountBot/Providers/RconProvider.cs @@ -0,0 +1,49 @@ +using DiscordPlayerCountBot.Enums; +using DiscordPlayerCountBot.Exceptions; +using DiscordPlayerCountBot.Services; + +namespace PlayerCountBot.Providers +{ + [Name("Rcon")] + public class RconProvider : ServerInformationProvider + { + public RconProvider(BotInformation info) : base(info) + { + } + + public async override Task GetServerInformation(BotInformation information, Dictionary applicationVariables) + { + var values = $"Valid Values: {string.Join(",", Enum.GetNames())}"; + + if (information.RconServiceName == null) + { + throw new ConfigurationException($"Bot: {information.Name} must have RconServiceName specified in it's config. {values}"); + } + + var service = new RconService(); + + if (!Enum.TryParse(information.RconServiceName, true, out var serviceType)) + { + throw new ConfigurationException($"Bot: {information.Name} has an invalid RconServiceName specified in it's config. {values}"); + } + + try + { + var addressAndPort = information.GetAddressAndPort(); + var response = await service.GetRconResponse(addressAndPort.Item1, addressAndPort.Item2, applicationVariables["RconPassword"], serviceType); + + if (response == null) + throw new ApplicationException($"Server Address: {information.Address} was not found in Steam's directory."); + + HandleLastException(information); + + return response; + } + catch (Exception e) + { + HandleException(e); + return null; + } + } + } +} diff --git a/DiscordPlayerCountBot/Providers/SteamQueryProvider.cs b/DiscordPlayerCountBot/Providers/SteamQueryProvider.cs new file mode 100644 index 0000000..8a02379 --- /dev/null +++ b/DiscordPlayerCountBot/Providers/SteamQueryProvider.cs @@ -0,0 +1,37 @@ +using DiscordPlayerCountBot.Services.SteamQuery; + +namespace PlayerCountBot.Providers +{ + [Name("Steam Query")] + public class SteamQueryProvider : ServerInformationProvider + { + public SteamQueryProvider(BotInformation info) : base(info) + { + } + + public async override Task GetServerInformation(BotInformation information, Dictionary applicationVariables) + { + var service = new SteamQueryService(); + + try + { + var addressAndPort = information.GetAddressAndPort(); + var response = await service.GetQueryResponse(addressAndPort.Item1, addressAndPort.Item2); + + if (response == null) + { + throw new ApplicationException($" Failed to get a Server Information response from Steam Query."); + } + + HandleLastException(information); + + return response; + } + catch (Exception e) + { + HandleException(e); + return null; + } + } + } +} diff --git a/DiscordPlayerCountBot/Services/Rcon/IRconService.cs b/DiscordPlayerCountBot/Services/Rcon/IRconService.cs new file mode 100644 index 0000000..47ce035 --- /dev/null +++ b/DiscordPlayerCountBot/Services/Rcon/IRconService.cs @@ -0,0 +1,9 @@ +using DiscordPlayerCountBot.Enums; + +namespace DiscordPlayerCountBot.Services +{ + public interface IRconService + { + public Task GetRconResponse(string address, int port, string authorizationToken, RconServiceType serviceType); + } +} diff --git a/DiscordPlayerCountBot/Services/Rcon/Praser/ArkInformationParser.cs b/DiscordPlayerCountBot/Services/Rcon/Praser/ArkInformationParser.cs new file mode 100644 index 0000000..528c264 --- /dev/null +++ b/DiscordPlayerCountBot/Services/Rcon/Praser/ArkInformationParser.cs @@ -0,0 +1,29 @@ +using DiscordPlayerCountBot.Exceptions; +using System.Text.RegularExpressions; + +namespace DiscordPlayerCountBot.Services.Praser +{ + public class ArkInformationParser : IRconInformationParser + { + public BaseViewModel Parse(string message) + { + var match = Regex.Match(message, @"There are (\d+)/(\d+) players online(?:.*\nQueue:\s+(\d+)\s+players waiting)?"); + + if (!match.Success) + { + throw new ParsingException("Could not find players, max players, or queue information from an ARK RCON Response"); + } + + var players = int.Parse(match.Groups[1].Value); + var maxPlayers = int.Parse(match.Groups[2].Value); + int queuedPlayers = match.Groups.Count < 3 || string.IsNullOrEmpty(match.Groups[3].Value) ? 0 : int.Parse(match.Groups[3].Value); + + return new BaseViewModel() + { + Players = players, + MaxPlayers = maxPlayers, + QueuedPlayers = queuedPlayers + }; + } + } +} diff --git a/DiscordPlayerCountBot/Services/Rcon/Praser/CSGOInformationParser.cs b/DiscordPlayerCountBot/Services/Rcon/Praser/CSGOInformationParser.cs new file mode 100644 index 0000000..21b44e5 --- /dev/null +++ b/DiscordPlayerCountBot/Services/Rcon/Praser/CSGOInformationParser.cs @@ -0,0 +1,31 @@ +using DiscordPlayerCountBot.Exceptions; +using System.Text.RegularExpressions; + +namespace DiscordPlayerCountBot.Services.Praser +{ + public class CSGOInformationParser : IRconInformationParser + { + public BaseViewModel Parse(string message) + { + var playersMatch = Regex.Match(message, @"players\s+:\s+(\d+)\s+humans"); + var maxPlayersMatch = Regex.Match(message, @"\((\d+)/\d+\s+max\)"); + var queuedPlayersMatch = Regex.Match(message, @"queue\s+:\s+(\d+)\s+players waiting"); + + if (!playersMatch.Success || !maxPlayersMatch.Success) + { + throw new ParsingException("Could not find players or max players from a CSGO Rcon Response"); + } + + var players = int.Parse(playersMatch.Groups[0].Value); + var maxPlayers = int.Parse(maxPlayersMatch.Groups[0].Value); + int queuedPlayers = !queuedPlayersMatch.Success ? 0 : int.Parse(queuedPlayersMatch.Groups[0].Value); + + return new BaseViewModel() + { + Players = players, + MaxPlayers = maxPlayers, + QueuedPlayers = queuedPlayers + }; + } + } +} diff --git a/DiscordPlayerCountBot/Services/Rcon/Praser/IRconInformationParser.cs b/DiscordPlayerCountBot/Services/Rcon/Praser/IRconInformationParser.cs new file mode 100644 index 0000000..abd74d9 --- /dev/null +++ b/DiscordPlayerCountBot/Services/Rcon/Praser/IRconInformationParser.cs @@ -0,0 +1,7 @@ +namespace DiscordPlayerCountBot.Services.Praser +{ + public interface IRconInformationParser + { + public BaseViewModel Parse(string message); + } +} diff --git a/DiscordPlayerCountBot/Services/Rcon/Praser/MinecraftInformationParser.cs b/DiscordPlayerCountBot/Services/Rcon/Praser/MinecraftInformationParser.cs new file mode 100644 index 0000000..2411fd1 --- /dev/null +++ b/DiscordPlayerCountBot/Services/Rcon/Praser/MinecraftInformationParser.cs @@ -0,0 +1,30 @@ +using DiscordPlayerCountBot.Exceptions; +using System.Text.RegularExpressions; + +namespace DiscordPlayerCountBot.Services.Praser +{ + public class MinecraftInformationParser : IRconInformationParser + { + public BaseViewModel Parse(string message) + { + var playerInfoMatch = Regex.Match(message, @"(\d+)\s+of a max of\s+(\d+)"); + var queuedPlayersMatch = Regex.Match(message, @"Queue:\s+(\d+)\s+players waiting"); + + if (!playerInfoMatch.Success || playerInfoMatch.Groups.Count < 3) + { + throw new ParsingException("Could not find players or max players from a Minecraft Rcon Response"); + } + + var players = int.Parse(playerInfoMatch.Groups[1].Value); + var maxPlayers = int.Parse(playerInfoMatch.Groups[2].Value); + int queuedPlayers = !queuedPlayersMatch.Success ? 0 : int.Parse(queuedPlayersMatch.Groups[1].Value); + + return new BaseViewModel() + { + Players = players, + MaxPlayers = maxPlayers, + QueuedPlayers = queuedPlayers + }; + } + } +} diff --git a/DiscordPlayerCountBot/Services/Rcon/RconService.cs b/DiscordPlayerCountBot/Services/Rcon/RconService.cs new file mode 100644 index 0000000..474025e --- /dev/null +++ b/DiscordPlayerCountBot/Services/Rcon/RconService.cs @@ -0,0 +1,45 @@ +using DiscordPlayerCountBot.Enums; +using DiscordPlayerCountBot.Exceptions; +using DiscordPlayerCountBot.Services.Rcon.ServiceInformation; +using RconSharp; + +namespace DiscordPlayerCountBot.Services +{ + public class RconService : IRconService + { + private readonly Dictionary PlayerCommands = new() + { + {RconServiceType.CSGO, new CSGORconServiceInformation()}, + {RconServiceType.Minecraft, new MinecraftRconServiceInformation()}, + {RconServiceType.Ark, new ArkRconServiceInformation()} + }; + + public async Task GetRconResponse(string address, int port, string authorizationToken, RconServiceType serviceType) + { + if (!PlayerCommands.TryGetValue(serviceType, out var serviceInformation)) + { + throw new ArgumentException("Unsupported Rcon Service Type.... How did you get here?"); + } + + var client = RconClient.Create(address, port); + + await client.ConnectAsync(); + + var authenticated = await client.AuthenticateAsync(authorizationToken); + + if (!authenticated) + { + throw new RconAuthenticationException($"There was a failure to authenticate with server: {address}:{port}.\nIs your password correct?\nIs this the correct server address?"); + } + + var command = serviceInformation.GetPlayerListCommand(); + var response = await client.ExecuteCommandAsync(command); + var viewModel = serviceInformation.GetParser().Parse(response); + + viewModel.Address = address; + viewModel.Port = port; + + return viewModel; + } + } +} diff --git a/DiscordPlayerCountBot/Services/Rcon/RconServiceInformation.cs b/DiscordPlayerCountBot/Services/Rcon/RconServiceInformation.cs new file mode 100644 index 0000000..40f8184 --- /dev/null +++ b/DiscordPlayerCountBot/Services/Rcon/RconServiceInformation.cs @@ -0,0 +1,12 @@ +using DiscordPlayerCountBot.Enums; +using DiscordPlayerCountBot.Services.Praser; + +namespace DiscordPlayerCountBot.Services +{ + public interface IRconServiceInformation + { + public abstract RconServiceType GetServiceType(); + public abstract string GetPlayerListCommand(); + public abstract IRconInformationParser GetParser(); + } +} diff --git a/DiscordPlayerCountBot/Services/Rcon/ServiceInformation/ArkRconServiceInformation.cs b/DiscordPlayerCountBot/Services/Rcon/ServiceInformation/ArkRconServiceInformation.cs new file mode 100644 index 0000000..0fd54c9 --- /dev/null +++ b/DiscordPlayerCountBot/Services/Rcon/ServiceInformation/ArkRconServiceInformation.cs @@ -0,0 +1,23 @@ +using DiscordPlayerCountBot.Enums; +using DiscordPlayerCountBot.Services.Praser; + +namespace DiscordPlayerCountBot.Services.Rcon.ServiceInformation +{ + public class ArkRconServiceInformation : IRconServiceInformation + { + public IRconInformationParser GetParser() + { + return new ArkInformationParser(); + } + + public RconServiceType GetServiceType() + { + return RconServiceType.Ark; + } + + public string GetPlayerListCommand() + { + return "listplayers"; + } + } +} diff --git a/DiscordPlayerCountBot/Services/Rcon/ServiceInformation/CSGORconServiceInformation.cs b/DiscordPlayerCountBot/Services/Rcon/ServiceInformation/CSGORconServiceInformation.cs new file mode 100644 index 0000000..1dc2ad3 --- /dev/null +++ b/DiscordPlayerCountBot/Services/Rcon/ServiceInformation/CSGORconServiceInformation.cs @@ -0,0 +1,23 @@ +using DiscordPlayerCountBot.Enums; +using DiscordPlayerCountBot.Services.Praser; + +namespace DiscordPlayerCountBot.Services.Rcon.ServiceInformation +{ + public class CSGORconServiceInformation : IRconServiceInformation + { + public IRconInformationParser GetParser() + { + return new CSGOInformationParser(); + } + + public RconServiceType GetServiceType() + { + return RconServiceType.CSGO; + } + + public string GetPlayerListCommand() + { + return "status"; + } + } +} diff --git a/DiscordPlayerCountBot/Services/Rcon/ServiceInformation/MinecraftRconServiceInformation.cs b/DiscordPlayerCountBot/Services/Rcon/ServiceInformation/MinecraftRconServiceInformation.cs new file mode 100644 index 0000000..e99a2e9 --- /dev/null +++ b/DiscordPlayerCountBot/Services/Rcon/ServiceInformation/MinecraftRconServiceInformation.cs @@ -0,0 +1,23 @@ +using DiscordPlayerCountBot.Enums; +using DiscordPlayerCountBot.Services.Praser; + +namespace DiscordPlayerCountBot.Services.Rcon.ServiceInformation +{ + public class MinecraftRconServiceInformation : IRconServiceInformation + { + public IRconInformationParser GetParser() + { + return new MinecraftInformationParser(); + } + + public RconServiceType GetServiceType() + { + return RconServiceType.Minecraft; + } + + public string GetPlayerListCommand() + { + return "list"; + } + } +} diff --git a/DiscordPlayerCountBot/Services/SteamQuery/ISteamQueryService.cs b/DiscordPlayerCountBot/Services/SteamQuery/ISteamQueryService.cs new file mode 100644 index 0000000..5eb2f46 --- /dev/null +++ b/DiscordPlayerCountBot/Services/SteamQuery/ISteamQueryService.cs @@ -0,0 +1,9 @@ +using DiscordPlayerCountBot.Enums; + +namespace DiscordPlayerCountBot.Services.SteamQuery +{ + internal interface ISteamQueryService + { + public Task GetQueryResponse(string address, int port); + } +} diff --git a/DiscordPlayerCountBot/Services/SteamQuery/SteamQueryService.cs b/DiscordPlayerCountBot/Services/SteamQuery/SteamQueryService.cs new file mode 100644 index 0000000..abf3df0 --- /dev/null +++ b/DiscordPlayerCountBot/Services/SteamQuery/SteamQueryService.cs @@ -0,0 +1,29 @@ +using SteamServerQuery; + +namespace DiscordPlayerCountBot.Services.SteamQuery +{ + public class SteamQueryService : ISteamQueryService + { + public async Task GetQueryResponse(string address, int port) + { + var model = new BaseViewModel() + { + Address = address, + Port = port + }; + + try + { + var serverInformation = await SteamServer.QueryServerAsync(address, port = 0); + model.MaxPlayers = serverInformation.MaxPlayers; + model.Players = serverInformation.Players; + model.QueuedPlayers = 0; + } catch + { + throw; + } + + return model; + } + } +}