diff --git a/.gitignore b/.gitignore index 796453635..31b0cf551 100644 --- a/.gitignore +++ b/.gitignore @@ -186,4 +186,7 @@ FakesAssemblies/ # LightSwitch generated files GeneratedArtifacts/ _Pvt_Extensions/ -ModelManifest.xml \ No newline at end of file +ModelManifest.xml +/.vs/IW4M Admin/v15 +/.vs/IW4M Admin/v15/Browse.VC.db +/.vs/IW4M Admin/v15 diff --git a/Admin/Commands.cs b/Admin/Commands.cs new file mode 100644 index 000000000..1ecf1a11a --- /dev/null +++ b/Admin/Commands.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; +using SharedLibrary; +using SharedLibrary.Network; +using System.Threading.Tasks; + +namespace IW4MAdmin +{ + class Plugins : Command + { + public Plugins(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override async Task ExecuteAsync(Event E) + { + await E.Origin.Tell("^5Loaded Plugins:"); + foreach (SharedLibrary.Extensions.IPlugin P in PluginImporter.potentialPlugins) + { + await E.Origin.Tell(String.Format("^3{0} ^7[v^3{1}^7] by ^5{2}^7", P.Name, P.Version, P.Author)); + } + } + } +} + \ No newline at end of file diff --git a/Admin/Config.cs b/Admin/Config.cs new file mode 100644 index 000000000..4c1cc714f --- /dev/null +++ b/Admin/Config.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net; + +using SharedLibrary.Interfaces; + + +namespace IW4MAdmin +{ + public class Config : Serialize + { + public string IP; + public int Port; + public string Password; + public string FtpPrefix; + + public override string Filename() + { + return $"config/servers/{IP}_{Port}.cfg"; + } + + public static Config Generate() + { + string IP = String.Empty; + int Port = 0; + string Password; + + while(IP == String.Empty) + { + try + { + Console.Write("Enter server IP: "); + string input = Console.ReadLine(); + IPAddress.Parse(input); + IP = input; + } + + catch (Exception) + { + continue; + } + } + + while(Port == 0) + { + try + { + Console.Write("Enter server port: "); + Port = Int32.Parse(Console.ReadLine()); + } + + catch (Exception) + { + continue; + } + } + + Console.Write("Enter server RCON password: "); + Password = Console.ReadLine(); + + var config = new Config() { IP = IP, Password = Password, Port = Port }; + config.Write(); + + Console.WriteLine("Config saved, add another? [y/n]:"); + if (Console.ReadLine().ToLower().First() == 'y') + Generate(); + + return config; + } + } +} diff --git a/Admin/Heartbeat.cs b/Admin/Heartbeat.cs index 986ceade7..ee9e61a7e 100644 --- a/Admin/Heartbeat.cs +++ b/Admin/Heartbeat.cs @@ -10,16 +10,15 @@ namespace IW4MAdmin public Heartbeat(Server I) { Handle = new Connection("http://raidmax.org/IW4M/Admin"); - Instance = I; } - public void Send() + public void Send(Server S) { - String URI = String.Format("http://raidmax.org/IW4M/Admin/heartbeat.php?address={0}&name={1}&map={2}&players={3}&version={4}", Instance.getPort().ToString(), Instance.getName(), Instance.getMap(), Instance.getClientNum() + '/' + Instance.getMaxClients().ToString(), IW4MAdmin.Program.Version.ToString()); + String URI = String.Format("http://raidmax.org/IW4M/Admin/heartbeat.php?port={0}&name={1}&map={2}&players={3}&version={4}&gametype={5}&servercount={6}", S.getPort(), S.getName(), S.CurrentMap.Name, S.getPlayers().Count, IW4MAdmin.Program.Version.ToString(), S.Gametype, Manager.GetInstance().Servers); + // blind fire Handle.Request(URI); } private Connection Handle; - private Server Instance; } } diff --git a/Admin/IW4M ADMIN.csproj b/Admin/IW4M ADMIN.csproj index b19b49b17..c5ed3fb00 100644 --- a/Admin/IW4M ADMIN.csproj +++ b/Admin/IW4M ADMIN.csproj @@ -9,7 +9,7 @@ Properties IW4MAdmin IW4MAdmin - v4.0 + v4.5 512 @@ -61,6 +61,8 @@ false false false + + LocalIntranet @@ -88,25 +90,29 @@ app.manifest - + False - lib\SharedLibrary.dll + lib\Kayak.dll + False + + + False + ..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll + True - - False - libs\System.Data.SQLite.dll - False - + - + + + @@ -117,19 +123,15 @@ Settings.settings - - + - - PreserveNewest - - - PreserveNewest - - + + PreserveNewest + + PreserveNewest @@ -138,41 +140,47 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - Always - Always + PreserveNewest + + + PreserveNewest PreserveNewest - Always + PreserveNewest PreserveNewest - Always + PreserveNewest + + + PreserveNewest + + + PreserveNewest - Always + PreserveNewest + + + PreserveNewest PreserveNewest - - Always + + PreserveNewest - - Always + + PreserveNewest PreserveNewest @@ -186,6 +194,10 @@ PreserveNewest + + PreserveNewest + + PreserveNewest @@ -198,6 +210,7 @@ PreserveNewest + SettingsSingleFileGenerator @@ -298,10 +311,24 @@ Assembly + + + {d51eeceb-438a-47da-870f-7d7b41bc24d6} + SharedLibrary + False + + + - - + move "$(TargetDir)Newtonsoft.Json.dll" "$(TargetDir)lib\Newtonsoft.Json.dll" +copy /Y "$(SolutionDir)lib\*.dll" "$(TargetDir)lib" + +copy /Y "$(TargetDir)$(TargetName).exe" "$(SolutionDir)BUILD" +copy /Y "$(TargetDir)IW4MAdmin.exe.config" "$(SolutionDir)BUILD" +copy /Y "$(ProjectDir)lib\Kayak.dll" "$(SolutionDir)BUILD\lib" +copy /Y "$(ProjectDir)lib\SQLite.Interop.dll" "$(SolutionDir)BUILD\lib" + + \ No newline at end of file diff --git a/Auto Restart Plugin/Main.cs b/Auto Restart Plugin/Main.cs new file mode 100644 index 000000000..462d2ab9f --- /dev/null +++ b/Auto Restart Plugin/Main.cs @@ -0,0 +1,72 @@ +using System; +using SharedLibrary; +using SharedLibrary.Extensions; +using System.Threading.Tasks; + +namespace Auto_Restart_Plugin +{ + public class Main : IPlugin + { + public string Author + { + get + { + return "RaidMax"; + } + } + + public float Version + { + get + { + return 1.0f; + } + } + + public string Name + { + get + { + return "Auto Restart Plugin"; + } + } + + public async Task OnLoad() + { + return; + } + + public async Task OnUnload() + { + return; + } + + public async Task OnTick(Server S) + { + switch (Monitoring.shouldRestart()) + { + case 300: + await S.Broadcast("^1Server will be performing an ^5AUTOMATIC ^1restart in ^55 ^1minutes."); + break; + case 120: + await S.Broadcast("^1Server will be performing an ^5AUTOMATIC ^1restart in ^52 ^1minutes."); + break; + case 60: + await S.Broadcast("^1Server will be performing an ^5AUTOMATIC ^1restart in ^51 ^1minute."); + break; + case 30: + await S.Broadcast("^1Server will be performing an ^5AUTOMATIC ^1restart in ^530 ^1seconds."); + break; + case 0: + await S.Broadcast("^1Server now performing an ^5AUTOMATIC ^1restart ^5NOW ^1please reconnect."); + Monitoring.Restart(S); + break; + } + } + + public async Task OnEvent(Event E, Server S) + { + return; + } + } +} diff --git a/Auto Restart Plugin/Monitoring.cs b/Auto Restart Plugin/Monitoring.cs new file mode 100644 index 000000000..098717996 --- /dev/null +++ b/Auto Restart Plugin/Monitoring.cs @@ -0,0 +1,127 @@ +using System; +using System.Diagnostics; +using System.Text; +using System.Threading; +using System.Runtime.InteropServices; +using System.Management; +using SharedLibrary; + +namespace Auto_Restart_Plugin +{ + static class Monitoring + { + [DllImport("kernel32")] + public static extern bool DeleteFile(string name); + + public static void Restart(Server goodBye) + { + _Restart(goodBye); + } + + private static void _Restart(Server goodBye) + { + try + { + string cmdLine = GetCommandLine(Process.GetProcessById(goodBye.pID())); + var info = new ProcessStartInfo(); + + // if we don't delete this, we get a prompt.. + DeleteFile(Process.GetProcessById(goodBye.pID()).MainModule.FileName + ":Zone.Identifier"); + + //info.WorkingDirectory = goodBye.Basepath; + info.Arguments = cmdLine; + info.FileName = Process.GetProcessById(goodBye.pID()).MainModule.FileName; + // goodBye.executeCommand("killserver"); + + Process.GetProcessById(Process.GetProcessById(goodBye.pID()).Parent().Id).Kill(); + Process.GetProcessById(goodBye.pID()).Kill(); + + Process.Start(info); + } + + catch (Exception E) + { + goodBye.Log.Write("SOMETHING FUCKED UP BEYOND ALL REPAIR " + E.ToString()); + } + } + + public static int shouldRestart() + { + var curTime = DateTime.Now; + DateTime restartTime = new DateTime(curTime.Year, curTime.Month, curTime.Day, 4, 0, 0); + var a = Math.Floor((restartTime - curTime).TotalMilliseconds / 1000); + if (a > 0 && a < 2) // just in case of precision + return 0; + else + { + switch((int)a) + { + case 300: + return 300; + case 120: + return 120; + case 60: + return 60; + case 30: + return 30; + default: + return 1337; + } + } + } + + //http://stackoverflow.com/questions/2633628/can-i-get-command-line-arguments-of-other-processes-from-net-c + private static string GetCommandLine(this Process process) + { + var commandLine = new StringBuilder(); + commandLine.Append(" "); + using (var searcher = new ManagementObjectSearcher("SELECT CommandLine FROM Win32_Process WHERE ProcessId = " + process.Id)) + { + foreach (var @object in searcher.Get()) + { + if (@object["CommandLine"].ToString().Contains("iw4m")) + commandLine.Append(@object["CommandLine"].ToString().Substring(4)); + else + commandLine.Append(@object["CommandLine"]); + commandLine.Append(" "); + } + } + + return commandLine.ToString().Trim(); + } + } + + + public static class ProcessExtensions + { + private static string FindIndexedProcessName(int pid) + { + var processName = Process.GetProcessById(pid).ProcessName; + var processesByName = Process.GetProcessesByName(processName); + string processIndexdName = null; + + for (var index = 0; index < processesByName.Length; index++) + { + processIndexdName = index == 0 ? processName : processName + "#" + index; + var processId = new PerformanceCounter("Process", "ID Process", processIndexdName); + if ((int)processId.NextValue() == pid) + { + return processIndexdName; + } + } + + return processIndexdName; + } + + private static Process FindPidFromIndexedProcessName(string indexedProcessName) + { + var parentId = new PerformanceCounter("Process", "Creating Process ID", indexedProcessName); + return Process.GetProcessById((int)parentId.NextValue()); + } + + public static Process Parent(this Process process) + { + return FindPidFromIndexedProcessName(FindIndexedProcessName(process.Id)); + } + } +} diff --git a/Webfront Plugin/Webfront Plugin.csproj b/Chat Monitor Plugin/Chat Monitor Plugin.csproj similarity index 67% rename from Webfront Plugin/Webfront Plugin.csproj rename to Chat Monitor Plugin/Chat Monitor Plugin.csproj index 211eed9c7..b1912ac22 100644 --- a/Webfront Plugin/Webfront Plugin.csproj +++ b/Chat Monitor Plugin/Chat Monitor Plugin.csproj @@ -4,15 +4,16 @@ Debug AnyCPU - {99E36EBD-1FA1-494C-8A66-BECE64EFF378} + {79406C5E-A8AD-4A50-A7F0-53CE56792A31} Library Properties - Webfront_Plugin - Webfront Plugin + ChatMonitor + ChatMonitor v4.0 512 + AnyCPU true full false @@ -22,6 +23,7 @@ 4 + AnyCPU pdbonly true bin\Release\ @@ -29,31 +31,28 @@ prompt 4 + + + - - ..\Admin\bin\Release\lib\Kayak.dll - - - False - ..\SharedLibrary\bin\Release\SharedLibrary.dll - - - - - - - - + - - - - + + + + + {d51eeceb-438a-47da-870f-7d7b41bc24d6} + SharedLibrary + + + {99e36ebd-1fa1-494c-8a66-bece64eff378} + Webfront Plugin + - copy /Y "$(TargetDir)$(TargetName).dll" "$(SolutionDir)Admin\plugins\WebfrontPlugin.dll" + copy /Y "$(TargetDir)$(TargetName).dll" "$(SolutionDir)Admin\plugins\ChatMonitorPlugin.dll" + \ No newline at end of file diff --git a/EventAPI/Plugin.cs b/EventAPI/Plugin.cs new file mode 100644 index 000000000..0aaa8856b --- /dev/null +++ b/EventAPI/Plugin.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Text; +using SharedLibrary; +using SharedLibrary.Extensions; +using System.Threading.Tasks; + +namespace EventAPI +{ + class EventsJSON : IPage + { + private struct EventResponse + { + public int eventCount; + public RestEvent Event; + } + + public string getName() + { + return "Events"; + } + + public string getPath() + { + return "/api/events"; + } + + public HttpResponse getPage(System.Collections.Specialized.NameValueCollection querySet, IDictionary headers) + { + bool shouldQuery = querySet.Get("status") != null; + EventResponse requestedEvent = new EventResponse(); + HttpResponse resp = new HttpResponse(); + + if (shouldQuery) + { + StringBuilder s = new StringBuilder(); + foreach (var S in Events.activeServers) + s.Append(String.Format("{0} has {1}/{4} players playing {2} on {3}\n", S.getName(), S.getPlayers().Count, Utilities.gametypeLocalized(S.getGametype()), S.CurrentMap.Name, S.MaxClients)); + requestedEvent.Event = new RestEvent(RestEvent.eType.STATUS, RestEvent.eVersion.IW4MAdmin, s.ToString(), "Status", "", ""); + requestedEvent.eventCount = 1; + } + + else if (Events.apiEvents.Count > 0) + { + requestedEvent.Event = Events.apiEvents.Dequeue(); + requestedEvent.eventCount = 1; + } + + else + { + requestedEvent.eventCount = 0; + } + + resp.content = Newtonsoft.Json.JsonConvert.SerializeObject(requestedEvent); + resp.contentType = getContentType(); + resp.additionalHeaders = new Dictionary(); + return resp; + } + + public string getContentType() + { + return "application/json"; + } + + public bool isVisible() + { + return false; + } + } + + class Events : IPlugin + { + public static Queue apiEvents { get; private set; } + public static List activeServers; + + DateTime lastClear; + int flaggedMessages; + List flaggedMessagesText; + + public String Name + { + get { return "Event API Plugin"; } + } + + public float Version + { + get { return 1.0f; } + } + + public string Author + { + get + { + return "RaidMax"; + } + } + + public async Task OnLoad() + { + apiEvents = new Queue(); + flaggedMessagesText = new List(); + activeServers = new List(); + WebService.pageList.Add(new EventsJSON()); + } + + public async Task OnUnload() + { + apiEvents.Clear(); + activeServers.Clear(); + } + + public async Task OnTick(Server S) + { + return; + } + + public async Task OnEvent(Event E, Server S) + { + if (E.Type == Event.GType.Start) + { + activeServers.Add(S); + S.Log.Write("Event API now running on " + S.getName(), Log.Level.Production); + } + + if (E.Type == Event.GType.Stop) + { + activeServers.RemoveAll(s => s.getPort() == S.getPort()); + S.Log.Write("Event API no longer running on " + S.getName(), Log.Level.Production); + } + + if (E.Type == Event.GType.Connect) + { + addRestEvent(new RestEvent(RestEvent.eType.NOTIFICATION, RestEvent.eVersion.IW4MAdmin, E.Origin.Name + " has joined " + S.getName(), E.Type.ToString(), S.getName(), E.Origin.Name)); + } + + if (E.Type == Event.GType.Disconnect) + { + addRestEvent(new RestEvent(RestEvent.eType.NOTIFICATION, RestEvent.eVersion.IW4MAdmin, E.Origin.Name + " has left " + S.getName(), E.Type.ToString(), S.getName(), E.Origin.Name)); + } + + if (E.Type == Event.GType.Say) + { + if (E.Data.Length != 0 && E.Data[0] != '!') + addRestEvent(new RestEvent(RestEvent.eType.NOTIFICATION, RestEvent.eVersion.IW4MAdmin, E.Data, "Chat", E.Origin.Name, "")); + } + + if (E.Type == Event.GType.Say && E.Origin.Level < Player.Permission.Moderator) + { + string message = E.Data.ToLower(); + bool flagged = message.Contains(" wh ") || message.Contains("hax") || message.Contains("cheat") || message.Contains("hack") || message.Contains("aim") || message.Contains("wall") || message.Contains("cheto") || message.Contains("hak"); + + if (flagged) + { + flaggedMessages++; + flaggedMessagesText.Add(String.Format("{0}: {1}", E.Origin.Name, E.Data)); + } + + if (flaggedMessages > 3) + { + await E.Owner.Broadcast("If you suspect someone of ^5CHEATING ^7use the ^5!report ^7command"); + + addRestEvent(new RestEvent(RestEvent.eType.ALERT, RestEvent.eVersion.IW4MAdmin, "Chat indicates there may be a cheater", "Alert", E.Owner.getName(), "")); + addRestEvent(new RestEvent(RestEvent.eType.NOTIFICATION, RestEvent.eVersion.IW4MAdmin, String.Join("\n", flaggedMessagesText), "Chat Monitor", E.Owner.getName(), "")); + flaggedMessages = 0; + } + + else if ((DateTime.Now - lastClear).TotalMinutes >= 3) + { + flaggedMessages = 0; + flaggedMessagesText.Clear(); + lastClear = DateTime.Now; + } + } + } + + public static void addRestEvent(RestEvent E) + { + if (apiEvents.Count > 10) + apiEvents.Dequeue(); + apiEvents.Enqueue(E); + } + } +} diff --git a/EventAPI/packages.config b/EventAPI/packages.config new file mode 100644 index 000000000..0cdd293b3 --- /dev/null +++ b/EventAPI/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/IW4M Admin.sln b/IW4M Admin.sln index 7a32160f3..45383877f 100644 --- a/IW4M Admin.sln +++ b/IW4M Admin.sln @@ -1,17 +1,16 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.24720.0 +# Visual Studio 15 +VisualStudioVersion = 15.0.26403.7 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IW4M ADMIN", "Admin\IW4M ADMIN.csproj", "{DD5DCDA2-51DB-4B1A-922F-5705546E6115}" ProjectSection(ProjectDependencies) = postProject + {2321A25F-7871-47C3-8440-02551918D6A1} = {2321A25F-7871-47C3-8440-02551918D6A1} + {AF097E6B-48D5-4452-9CCF-0A81A21F341D} = {AF097E6B-48D5-4452-9CCF-0A81A21F341D} {4785AB75-66F3-4391-985D-63A5A049A0FA} = {4785AB75-66F3-4391-985D-63A5A049A0FA} - {99E36EBD-1FA1-494C-8A66-BECE64EFF378} = {99E36EBD-1FA1-494C-8A66-BECE64EFF378} - {D51EECEB-438A-47DA-870F-7D7B41BC24D6} = {D51EECEB-438A-47DA-870F-7D7B41BC24D6} - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Webfront Plugin", "Webfront Plugin\Webfront Plugin.csproj", "{99E36EBD-1FA1-494C-8A66-BECE64EFF378}" - ProjectSection(ProjectDependencies) = postProject + {428D8EB9-ECA3-4A66-AA59-3A944378C33F} = {428D8EB9-ECA3-4A66-AA59-3A944378C33F} + {E46C85BD-A99C-484E-BCCE-0F1831C5925E} = {E46C85BD-A99C-484E-BCCE-0F1831C5925E} + {C9E821BF-23AD-4CB5-B7F9-B3B99B606650} = {C9E821BF-23AD-4CB5-B7F9-B3B99B606650} {D51EECEB-438A-47DA-870F-7D7B41BC24D6} = {D51EECEB-438A-47DA-870F-7D7B41BC24D6} EndProjectSection EndProject @@ -27,34 +26,162 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Welcome Plugin", "Welcome P {D51EECEB-438A-47DA-870F-7D7B41BC24D6} = {D51EECEB-438A-47DA-870F-7D7B41BC24D6} EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Auto Restart Plugin", "Auto Restart Plugin\Auto Restart Plugin.csproj", "{2321A25F-7871-47C3-8440-02551918D6A1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Votemap Plugin", "Votemap Plugin\Votemap Plugin.csproj", "{428D8EB9-ECA3-4A66-AA59-3A944378C33F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessageboardPlugin", "MessageboardPlugin\MessageboardPlugin.csproj", "{E46C85BD-A99C-484E-BCCE-0F1831C5925E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventAPI", "EventAPI\EventAPI.csproj", "{C9E821BF-23AD-4CB5-B7F9-B3B99B606650}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{26E8B310-269E-46D4-A612-24601F16065F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Debug|x64.ActiveCfg = Debug|Any CPU + {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Debug|x64.Build.0 = Debug|Any CPU + {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Debug|x86.ActiveCfg = Debug|Any CPU + {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Debug|x86.Build.0 = Debug|Any CPU {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release|Any CPU.ActiveCfg = Release|Any CPU {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release|Any CPU.Build.0 = Release|Any CPU - {99E36EBD-1FA1-494C-8A66-BECE64EFF378}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {99E36EBD-1FA1-494C-8A66-BECE64EFF378}.Debug|Any CPU.Build.0 = Debug|Any CPU - {99E36EBD-1FA1-494C-8A66-BECE64EFF378}.Release|Any CPU.ActiveCfg = Release|Any CPU - {99E36EBD-1FA1-494C-8A66-BECE64EFF378}.Release|Any CPU.Build.0 = Release|Any CPU + {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release|x64.ActiveCfg = Release|Any CPU + {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release|x64.Build.0 = Release|Any CPU + {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release|x86.ActiveCfg = Release|Any CPU + {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release|x86.Build.0 = Release|Any CPU {4785AB75-66F3-4391-985D-63A5A049A0FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4785AB75-66F3-4391-985D-63A5A049A0FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4785AB75-66F3-4391-985D-63A5A049A0FA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {4785AB75-66F3-4391-985D-63A5A049A0FA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {4785AB75-66F3-4391-985D-63A5A049A0FA}.Debug|x64.ActiveCfg = Debug|Any CPU + {4785AB75-66F3-4391-985D-63A5A049A0FA}.Debug|x64.Build.0 = Debug|Any CPU + {4785AB75-66F3-4391-985D-63A5A049A0FA}.Debug|x86.ActiveCfg = Debug|Any CPU + {4785AB75-66F3-4391-985D-63A5A049A0FA}.Debug|x86.Build.0 = Debug|Any CPU {4785AB75-66F3-4391-985D-63A5A049A0FA}.Release|Any CPU.ActiveCfg = Release|Any CPU {4785AB75-66F3-4391-985D-63A5A049A0FA}.Release|Any CPU.Build.0 = Release|Any CPU + {4785AB75-66F3-4391-985D-63A5A049A0FA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {4785AB75-66F3-4391-985D-63A5A049A0FA}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {4785AB75-66F3-4391-985D-63A5A049A0FA}.Release|x64.ActiveCfg = Release|Any CPU + {4785AB75-66F3-4391-985D-63A5A049A0FA}.Release|x64.Build.0 = Release|Any CPU + {4785AB75-66F3-4391-985D-63A5A049A0FA}.Release|x86.ActiveCfg = Release|Any CPU + {4785AB75-66F3-4391-985D-63A5A049A0FA}.Release|x86.Build.0 = Release|Any CPU {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Debug|x64.ActiveCfg = Debug|Any CPU + {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Debug|x64.Build.0 = Debug|Any CPU + {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Debug|x86.ActiveCfg = Debug|Any CPU + {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Debug|x86.Build.0 = Debug|Any CPU {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Release|Any CPU.ActiveCfg = Release|Any CPU {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Release|Any CPU.Build.0 = Release|Any CPU + {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Release|x64.ActiveCfg = Release|Any CPU + {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Release|x64.Build.0 = Release|Any CPU + {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Release|x86.ActiveCfg = Release|Any CPU + {D51EECEB-438A-47DA-870F-7D7B41BC24D6}.Release|x86.Build.0 = Release|Any CPU {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Debug|x64.ActiveCfg = Debug|Any CPU + {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Debug|x64.Build.0 = Debug|Any CPU + {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Debug|x86.ActiveCfg = Debug|Any CPU + {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Debug|x86.Build.0 = Debug|Any CPU {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Release|Any CPU.ActiveCfg = Release|Any CPU {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Release|Any CPU.Build.0 = Release|Any CPU + {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Release|x64.ActiveCfg = Release|Any CPU + {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Release|x64.Build.0 = Release|Any CPU + {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Release|x86.ActiveCfg = Release|Any CPU + {AF097E6B-48D5-4452-9CCF-0A81A21F341D}.Release|x86.Build.0 = Release|Any CPU + {2321A25F-7871-47C3-8440-02551918D6A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2321A25F-7871-47C3-8440-02551918D6A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2321A25F-7871-47C3-8440-02551918D6A1}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2321A25F-7871-47C3-8440-02551918D6A1}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {2321A25F-7871-47C3-8440-02551918D6A1}.Debug|x64.ActiveCfg = Debug|Any CPU + {2321A25F-7871-47C3-8440-02551918D6A1}.Debug|x64.Build.0 = Debug|Any CPU + {2321A25F-7871-47C3-8440-02551918D6A1}.Debug|x86.ActiveCfg = Debug|Any CPU + {2321A25F-7871-47C3-8440-02551918D6A1}.Debug|x86.Build.0 = Debug|Any CPU + {2321A25F-7871-47C3-8440-02551918D6A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2321A25F-7871-47C3-8440-02551918D6A1}.Release|Any CPU.Build.0 = Release|Any CPU + {2321A25F-7871-47C3-8440-02551918D6A1}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {2321A25F-7871-47C3-8440-02551918D6A1}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {2321A25F-7871-47C3-8440-02551918D6A1}.Release|x64.ActiveCfg = Release|Any CPU + {2321A25F-7871-47C3-8440-02551918D6A1}.Release|x64.Build.0 = Release|Any CPU + {2321A25F-7871-47C3-8440-02551918D6A1}.Release|x86.ActiveCfg = Release|Any CPU + {2321A25F-7871-47C3-8440-02551918D6A1}.Release|x86.Build.0 = Release|Any CPU + {428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Debug|x64.ActiveCfg = Debug|Any CPU + {428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Debug|x64.Build.0 = Debug|Any CPU + {428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Debug|x86.ActiveCfg = Debug|Any CPU + {428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Debug|x86.Build.0 = Debug|Any CPU + {428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release|Any CPU.Build.0 = Release|Any CPU + {428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release|x64.ActiveCfg = Release|Any CPU + {428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release|x64.Build.0 = Release|Any CPU + {428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release|x86.ActiveCfg = Release|Any CPU + {428D8EB9-ECA3-4A66-AA59-3A944378C33F}.Release|x86.Build.0 = Release|Any CPU + {E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Debug|x64.ActiveCfg = Debug|Any CPU + {E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Debug|x86.ActiveCfg = Debug|Any CPU + {E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Release|Any CPU.Build.0 = Release|Any CPU + {E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Release|x64.ActiveCfg = Release|Any CPU + {E46C85BD-A99C-484E-BCCE-0F1831C5925E}.Release|x86.ActiveCfg = Release|Any CPU + {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Debug|x64.ActiveCfg = Debug|Any CPU + {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Debug|x64.Build.0 = Debug|Any CPU + {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Debug|x86.ActiveCfg = Debug|Any CPU + {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Debug|x86.Build.0 = Debug|Any CPU + {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Release|Any CPU.Build.0 = Release|Any CPU + {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Release|x64.ActiveCfg = Release|Any CPU + {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Release|x64.Build.0 = Release|Any CPU + {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Release|x86.ActiveCfg = Release|Any CPU + {C9E821BF-23AD-4CB5-B7F9-B3B99B606650}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {4785AB75-66F3-4391-985D-63A5A049A0FA} = {26E8B310-269E-46D4-A612-24601F16065F} + {AF097E6B-48D5-4452-9CCF-0A81A21F341D} = {26E8B310-269E-46D4-A612-24601F16065F} + {2321A25F-7871-47C3-8440-02551918D6A1} = {26E8B310-269E-46D4-A612-24601F16065F} + {428D8EB9-ECA3-4A66-AA59-3A944378C33F} = {26E8B310-269E-46D4-A612-24601F16065F} + {E46C85BD-A99C-484E-BCCE-0F1831C5925E} = {26E8B310-269E-46D4-A612-24601F16065F} + {C9E821BF-23AD-4CB5-B7F9-B3B99B606650} = {26E8B310-269E-46D4-A612-24601F16065F} + EndGlobalSection EndGlobal diff --git a/MessageboardPlugin/Encryption.cs b/MessageboardPlugin/Encryption.cs new file mode 100644 index 000000000..6453979d1 --- /dev/null +++ b/MessageboardPlugin/Encryption.cs @@ -0,0 +1,43 @@ +using System; +using System.Security.Cryptography; +using System.Text; + +//http://codereview.stackexchange.com/questions/96494/user-password-encryption-in-c + SCrypt +namespace MessageBoard.Encryption +{ + + public static class PasswordHasher + { + public static byte[] ComputeHash(string password, byte[] salt) + { + byte[] pwBytes = Encoding.UTF8.GetBytes(password); + byte[] hashBytes = new byte[64]; + CryptSharp.Utility.SCrypt.ComputeKey(pwBytes, salt, 16384, 8, 1, null, hashBytes); + return hashBytes; + } + + public static byte[] GenerateSalt(int saltByteSize = 24) + { + RNGCryptoServiceProvider saltGenerator = new RNGCryptoServiceProvider(); + byte[] salt = new byte[saltByteSize]; + saltGenerator.GetBytes(salt); + return salt; + } + + public static bool VerifyPassword(String password, byte[] passwordSalt, byte[] passwordHash) + { + byte[] computedHash = ComputeHash(password, passwordSalt); + return AreHashesEqual(computedHash, passwordHash); + } + + //Length constant verification - prevents timing attack + private static bool AreHashesEqual(byte[] firstHash, byte[] secondHash) + { + int minHashLength = firstHash.Length <= secondHash.Length ? firstHash.Length : secondHash.Length; + var xor = firstHash.Length ^ secondHash.Length; + for (int i = 0; i < minHashLength; i++) + xor |= firstHash[i] ^ secondHash[i]; + return 0 == xor; + } + } +} \ No newline at end of file diff --git a/MessageboardPlugin/Events.cs b/MessageboardPlugin/Events.cs new file mode 100644 index 000000000..26f74b530 --- /dev/null +++ b/MessageboardPlugin/Events.cs @@ -0,0 +1,9 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MessageBoard.Events +{ + public delegate void ActionEventHandler(User origin, EventArgs e); +} diff --git a/MessageboardPlugin/Exceptions.cs b/MessageboardPlugin/Exceptions.cs new file mode 100644 index 000000000..33bd19284 --- /dev/null +++ b/MessageboardPlugin/Exceptions.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MessageBoard.Exceptions +{ + public class ThreadException : Exception + { + public ThreadException(string msg) : base(msg) { } + } + + public class UserException : Exception + { + public UserException(string msg) : base(msg) { } + } + + public class SessionException : Exception + { + public SessionException(string msg) : base(msg) { } + } + + public class CategoryException : Exception + { + public CategoryException(string msg) : base(msg) { } + } + + public class PermissionException: Exception + { + public PermissionException(string msg) : base(msg) { } + } +} + \ No newline at end of file diff --git a/MessageboardPlugin/Forum.cs b/MessageboardPlugin/Forum.cs new file mode 100644 index 000000000..9442bb444 --- /dev/null +++ b/MessageboardPlugin/Forum.cs @@ -0,0 +1,1160 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using SharedLibrary; +using System.Collections.Specialized; + + +namespace MessageBoard.Forum +{ + public class Manager + { + private List forumPages; + private List activeSessions; + private Storage.Database database; + + private const int MAX_SESSIONS = 64; + public const int TITLE_MAXLENGTH = 30; + public const int CONTENT_MAXLENGTH = 8192; + public const int USERNAME_MAXLENGTH = 16; + public const int PASSWORD_MAXLENGTH = 64; + + public Rank guestRank; + public Rank UserRank; + public Rank ModRank; + public Rank AdminRank; + + public enum ErrorCode + { + NO_ERROR, + GLOBAL_PERMISSIONDENIED, + USER_DUPLICATE, + USER_INVALID, + USER_BADCREDENTIALS, + USER_EMPTYCREDENTIALS, + USER_NOTAUTHORIZED, + USER_PASSWORDTOOLONG, + USER_USERNAMETOOLONG, + SESSION_INVALID, + THREAD_BADDATA, + THREAD_EMPTYDATA, + THREAD_CONTENTTOOLONG, + THREAD_TITLETOOLONG, + THREAD_INVALID, + REPLY_SAVEFAILED, + CATEGORY_INVALID, + CATEGORY_EMPTY + } + + public Manager() + { + forumPages = new List(); + activeSessions = new List(); + database = new Storage.Database("forum.db"); + } + + public void startSession(string sessionID) + { + try + { + Session newSession = getSession(sessionID); + newSession.sessionStartTime = DateTime.Now; + Console.WriteLine("Matching session was found - {0}", sessionID); + addSession(newSession); + } + + catch (Exceptions.SessionException) + { + Console.WriteLine("No session was found so we are adding a new one"); + Session newSession = new Session(new User(), sessionID); + addSession(newSession); + } + } + + public Session getSession(string sessionID) + { + Session requestedSession = activeSessions.Find(sess => sess.sessionID == sessionID); + + if (requestedSession == null) + requestedSession = database.getSession(sessionID); + + if (requestedSession == null) + throw new Exceptions.SessionException("Session not found"); + + return requestedSession; + } + + public User getUser(int userID) + { + User requestedUser = database.getUser(userID); + + if (requestedUser == null) + throw new Exceptions.UserException("User not found"); + + return requestedUser; + } + + public ForumThread getThread(int threadID) + { + ForumThread requestedThread = database.getThread(threadID); + + if (requestedThread == null) + throw new Exceptions.ThreadException("Thread not found"); + + return requestedThread; + } + + public Post getPost(int postID) + { + Post requestedPost = database.getReply(postID); + + if (requestedPost == null) + throw new Exceptions.ThreadException("Post not found"); + + return requestedPost; + } + + public List getReplies(int threadID) + { + return database.getRepliesFromThreadID(threadID); + } + + public Post getReply(int replyID) + { + Post reply = database.getReply(replyID); + + if (reply == null) + throw new Exceptions.ThreadException("Reply not found"); + + return reply; + } + + public ErrorCode addPost(ForumThread parentThread, Post newPost) + { + int addedPost = database.addReply(newPost); + if (addedPost > 0) + { + parentThread.replies++; + parentThread.updatedDate = DateTime.Now; + database.updateThread(parentThread); + database.updateUser(newPost.author); + return ErrorCode.NO_ERROR; + } + + return ErrorCode.REPLY_SAVEFAILED; + } + + private ErrorCode addSession(Session sess) + { + if (activeSessions.Count >= MAX_SESSIONS) + activeSessions.RemoveAt(0); + + //activeSessions.RemoveAll(x => (x.sessionID == sess.sessionID && sess.sessionUser.ranking.equivalentRank > x.sessionUser.ranking.equivalentRank)); + + //Console.WriteLine(String.Format("Adding new session [{0}] [{1}]", sess.sessionID, sess.sessionUser.username)); + + if (activeSessions.Find(x => x.sessionID == sess.sessionID) == null) + activeSessions.Add(sess); + + // if it's a guest session, we don't want to save them in the database... + if (sess.sessionUser.ranking.equivalentRank > Player.Permission.User) + { + database.setSession(sess.sessionUser.id, sess.sessionID); + sess.sessionUser.lastLogin = DateTime.Now; + database.updateUser(sess.sessionUser); + } + + return ErrorCode.NO_ERROR; + } + + public void removeSession(string sessID) + { + activeSessions.RemoveAll(x => x.sessionID == sessID); + } + + public ErrorCode addUser(User newUser, Session userSession) + { + if (database.userExists(newUser.username, newUser.email)) + return ErrorCode.USER_DUPLICATE; + + // first added user is going to be admin + if (database.getNumUsers() == 0) + newUser.ranking = AdminRank; + + User createdUser = database.addUser(newUser, userSession); + return addSession(new Session(createdUser, userSession.sessionID)); + } + + public void updateUser(User updatedUser) + { + database.updateUser(updatedUser); + } + + public ErrorCode updateThread(ForumThread newThread) + { + if (database.updateThread(newThread)) + return ErrorCode.NO_ERROR; + + else + return ErrorCode.THREAD_INVALID; + } + + public ErrorCode updateReply(Post updatedReply) + { + if (database.updateReply(updatedReply)) + return ErrorCode.NO_ERROR; + else + return ErrorCode.THREAD_INVALID; + } + + public ErrorCode addThread(ForumThread newThread) + { + if (database.addThread(newThread) > 0) + return ErrorCode.NO_ERROR; + else + return ErrorCode.THREAD_INVALID; + } + + public ErrorCode authorizeUser(string username, string password, string sessionID) + { + User toAuth = database.getUser(username); + + if (toAuth == null) + return ErrorCode.USER_BADCREDENTIALS; + + bool validCredentials = Encryption.PasswordHasher.VerifyPassword(password, Convert.FromBase64String(toAuth.getPasswordSalt()), Convert.FromBase64String(toAuth.getPasswordHash())); + + if (!validCredentials) + return ErrorCode.USER_BADCREDENTIALS; + + addSession(new Session(toAuth, sessionID)); + return ErrorCode.NO_ERROR; + } + + public List getAllCategories() + { + return database.getAllCategories(); + } + + public List getRecentThreads(int catID) + { + return database.getRecentThreads(catID); + } + + public List getCategoryThreads(int categoryID) + { + return database.getCategoryThreads(categoryID); + } + + public Category getCategory(int id) + { + Category cat = database.getCategory(id); + + if (cat == null) + throw new Exceptions.CategoryException("Category not found"); + + return cat; + } + + public List getSessions() + { + return activeSessions; + } + + public void Start() + { + var login = new Pages.Login(); + var loginJSON = new Pages.LoginJSON(); + var register = new Pages.Register(); + var registerJSON = new Pages.RegisterJSON(); + var userinfoJSON = new Pages.userinfoJSON(); + var viewUser = new Pages.ViewUser(); + var categoriesJSON = new Pages.categoriesJSON(); + var category = new Pages.ViewCategory(); + var categorythreadsJSON = new Pages.categorythreadsJSON(); + var home = new Pages.Home(); + var recentthreadsJSON = new Pages.recentthreadsJSON(); + var postthread = new Pages.PostThread(); + var postthreadJSON = new Pages.postthreadJSON(); + var editthreadJSON = new Pages.editthreadJSON(); + var threadJSON = new Pages.threadJSON(); + var viewthread = new Pages.ViewThread(); + var logout = new Pages.LogOut(); + var stats = new Pages.StatsJSON(); + + forumPages.Add(login); + forumPages.Add(loginJSON); + forumPages.Add(register); + forumPages.Add(registerJSON); + forumPages.Add(userinfoJSON); + forumPages.Add(viewUser); + forumPages.Add(categoriesJSON); + forumPages.Add(category); + forumPages.Add(categorythreadsJSON); + forumPages.Add(home); + forumPages.Add(recentthreadsJSON); + forumPages.Add(postthread); + forumPages.Add(postthreadJSON); + forumPages.Add(editthreadJSON); + forumPages.Add(threadJSON); + forumPages.Add(viewthread); + forumPages.Add(logout); + forumPages.Add(stats); + + SharedLibrary.WebService.pageList.Add(login); + SharedLibrary.WebService.pageList.Add(loginJSON); + SharedLibrary.WebService.pageList.Add(register); + SharedLibrary.WebService.pageList.Add(registerJSON); + SharedLibrary.WebService.pageList.Add(userinfoJSON); + SharedLibrary.WebService.pageList.Add(viewUser); + SharedLibrary.WebService.pageList.Add(categoriesJSON); + SharedLibrary.WebService.pageList.Add(category); + SharedLibrary.WebService.pageList.Add(categorythreadsJSON); + SharedLibrary.WebService.pageList.Add(home); + SharedLibrary.WebService.pageList.Add(recentthreadsJSON); + SharedLibrary.WebService.pageList.Add(postthread); + SharedLibrary.WebService.pageList.Add(postthreadJSON); + SharedLibrary.WebService.pageList.Add(editthreadJSON); + SharedLibrary.WebService.pageList.Add(threadJSON); + SharedLibrary.WebService.pageList.Add(viewthread); + SharedLibrary.WebService.pageList.Add(logout); + SharedLibrary.WebService.pageList.Add(stats); + + guestRank = database.getRank("Guest"); + UserRank = database.getRank("User"); + ModRank = database.getRank("Moderator"); + AdminRank = database.getRank("Administrator"); + } + + public void Stop() + { + //session logouts + //checkme + foreach (var page in forumPages) + SharedLibrary.WebService.pageList.Remove(page); + } + } + + + public class Pages + { + public abstract class JSONPage : IPage + { + protected Session currentSession; + + public bool isVisible() + { + return false; + } + + public virtual string getPath() + { + return "/forum"; + } + + public string getName() + { + return "JSONPage"; + } + + public virtual HttpResponse getPage(System.Collections.Specialized.NameValueCollection querySet, IDictionary requestHeaders) + { + HttpResponse resp = new HttpResponse(); + resp.contentType = "application/json"; + resp.additionalHeaders = new Dictionary(); + + if (requestHeaders.ContainsKey("Cookie")) + { + Console.WriteLine("JSON request contains session header - " + requestHeaders["Cookie"]); + string cookie = requestHeaders["Cookie"].Split('=')[1]; + Plugin.Main.forum.startSession(cookie); + currentSession = Plugin.Main.forum.getSession(cookie); + } + + else + { + string sessionID = Convert.ToBase64String(Encryption.PasswordHasher.GenerateSalt()); + resp.additionalHeaders.Add("Set-Cookie", "IW4MAdmin_ForumSession=" + sessionID + "; path=/; expires=Sat, 01 May 2025 12:00:00 GMT"); + currentSession = new Session(new User(), sessionID); + Plugin.Main.forum.startSession(sessionID); + currentSession = Plugin.Main.forum.getSession(sessionID); + } + + return resp; + } + } + + abstract public class ForumPage : HTMLPage + { + public ForumPage(bool visible) : base(visible) { } + public abstract override string getName(); + public override string getPath() + { + return base.getPath() + "/forum"; + } + public override Dictionary getHeaders(IDictionary requestHeaders) + { + return base.getHeaders(requestHeaders); + } + + protected string templatation(string bodyContent) + { + StringBuilder S = new StringBuilder(); + S.Append(base.loadHeader()); + S.Append(bodyContent); + S.Append(base.loadFooter()); + + return S.ToString(); + } + } + + public class Login : ForumPage + { + public Login() : base(true) + { + + } + + public override string getName() + { + return "Forum"; + } + + public override string getPath() + { + return base.getPath() + "/login"; + } + + public override string getContent(NameValueCollection querySet, IDictionary headers) + { + return templatation(loadFile("forum\\login.html")); + } + } + + public class Register : ForumPage + { + public Register(): base(false) + { + + } + + public override string getName() + { + return "Register"; + } + + public override string getPath() + { + return base.getPath() + "/register"; + } + + public override string getContent(NameValueCollection querySet, IDictionary headers) + { + string content = loadFile("forum\\register.html"); + return templatation(content); + } + } + + public class Home : ForumPage + { + public Home() : base(false) + { + + } + + public override string getName() + { + return "Forum - Home"; + } + + public override string getPath() + { + return base.getPath() + "/home"; + } + + public override string getContent(NameValueCollection querySet, IDictionary headers) + { + string content = loadFile("forum\\home.html"); + return templatation(content); + } + } + + public class PostThread : ForumPage + { + public PostThread() : base(false) + { + + } + + public override string getName() + { + return "Forum - Post New Thread"; + } + + public override string getPath() + { + return base.getPath() + "/postthread"; + } + + public override string getContent(NameValueCollection querySet, IDictionary headers) + { + string content = loadFile("forum\\postthread.html"); + return templatation(content); + } + } + + public class ViewCategory : ForumPage + { + public ViewCategory() : base(false) + { + + } + + public override string getName() + { + return "Forum - Category View"; + } + + public override string getPath() + { + return base.getPath() + "/category"; + } + + public override string getContent(NameValueCollection querySet, IDictionary headers) + { + string content = loadFile("forum\\category.html"); + return templatation(content); + } + } + + public class ViewUser : ForumPage + { + public ViewUser() : base(false) + { + + } + + public override string getName() + { + return "Forum - View User"; + } + + public override string getPath() + { + return base.getPath() + "/user"; + } + + public override string getContent(NameValueCollection querySet, IDictionary headers) + { + string content = loadFile("forum\\user.html"); + return templatation(content); + } + } + + public class ViewThread : ForumPage + { + public ViewThread() : base(false) + { + + } + + public override string getName() + { + return "Forum - View Thread"; + } + + public override string getPath() + { + return base.getPath() + "/thread"; + } + + public override string getContent(NameValueCollection querySet, IDictionary headers) + { + string content = loadFile("forum\\thread.html"); + return templatation(content); + } + } + + public class LogOut : ForumPage + { + public LogOut() : base(false) + { + + } + + public override string getName() + { + return "Forum - Log Out"; + } + + public override string getPath() + { + return base.getPath() + "/logout"; + } + + public override Dictionary getHeaders(IDictionary requestHeaders) + { + Plugin.Main.forum.removeSession(requestHeaders["Cookie"].Split('=')[1]); + return new Dictionary() { { "Set-Cookie", "IW4MAdmin_ForumSession=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT" } }; + } + + public override string getContent(NameValueCollection querySet, IDictionary headers) + { + string content = @""; + return templatation(content); + } + } + + public class RegisterJSON : JSONPage + { + public override string getPath() + { + return base.getPath() + "/_register"; + } + + public override HttpResponse getPage(NameValueCollection querySet, IDictionary requestHeaders) + { + var resp = base.getPage(querySet, requestHeaders); + + var result = new ActionResponse(); + result.success = false; + result.destination = base.getPath() + "/error"; + + try { + byte[] passwordSalt = Encryption.PasswordHasher.GenerateSalt(); + string b64PasswordHash = Convert.ToBase64String(Encryption.PasswordHasher.ComputeHash(querySet["password"], passwordSalt)); + + User registeringUser = new User(querySet["username"], querySet["hiddenUsername"], querySet["email"], b64PasswordHash, Convert.ToBase64String(passwordSalt), Plugin.Main.forum.UserRank); + + currentSession = new Session(registeringUser, currentSession.sessionID); + var addUserResult = Plugin.Main.forum.addUser(registeringUser, currentSession); + + if (addUserResult != Manager.ErrorCode.NO_ERROR) + { + result.errorCode = addUserResult; + } + + else + { + result.destination = base.getPath() + "/home"; + result.success = true; + result.errorCode = Manager.ErrorCode.NO_ERROR; + } + } + + catch (Exception E) { + result.errorCode = Manager.ErrorCode.USER_INVALID; + } + + resp.content = Newtonsoft.Json.JsonConvert.SerializeObject(result); + return resp; + } + } + + public class userinfoJSON : JSONPage + { + public override string getPath() + { + return base.getPath() + "/_userinfo"; + } + + public override HttpResponse getPage(NameValueCollection querySet, IDictionary requestHeaders) + { + var resp = base.getPage(querySet, requestHeaders); + + UserInfo info = new UserInfo(); + bool validUserSelection = true; + User requestedUser = null; + + try + { + requestedUser = Plugin.Main.forum.getUser(Convert.ToInt32(querySet["id"])); + } + + catch (FormatException) + { + // logme + validUserSelection = false; + } + + catch (Exceptions.UserException) + { + //logme + validUserSelection = false; + } + + if (validUserSelection) + { + resp.content = Newtonsoft.Json.JsonConvert.SerializeObject(requestedUser); + } + + else + { + if (querySet.Get("setavatarurl") != null) + { + if (currentSession.sessionUser.ranking.name != "Guest") + { + currentSession.sessionUser.avatarURL = querySet["setavatarurl"]; + Plugin.Main.forum.updateUser(currentSession.sessionUser); + resp.content = "OK!"; + return resp; + } + } + + else + { + info.email = currentSession.sessionUser.email; + info.username = currentSession.sessionUser.username; + info.rank = currentSession.sessionUser.ranking; + + // this should not be a thing but ok... + Player matchedPlayer = Plugin.Main.stupidServer.clientDB.getPlayer(querySet["ip"]); + + if (matchedPlayer != null) + info.matchedUsername = matchedPlayer.Name; + + resp.content = Newtonsoft.Json.JsonConvert.SerializeObject(info); + } + } + + return resp; + } + } + + public class LoginJSON : JSONPage + { + public override string getPath() + { + return base.getPath() + "/_login"; + } + + public override HttpResponse getPage(NameValueCollection querySet, IDictionary requestHeaders) + { + var resp = base.getPage(querySet, requestHeaders); + ActionResponse aResp = new ActionResponse(); + aResp.success = false; + + try + { + var result = Plugin.Main.forum.authorizeUser(querySet["username"], querySet["password"], currentSession.sessionID); + aResp.success = result == Manager.ErrorCode.NO_ERROR; + aResp.errorCode = result; + aResp.destination = "home"; + } + + catch (KeyNotFoundException) + { + aResp.errorCode = Manager.ErrorCode.USER_EMPTYCREDENTIALS; + } + + resp.content = Newtonsoft.Json.JsonConvert.SerializeObject(aResp); + return resp; + } + } + + public class categoriesJSON : JSONPage + { + public override string getPath() + { + return base.getPath() + "/_categories"; + } + + public override HttpResponse getPage(NameValueCollection querySet, IDictionary requestHeaders) + { + var resp = base.getPage(querySet, requestHeaders); + var categories = Plugin.Main.forum.getAllCategories(); + + + resp.content = Newtonsoft.Json.JsonConvert.SerializeObject(categories); + return resp; + } + } + + public class recentthreadsJSON : JSONPage + { + public override string getPath() + { + return base.getPath() + "/_recentthreads"; + } + + public override HttpResponse getPage(NameValueCollection querySet, IDictionary requestHeaders) + { + var resp = base.getPage(querySet, requestHeaders); + + try + { + List threads = new List(); + var categories = Plugin.Main.forum.getAllCategories(); + + foreach (var t in categories) + { + if ((t.permissions.Find(x => x.rankID == currentSession.sessionUser.ranking.id).actionable & Permission.Action.READ) != Permission.Action.READ) + continue; + + HomeThread thread = new HomeThread(); + thread.categoryTitle = t.title; + thread.categoryDescription = t.description; + thread.categoryID = t.id; + thread.recentThreads = Plugin.Main.forum.getRecentThreads(t.id); + + threads.Add(thread); + } + + resp.content = Newtonsoft.Json.JsonConvert.SerializeObject(threads); + } + catch (Exception e) + { + //logme + resp.content = ""; + } + + return resp; + } + } + + public class categorythreadsJSON : JSONPage + { + public override string getPath() + { + return base.getPath() + "/_categorythreads"; + } + + public override HttpResponse getPage(NameValueCollection querySet, IDictionary requestHeaders) + { + var resp = base.getPage(querySet, requestHeaders); + var aResp = new ActionResponse(); + + try + { + var category = Plugin.Main.forum.getCategory(Convert.ToInt32(querySet["id"])); + + if ((category.permissions.Find(x => x.rankID == currentSession.sessionUser.ranking.id).actionable & Permission.Action.READ) != Permission.Action.READ) + throw new Exceptions.PermissionException("User cannot view this category"); + + var categoryThreads = Plugin.Main.forum.getCategoryThreads(category.id); + + resp.content = Newtonsoft.Json.JsonConvert.SerializeObject(categoryThreads); + return resp; + } + + catch (FormatException) + { + //logme + aResp.errorCode = Manager.ErrorCode.CATEGORY_INVALID; + } + + catch (Exceptions.CategoryException) + { + //logme + aResp.errorCode = Manager.ErrorCode.CATEGORY_INVALID; + } + + catch (Exceptions.PermissionException) + { + aResp.errorCode = Manager.ErrorCode.GLOBAL_PERMISSIONDENIED; + } + + resp.content = Newtonsoft.Json.JsonConvert.SerializeObject(aResp); + return resp; + } + } + + public class threadJSON : JSONPage + { + public override string getPath() + { + return base.getPath() + "/_thread"; + } + + public override HttpResponse getPage(NameValueCollection querySet, IDictionary requestHeaders) + { + var resp = base.getPage(querySet, requestHeaders); + var aResp = new ActionResponse(); + aResp.success = false; + aResp.errorCode = Manager.ErrorCode.NO_ERROR; + + try + { + if (querySet.Get("id") != null) + { + var thread = Plugin.Main.forum.getThread(Convert.ToInt32(querySet["id"])); + + if ((thread.threadCategory.permissions.Find(x => x.rankID == currentSession.sessionUser.ranking.id).actionable & Permission.Action.READ) != Permission.Action.READ) + throw new Exceptions.PermissionException("You cannot view this post"); + + var replies = Plugin.Main.forum.getReplies(thread.id); + + resp.content = Newtonsoft.Json.JsonConvert.SerializeObject(new ThreadView(thread, replies)); + aResp.success = true; + } + } + + catch (FormatException) + { + aResp.errorCode = Manager.ErrorCode.THREAD_INVALID; + } + + catch (Exceptions.ThreadException) + { + aResp.errorCode = Manager.ErrorCode.THREAD_INVALID; + } + + catch (Exceptions.PermissionException) + { + aResp.errorCode = Manager.ErrorCode.GLOBAL_PERMISSIONDENIED; + } + + if (aResp.success == false) + resp.content = Newtonsoft.Json.JsonConvert.SerializeObject(aResp); + + return resp; + } + } + + public class editthreadJSON : JSONPage + { + public override string getPath() + { + return base.getPath() + "/_editthread"; + } + + public override HttpResponse getPage(NameValueCollection querySet, IDictionary requestHeaders) + { + var resp = base.getPage(querySet, requestHeaders); + var aResp = new ActionResponse(); + aResp.success = false; + aResp.errorCode = Manager.ErrorCode.NO_ERROR; + + try + { + if (querySet.Get("id") != null) + { + var thread = Plugin.Main.forum.getThread(Convert.ToInt32(querySet["id"])); + + if (thread.author.id != currentSession.sessionUser.id && (thread.threadCategory.permissions.Find(x => x.rankID == currentSession.sessionUser.ranking.id).actionable & Permission.Action.MODIFY) != Permission.Action.MODIFY) + throw new Exceptions.PermissionException("User cannot modify this post"); + + if (querySet.Get("delete") != null) + { + thread.visible = false; + aResp.errorCode = Plugin.Main.forum.updateThread(thread); + aResp.success = aResp.errorCode == Manager.ErrorCode.NO_ERROR; + aResp.destination = "category?id=" + thread.threadCategory.id; + } + + else if (querySet.Get("update") != null) + { + if (querySet.Get("content") == null || querySet.Get("title") == null) + throw new Exceptions.ThreadException("Invalid update data"); + + if (querySet.Get("content").Length > Manager.CONTENT_MAXLENGTH) + { + aResp.errorCode = Manager.ErrorCode.THREAD_CONTENTTOOLONG; + } + + else if (querySet.Get("title").Length > Manager.TITLE_MAXLENGTH) + { + aResp.errorCode = Manager.ErrorCode.THREAD_TITLETOOLONG; + } + + else + { + //fixsecurity + var markdownParser = new MarkdownDeep.Markdown(); + string markdownContent = markdownParser.Transform(querySet["content"]); + markdownContent = Uri.EscapeDataString(markdownContent); + string title = Uri.EscapeDataString(querySet["title"]); + + if (thread.updateTitle(title) && thread.updateContent(markdownContent)) + { + aResp.errorCode = Plugin.Main.forum.updateThread(thread); + aResp.success = aResp.errorCode == Manager.ErrorCode.NO_ERROR; + } + else + aResp.errorCode = Manager.ErrorCode.THREAD_EMPTYDATA; + } + } + } + + else if (querySet.Get("replyid") != null) + { + var reply = Plugin.Main.forum.getReply(Convert.ToInt32(querySet["replyid"])); + + if (currentSession.sessionUser.id == 0 || reply.author.id != currentSession.sessionUser.id && (reply.threadCategory.permissions.Find(x => x.rankID == currentSession.sessionUser.ranking.id).actionable & Permission.Action.MODIFY) != Permission.Action.MODIFY) + throw new Exceptions.PermissionException("User cannot modify this reply"); + + if (querySet.Get("delete") != null) + { + reply.visible = false; + aResp.errorCode = Plugin.Main.forum.updateReply(reply); + aResp.success = aResp.errorCode == Manager.ErrorCode.NO_ERROR; + aResp.destination = "thread?id=" + reply.threadid; + } + + } + + resp.content = Newtonsoft.Json.JsonConvert.SerializeObject(aResp); + } + + catch (FormatException) + { + aResp.errorCode = Manager.ErrorCode.THREAD_INVALID; + } + + catch (Exceptions.ThreadException) + { + aResp.errorCode = Manager.ErrorCode.THREAD_INVALID; + } + + catch (Exceptions.PermissionException) + { + aResp.errorCode = Manager.ErrorCode.GLOBAL_PERMISSIONDENIED; + } + + if (aResp.success == false) + resp.content = Newtonsoft.Json.JsonConvert.SerializeObject(aResp); + + return resp; + } + } + + public class postthreadJSON : JSONPage + { + public override string getPath() + { + return base.getPath() + "/_postthread"; + } + + public override HttpResponse getPage(NameValueCollection querySet, IDictionary requestHeaders) + { + var resp = base.getPage(querySet, requestHeaders); + ActionResponse aResp = new ActionResponse(); + + if (currentSession.sessionUser.ranking.equivalentRank < Player.Permission.Trusted) + { + aResp.errorCode = Manager.ErrorCode.USER_NOTAUTHORIZED; + } + + else + { + try + { + if (querySet["content"].Length < Manager.CONTENT_MAXLENGTH && querySet["title"].Length <= Manager.TITLE_MAXLENGTH) + { + + var markdownParser = new MarkdownDeep.Markdown(); + string markdownContent = markdownParser.Transform(querySet["content"]); + markdownContent = Uri.EscapeDataString(markdownContent); + string title = Uri.EscapeDataString(querySet["title"]); + + if (querySet.Get("threadid") != null) + { + var replyThread = Plugin.Main.forum.getThread(Convert.ToInt32(querySet.Get("threadid"))); + var reply = new Post(title, replyThread.getID(), markdownContent, currentSession.sessionUser); + + aResp.errorCode = Plugin.Main.forum.addPost(replyThread, reply); + aResp.destination = String.Format("thread?id={0}", replyThread.id); + aResp.success = aResp.errorCode == Manager.ErrorCode.NO_ERROR; + } + + else + { + Category threadCategory = Plugin.Main.forum.getCategory(Convert.ToInt32(querySet["category"])); + + if ((threadCategory.permissions.Find(x => x.rankID == currentSession.sessionUser.ranking.id).actionable & Permission.Action.WRITE) == Permission.Action.WRITE) + { + ForumThread newThread = new ForumThread(title, markdownContent, currentSession.sessionUser, threadCategory); + + aResp.errorCode = Plugin.Main.forum.addThread(newThread); + aResp.destination = String.Format("category?id={0}", threadCategory.id); + aResp.success = aResp.errorCode == Manager.ErrorCode.NO_ERROR; + } + + else + aResp.errorCode = Manager.ErrorCode.USER_NOTAUTHORIZED; + } + } + + else if (querySet["title"].Length > Manager.TITLE_MAXLENGTH) + aResp.errorCode = Manager.ErrorCode.THREAD_TITLETOOLONG; + else + aResp.errorCode = Manager.ErrorCode.THREAD_CONTENTTOOLONG; + } + + catch (Exceptions.ThreadException) + { + aResp.errorCode = Manager.ErrorCode.THREAD_BADDATA; + } + + catch (NullReferenceException) + { + //logme + aResp.errorCode = Manager.ErrorCode.THREAD_EMPTYDATA; + } + } + + resp.content = Newtonsoft.Json.JsonConvert.SerializeObject(aResp); + return resp; + } + } + + public class StatsJSON : JSONPage + { + public override string getPath() + { + return base.getPath() + "/_stats"; + } + + public override HttpResponse getPage(NameValueCollection querySet, IDictionary requestHeaders) + { + var resp = base.getPage(querySet, requestHeaders); + StatView stats = new StatView(); + + stats.onlineUsers = new List(); + + foreach (Session s in Plugin.Main.forum.getSessions()) + { + if (s.sessionUser.ranking.id > 0 && (DateTime.Now - s.sessionStartTime).TotalMinutes < 5 && s.sessionUser.username != "Guest") + stats.onlineUsers.Add(s.sessionUser); + } + + resp.content = Newtonsoft.Json.JsonConvert.SerializeObject(stats); + return resp; + } + } + + + protected struct StatView + { + public List onlineUsers; + } + + protected struct ActionResponse + { + public bool success; + public string destination; + public Manager.ErrorCode errorCode; + } + + protected struct HomeThread + { + public string categoryTitle; + public string categoryDescription; + public int categoryID; + public List recentThreads; + } + + protected struct ThreadView + { + public ForumThread Thread; + public List Replies; + + public ThreadView(ForumThread t, List r) + { + Thread = t; + Replies = r; + } + } + } +} \ No newline at end of file diff --git a/MessageboardPlugin/Identifiable.cs b/MessageboardPlugin/Identifiable.cs new file mode 100644 index 000000000..5cfb228be --- /dev/null +++ b/MessageboardPlugin/Identifiable.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MessageBoard +{ + interface Identifiable + { + int getID(); + } +} diff --git a/MessageboardPlugin/MessageboardPlugin.csproj b/MessageboardPlugin/MessageboardPlugin.csproj new file mode 100644 index 000000000..d1f080f60 --- /dev/null +++ b/MessageboardPlugin/MessageboardPlugin.csproj @@ -0,0 +1,120 @@ + + + + + Debug + AnyCPU + {E46C85BD-A99C-484E-BCCE-0F1831C5925E} + Library + Properties + MessageBoard + MessageboardPlugin + v4.5 + 512 + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + + + + {d51eeceb-438a-47da-870f-7d7b41bc24d6} + SharedLibrary + False + + + + + + + + + + + + + + + + + + ..\packages\CryptSharp.1.2.0.1\lib\net35\CryptSharp.dll + + + ..\packages\MarkdownDeep.NET.1.5\lib\.NetFramework 3.5\MarkdownDeep.dll + True + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll + False + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + mkdir "$(SolutionDir)Admin\bin\$(ConfigurationName)\forum" +copy /Y "$(TargetDir)$(TargetName).dll" "$(SolutionDir)Admin\bin\$(ConfigurationName)\plugins\MessageBoardPlugin.dll" +xcopy /E /Y "$(TargetDir)forum" "$(SolutionDir)Admin\bin\$(ConfigurationName)\forum" + +copy /Y "$(TargetDir)MarkdownDeep.dll" "$(SolutionDir)Admin\bin\$(ConfigurationName)\lib\MarkdownDeep.dll" +copy /Y "$(TargetDir)CryptSharp.dll" "$(SolutionDir)Admin\bin\$(ConfigurationName)\lib\CryptSharp.dll" +copy /Y "$(TargetDir)$(TargetName).dll" "$(SolutionDir)BUILD\plugins\" + + + \ No newline at end of file diff --git a/MessageboardPlugin/Plugin.cs b/MessageboardPlugin/Plugin.cs new file mode 100644 index 000000000..c7e9b7b55 --- /dev/null +++ b/MessageboardPlugin/Plugin.cs @@ -0,0 +1,65 @@ +using System; +using SharedLibrary; +using SharedLibrary.Extensions; +using System.Threading.Tasks; + +namespace MessageBoard.Plugin +{ + public class Main : IPlugin + { + public static Forum.Manager forum { get; private set; } + public static Server stupidServer { get; private set; } + + public string Author + { + get + { + return "RaidMax"; + } + } + + public float Version + { + get + { + return 0.1f; + } + } + + public string Name + { + get + { + return "Message Board Plugin"; + } + } + + public async Task OnLoad() + { + await Task.Run(() => + { + forum = new Forum.Manager(); + forum.Start(); + }); + } + + public async Task OnUnload() + { + forum.Stop(); + } + + public async Task OnTick(Server S) + { + return; + } + + public async Task OnEvent(Event E, Server S) + { + if (E.Type == Event.GType.Start) + { + if (stupidServer == null) + stupidServer = S; + } + } + } +} diff --git a/MessageboardPlugin/Rank.cs b/MessageboardPlugin/Rank.cs new file mode 100644 index 000000000..eb725d779 --- /dev/null +++ b/MessageboardPlugin/Rank.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MessageBoard +{ + public class Rank : Identifiable + { + public string name; + public SharedLibrary.Player.Permission equivalentRank; + public int id; + + /// + /// Initial creation + /// + /// + /// + /// + public Rank(string name, SharedLibrary.Player.Permission equivalentRank) + { + this.name = name; + this.equivalentRank = equivalentRank; + id = 0; + } + + public Rank(int id, string name, SharedLibrary.Player.Permission equivalentRank) + { + this.name = name; + this.equivalentRank = equivalentRank; + this.id = id; + } + + public int getID() + { + return id; + } + } + + public class Permission + { + [Flags] + public enum Action + { + NONE = 0x0, + READ = 0x1, + WRITE = 0x2, + MODIFY = 0x4, + DELETE = 0x8 + } + + public int rankID; + public Action actionable; + + public Permission(int rankID, Action actionable) + { + this.rankID = rankID; + this.actionable = actionable; + } + } +} diff --git a/MessageboardPlugin/Session.cs b/MessageboardPlugin/Session.cs new file mode 100644 index 000000000..91396bfbd --- /dev/null +++ b/MessageboardPlugin/Session.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MessageBoard +{ + public class Session + { + public User sessionUser; + public string sessionID { get; private set; } + public DateTime sessionStartTime; + + public Session(User sessionUser, string sessionID) + { + this.sessionUser = sessionUser; + this.sessionID = sessionID; + sessionStartTime = DateTime.Now; + } + + } +} diff --git a/MessageboardPlugin/Storage.cs b/MessageboardPlugin/Storage.cs new file mode 100644 index 000000000..94812bc9f --- /dev/null +++ b/MessageboardPlugin/Storage.cs @@ -0,0 +1,554 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Data; + +namespace MessageBoard.Storage +{ + class Database : SharedLibrary.Database + { + public Database(String FN) : base(FN) { } + + public override void Init() + { + if (!System.IO.File.Exists(FileName)) + { + string createClientTable = @"CREATE TABLE [USERS] ( + [id] INTEGER PRIMARY KEY AUTOINCREMENT, + [ranking] INTEGER DEFAULT 0, + [username] TEXT NOT NULL, + [email] TEXT NOT NULL, + [passwordhash] TEXT NOT NULL, + [passwordsalt] TEXT NOT NULL, + [lastlogin] TEXT NOT NULL, + [creationdate] TEXT NOT NULL, + [subscribedthreads] TEXT DEFAULT 0, + [avatarurl] TEXT + );"; + + string createSessionTable = @"CREATE TABLE [SESSIONS] ( + [sessionid] TEXT NOT NULL, + [sessionuserid] INTEGER NOT NULL, + FOREIGN KEY(sessionuserid) REFERENCES USERS(id) + );"; + + string createRankingTable = @"CREATE TABLE [RANKS] ( + [id] INTEGER PRIMARY KEY AUTOINCREMENT, + [name] TEXT UNIQUE NOT NULL, + [equivalentrank] INTEGER DEFAULT 0 + );"; + + string createCategoryTable = @"CREATE TABLE [CATEGORIES] ( + [id] INTEGER PRIMARY KEY AUTOINCREMENT, + [title] TEXT NOT NULL, + [description] TEXT NOT NULL, + [permissions] BLOB + );"; + + string createThreadTable = @"CREATE TABLE [THREADS] ( + [id] INTEGER PRIMARY KEY AUTOINCREMENT, + [title] TEXT NOT NULL, + [categoryid] INTEGER NOT NULL, + [replies] INTEGER DEFAULT 0, + [authorid] INTEGER NOT NULL, + [creationdate] TEXT NOT NULL, + [updateddate] TEXT NOT NULL, + [content] TEXT NOT NULL, + [visible] INTEGER DEFAULT 1, + FOREIGN KEY(authorid) REFERENCES USERS(id), + FOREIGN KEY(categoryid) REFERENCES CATEGORIES(id) + );"; + + string createReplyTable = @"CREATE TABLE [REPLIES] ( + [id] INTEGER PRIMARY KEY AUTOINCREMENT, + [title] TEXT NOT NULL, + [authorid] INT NOT NULL, + [threadid] INT NOT NULL, + [creationdate] TEXT NOT NULL, + [updateddate] TEXT NOT NULL, + [content] TEXT NOT NULL, + [visible] INTEGER DEFAULT 1, + FOREIGN KEY(authorid) REFERENCES USERS(id), + FOREIGN KEY(threadid) REFERENCES THREADS(id) + );"; + + + ExecuteNonQuery(createClientTable); + ExecuteNonQuery(createSessionTable); + ExecuteNonQuery(createRankingTable); + ExecuteNonQuery(createCategoryTable); + ExecuteNonQuery(createThreadTable); + ExecuteNonQuery(createReplyTable); + + Rank guestRank = new Rank(1, "Guest", SharedLibrary.Player.Permission.User); + Rank userRank = new Rank(2, "User", SharedLibrary.Player.Permission.Trusted); + Rank modRank = new Rank(3, "Moderator", SharedLibrary.Player.Permission.Moderator); + Rank adminRank = new Rank(4, "Administrator", SharedLibrary.Player.Permission.Owner); + + addRank(guestRank); + addRank(userRank); + addRank(modRank); + addRank(adminRank); + + List defaultCatPerms = new List { + new Permission(guestRank.getID(), Permission.Action.READ), + new Permission(userRank.getID(), Permission.Action.READ | Permission.Action.WRITE), + new Permission(modRank.getID(), Permission.Action.READ | Permission.Action.WRITE | Permission.Action.MODIFY), + new Permission(adminRank.getID(), Permission.Action.READ | Permission.Action.WRITE | Permission.Action.MODIFY | Permission.Action.DELETE) + }; + + Category defaultCat = new Category(1, "Default Category", "This is the default category.", defaultCatPerms); + addCategory(defaultCat); + } + } + + #region SESSIONS + public Session getSession(string sessionID) + { + DataTable Result = GetDataTable("SESSIONS", new KeyValuePair("sessionid", sessionID)); + + if (Result != null && Result.Rows.Count > 0) + { + DataRow ResponseRow = Result.Rows[0]; + int userID = Int32.Parse(ResponseRow["sessionuserid"].ToString()); + User sessionUser = getUser(userID); + + // this shouldn't happen.. but it might :c + if (sessionUser == null) + return null; + + Session foundSession = new Session(sessionUser, sessionID); + return foundSession; + } + + else + return null; + } + + public Session setSession(int userID, string sessionID) + { + // prevent duplicated tuples + if (getSession(sessionID) != null) + { + updateSession(sessionID, userID); + return getSession(sessionID); + } + + Dictionary newSession = new Dictionary(); + + newSession.Add("sessionid", sessionID); + newSession.Add("sessionuserid", userID); + + Insert("SESSIONS", newSession); + + return getSession(sessionID); + } + + public bool updateSession(string sessionID, int userID) + { + if (getSession(sessionID) == null) + return false; + + Dictionary updatedSession = new Dictionary(); + updatedSession.Add("sessionuserid", userID); + + Update("SESSIONS", updatedSession, new KeyValuePair("sessionid", sessionID)); + return true; + } + #endregion + + #region USERS + private User getUserFromDataTable(DataTable Result) + { + if (Result != null && Result.Rows.Count > 0) + { + DataRow ResponseRow = Result.Rows[0]; + int id = Convert.ToInt32(ResponseRow["id"].ToString()); + string passwordHash = ResponseRow["passwordhash"].ToString(); + string passwordSalt = ResponseRow["passwordsalt"].ToString(); + string username = ResponseRow["username"].ToString(); + string email = ResponseRow["email"].ToString(); + DateTime lastLogon = DateTime.Parse(ResponseRow["lastlogin"].ToString()); + DateTime creationDate = DateTime.Parse(ResponseRow["creationdate"].ToString()); + Rank ranking = getRank(Convert.ToInt32(ResponseRow["ranking"])); + string avatarURL = ResponseRow["avatarurl"].ToString(); + string posts = GetDataTable(String.Format("select (select count(*) from THREADS where authorid = {0}) + (select count(*) from REPLIES where authorid = {0}) as posts;", id)).Rows[0]["posts"].ToString(); + + User foundUser = new User(id, passwordHash, passwordSalt, username, email, Convert.ToInt32(posts), lastLogon, creationDate, ranking, avatarURL); + return foundUser; + } + + return null; + } + + private Dictionary getDataTableFromUser(User addedUser) + { + Dictionary newUser = new Dictionary(); + + newUser.Add("username", addedUser.username); + newUser.Add("email", addedUser.email); + newUser.Add("passwordhash", addedUser.getPasswordHash()); + newUser.Add("passwordsalt", addedUser.getPasswordSalt()); + newUser.Add("lastlogin", SharedLibrary.Utilities.DateTimeSQLite(addedUser.lastLogin)); + newUser.Add("creationdate", SharedLibrary.Utilities.DateTimeSQLite(addedUser.creationDate)); + //newUser.Add("subscribedthreads", String.Join(",", addedUser.subscribedThreads)); + newUser.Add("ranking", addedUser.ranking.getID()); + newUser.Add("avatarurl", addedUser.avatarURL); + + return newUser; + } + + public User getUser(int userid) + { + DataTable Result = GetDataTable("USERS", new KeyValuePair("id", userid)); + + return getUserFromDataTable(Result); + } + + public User getUser(string username) + { + DataTable Result = GetDataTable("USERS", new KeyValuePair("username", username)); + + return getUserFromDataTable(Result); + } + + public bool userExists(string username, string email) + { + String Query = String.Format("SELECT * FROM USERS WHERE username = '{0}' or email = '{1}'", username, email); + DataTable Result = GetDataTable(Query); + + return Result.Rows.Count > 0; + } + + /// + /// Returns ID of added user + /// + /// + /// + /// + public User addUser(User addedUser, Session userSession) + { + var newUser = getDataTableFromUser(addedUser); + Insert("USERS", newUser); + + // fixme + User createdUser = getUser(addedUser.username); + return createdUser; + } + + public bool updateUser(User updatedUser) + { + var user = getDataTableFromUser(updatedUser); + Update("USERS", user, new KeyValuePair("id", updatedUser.getID())); + + return true; + } + + public int getNumUsers() + { + var Result = GetDataTable("SELECT COUNT(id) AS userCount FROM `USERS`;"); + return Convert.ToInt32(Result.Rows[0]["userCount"]); + } + #endregion + + #region CATEGORIES + private Category getCategoryFromDataTable(DataTable Result) + { + if (Result != null && Result.Rows.Count > 0) + { + DataRow ResponseRow = Result.Rows[0]; + + int id = Convert.ToInt32(ResponseRow["id"]); + string title = ResponseRow["title"].ToString(); + string description = ResponseRow["description"].ToString(); + string permissions = Encoding.UTF8.GetString((byte[])ResponseRow["permissions"]); + List perms = Newtonsoft.Json.JsonConvert.DeserializeObject>(permissions); + + Category requestedCategory = new Category(id, title, description, perms); + return requestedCategory; + } + + return null; + } + + public void addCategory(Category addingCategory) + { + Dictionary newCategory = new Dictionary(); + + newCategory.Add("title", addingCategory.title); + newCategory.Add("description", addingCategory.description); + newCategory.Add("permissions", Newtonsoft.Json.JsonConvert.SerializeObject(addingCategory.permissions)); + + Insert("CATEGORIES", newCategory); + } + + public Category getCategory(int id) + { + string Query = String.Format("SELECT * FROM CATEGORIES WHERE id = {0}", id); + DataTable Result = GetDataTable(Query); + + return getCategoryFromDataTable(Result); + } + + public List getAllCategories() + { + string Query = String.Format("SELECT id FROM CATEGORIES"); + List cats = new List(); + DataTable Result = GetDataTable(Query); + + if (Result != null && Result.Rows.Count > 0) + { + for (int i = 0; i < Result.Rows.Count; i++) + cats.Add(getCategory(Convert.ToInt32(Result.Rows[i]["id"]))); + } + + return cats; + } + #endregion + + #region THREADS + + public Dictionary getDataTableFromThread(ForumThread Thread) + { + Dictionary newThread = new Dictionary(); + newThread.Add("title", Thread.title); + newThread.Add("categoryid", Thread.threadCategory.getID()); + newThread.Add("replies", Thread.replies); + newThread.Add("authorid", Thread.author.getID()); + newThread.Add("creationdate", SharedLibrary.Utilities.DateTimeSQLite(Thread.creationDate)); + newThread.Add("updateddate", SharedLibrary.Utilities.DateTimeSQLite(Thread.updatedDate)); + newThread.Add("content", Thread.content); + newThread.Add("visible", Convert.ToInt32(Thread.visible)); + + return newThread; + } + + public int addThread(ForumThread Thread) + { + Insert("THREADS", getDataTableFromThread(Thread)); + return getThreadID(Thread.creationDate); + } + + + public bool updateThread(ForumThread updatedThread) + { + var user = getDataTableFromThread(updatedThread); + Update("THREADS", user, new KeyValuePair("id", updatedThread.getID())); + + return true; + } + + public ForumThread getThread(int id) + { + DataTable Result = GetDataTable("THREADS", new KeyValuePair("id", id)); + + return getThreadFromDataTable(Result); + } + + private ForumThread getThreadFromDataTable(DataTable Result) + { + if (Result != null && Result.Rows.Count > 0) + { + DataRow ResponseRow = Result.Rows[0]; + int id = Convert.ToInt32(ResponseRow["id"].ToString()); + int categoryid = Convert.ToInt32(ResponseRow["categoryid"].ToString()); + int authorid = Convert.ToInt32(ResponseRow["authorid"].ToString()); + int replies = Convert.ToInt32(ResponseRow["replies"].ToString()); + string title = ResponseRow["title"].ToString(); + + var category = getCategory(categoryid); + var author = getUser(authorid); + + bool visible = Convert.ToBoolean((Convert.ToInt32(ResponseRow["visible"]))); + + DateTime creationDate = DateTime.Parse(ResponseRow["creationdate"].ToString()); + DateTime updatedDate = DateTime.Parse(ResponseRow["updateddate"].ToString()); + string content = ResponseRow["content"].ToString(); + + ForumThread retrievedThread = new ForumThread(id, title, visible, content, replies, author, category, creationDate, updatedDate); + return retrievedThread; + } + + return null; + } + + // we have no other unique id yet + private int getThreadID(DateTime creationDate) + { + string Query = String.Format("SELECT * FROM THREADS WHERE creationdate = \"{0}\"", SharedLibrary.Utilities.DateTimeSQLite(creationDate)); + DataTable Result = GetDataTable(Query); + + if (Result != null && Result.Rows.Count > 0) + return Convert.ToInt32(Result.Rows[0]["id"].ToString()); + + return 0; + } + + public List getRecentThreads(int categoryID) + { + List threads = new List(); + string Query = String.Format("SELECT id FROM THREADS WHERE categoryid = {0} AND visible = 1 ORDER BY `updateddate` DESC LIMIT 3", categoryID); + DataTable Result = GetDataTable(Query); + + if (Result != null && Result.Rows.Count > 0) + { + for (int i = 0; i < Result.Rows.Count; i++) + threads.Add(getThread(Convert.ToInt32(Result.Rows[i]["id"]))); + } + + return threads; + } + + public List getCategoryThreads(int categoryID) + { + List threads = new List(); + string Query = String.Format("SELECT id FROM THREADS WHERE categoryid = {0} and visible = 1 ORDER BY `updateddate` DESC", categoryID); + DataTable Result = GetDataTable(Query); + + if (Result != null && Result.Rows.Count > 0) + { + for (int i = 0; i < Result.Rows.Count; i++) + threads.Add(getThread(Convert.ToInt32(Result.Rows[i]["id"]))); + } + + return threads; + } + #endregion + + #region RANKING + public int addRank(Rank newRank) + { + Dictionary rank = new Dictionary(); + rank.Add("name", newRank.name); + rank.Add("equivalentrank", (int)newRank.equivalentRank); + + Insert("RANKS", rank); + + Rank r = getRank(newRank.name); + + if (r == null) + return 0; + + return r.getID(); + } + + public Rank getRank(string rankName) + { + DataTable Result = GetDataTable("RANKS", new KeyValuePair("name", rankName)); + + if (Result != null && Result.Rows.Count > 0) + { + DataRow ResponseRow = Result.Rows[0]; + string name = ResponseRow["name"].ToString(); + int equivRank = Convert.ToInt32(ResponseRow["equivalentrank"].ToString()); + int id = Convert.ToInt32(ResponseRow["id"].ToString()); + + Rank retrievedRank = new Rank(id, name, (SharedLibrary.Player.Permission)equivRank); + return retrievedRank; + } + + return null; + } + + public Rank getRank(int rankID) + { + DataTable Result = GetDataTable("RANKS", new KeyValuePair("id", rankID)); + + if (Result != null && Result.Rows.Count > 0) + { + DataRow ResponseRow = Result.Rows[0]; + string name = ResponseRow["name"].ToString(); + int equivRank = Convert.ToInt32(ResponseRow["equivalentrank"].ToString()); + + Rank retrievedRank = new Rank(rankID, name, (SharedLibrary.Player.Permission)equivRank); + return retrievedRank; + } + + return null; + } + #endregion + + #region REPLIES + public int addReply(Post reply) + { + Insert("REPLIES", getDataTableFromReply(reply)); + return getReplyID(reply.creationDate); + } + + public bool updateReply(Post reply) + { + return Update("REPLIES", getDataTableFromReply(reply), new KeyValuePair("id", reply.id)); + } + + public Post getReply(int id) + { + DataTable Result = GetDataTable("REPLIES", new KeyValuePair("id", id)); + + return getReplyFromDataTable(Result); + } + + public List getRepliesFromThreadID(int threadID) + { + List replies = new List(); + //var Result = GetDataTable("REPLIES", new KeyValuePair("threadid", threadID)); + var Result = GetDataTable("SELECT * FROM REPLIES WHERE threadid = " + threadID + " AND visible = 1"); + + foreach (DataRow row in Result.Rows) + { + replies.Add(getReply(Convert.ToInt32(row["id"].ToString()))); + } + + return replies; + } + + private Dictionary getDataTableFromReply(Post reply) + { + Dictionary newReply = new Dictionary(); + newReply.Add("title", reply.title); + newReply.Add("authorid", reply.author.getID()); + newReply.Add("threadid", reply.threadid); + newReply.Add("creationdate", SharedLibrary.Utilities.DateTimeSQLite(reply.creationDate)); + newReply.Add("updateddate", SharedLibrary.Utilities.DateTimeSQLite(reply.updatedDate)); + newReply.Add("content", reply.content); + newReply.Add("visible", Convert.ToInt32(reply.visible)); + + return newReply; + } + + private Post getReplyFromDataTable(DataTable Result) + { + if (Result != null && Result.Rows.Count > 0) + { + DataRow ResponseRow = Result.Rows[0]; + int id = Convert.ToInt32(ResponseRow["id"].ToString()); + int threadid = Convert.ToInt32(ResponseRow["threadid"].ToString()); + int authorid = Convert.ToInt32(ResponseRow["authorid"].ToString()); + string title = ResponseRow["title"].ToString(); + var author = getUser(authorid); + + DateTime creationDate = DateTime.Parse(ResponseRow["creationdate"].ToString()); + DateTime updatedDate = DateTime.Parse(ResponseRow["updateddate"].ToString()); + string content = ResponseRow["content"].ToString(); + + bool visible = Convert.ToBoolean((Convert.ToInt32(ResponseRow["visible"]))); + + Post retrievedPost = new Post(id, threadid, visible, title, content, author, creationDate, updatedDate); + return retrievedPost; + } + + return null; + } + + // we have no other unique id yet + private int getReplyID(DateTime creationDate) + { + DataTable Result = GetDataTable("REPLIES", new KeyValuePair("creationdate", SharedLibrary.Utilities.DateTimeSQLite(creationDate))); + + if (Result != null && Result.Rows.Count > 0) + return Convert.ToInt32(Result.Rows[0]["id"].ToString()); + + return 0; + } + #endregion + } +} diff --git a/MessageboardPlugin/Thread.cs b/MessageboardPlugin/Thread.cs new file mode 100644 index 000000000..15cfbf416 --- /dev/null +++ b/MessageboardPlugin/Thread.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MessageBoard +{ + + public class Post : ForumThread + { + /// + /// Initial creation + /// + /// + /// + /// + /// + /// + + public int threadid; + + public Post(string title, int threadid, string content, User author) : base (title, content, author, null) + { + this.threadid = threadid; + } + + public Post(int id, int threadid, bool visible, string title, string content, User author, DateTime creationDate, DateTime updatedDate) : base(id, title, visible, content, 0, author, null, creationDate, updatedDate) + { + this.lastModificationString = SharedLibrary.Utilities.timePassed(creationDate); + this.threadid = threadid; + } + + } + + + public class Category : Identifiable + { + public int id { get; private set; } + public string title { get; private set; } + public string description { get; private set; } + public List permissions { get; private set; } + + public Category(string title, string description) + { + this.title = title; + this.description = description; + this.permissions = new List(); + id = 0; + } + + public Category(int id, string title, string description, List permissions) + { + this.title = title; + this.description = description; + this.id = id; + this.permissions = permissions; + } + + public int getID() + { + return id; + } + } + + public class ForumThread : Identifiable + { + public string title { get; private set; } + public string content { get; private set; } + public User author { get; private set; } + public Category threadCategory { get; private set; } + public DateTime creationDate { get; private set; } + public DateTime updatedDate; + public string lastModificationString { get; protected set; } + public int id { get; private set; } + public int replies; + public bool visible = true; + + /// + /// Initial creation + /// + /// + /// + /// + public ForumThread(string title, string content, User author, Category threadCategory) + { + if (content.Length == 0) + throw new Exceptions.ThreadException("Post is empty"); + if (author == null) + throw new Exceptions.ThreadException("No author of post"); + if (title.Length == 0) + throw new Exceptions.ThreadException("Title is empty"); + + this.title = title; + this.content = content; + this.author = author; + this.threadCategory = threadCategory; + creationDate = DateTime.Now; + updatedDate = DateTime.Now; + replies = 0; + id = 0; + } + + /// + /// Loading from database + /// + /// + /// + /// + /// + /// + public ForumThread(int id, string title, bool visible, string content, int replies, User author, Category threadCategory, DateTime creationDate, DateTime updatedDate) + { + this.id = id; + this.replies = replies; + this.title = title; + this.content = content; + this.author = author; + this.threadCategory = threadCategory; + this.creationDate = creationDate; + this.updatedDate = updatedDate; + this.lastModificationString = SharedLibrary.Utilities.timePassed(updatedDate); + this.visible = visible; + } + + public int getID() + { + return id; + } + + public bool updateContent(string content) + { + if (content != null && content.Length > 0) + { + this.content = content; + return true; + } + + return false; + } + + public bool updateTitle(string title) + { + if (title != null && title.Length > 0) + { + this.title = title; + return true; + } + + return false; + } + } +} diff --git a/MessageboardPlugin/User.cs b/MessageboardPlugin/User.cs new file mode 100644 index 000000000..06bdbfc36 --- /dev/null +++ b/MessageboardPlugin/User.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using SharedLibrary; + +namespace MessageBoard +{ + public class User : Identifiable + { + private string passwordHash; // byte array -> b64 string + private string passwordSalt; // byte array -> b64 string + public DateTime lastLogin; + public string lastLoginString; + public readonly DateTime creationDate; + public int id { get; private set; } + public string avatarURL; + + public string username { get; private set; } + public string email { get; private set; } + public Rank ranking; + + public int posts; + public int privateMessages; + public int warnings; + + public List subscribedThreads { get; private set; } + + public User() + { + username = "Guest"; + ranking = Plugin.Main.forum.guestRank; + } + + /// + /// When creating a new user + /// + /// + /// + /// + /// + /// + /// + /// + public User(string username, string matchedUsername, string email, string passwordHash, string passwordSalt, Rank ranking) + { + if (username.Length < 1) + throw new Exceptions.UserException("Username is empty"); + if (email.Length < 1) + throw new Exceptions.UserException("Email is empty"); + + lastLogin = DateTime.Now; + subscribedThreads = new List(); + + this.username = username; + this.email = email; + this.posts = 0; + this.privateMessages = 0; + this.warnings = 0; + this.ranking = ranking; + this.passwordHash = passwordHash; + this.passwordSalt = passwordSalt; + this.creationDate = DateTime.Now; + this.avatarURL = ""; + + id = 0; + } + + public User(int id, string passwordHash, string passwordSalt, string username, string email, int posts, DateTime lastLogin, DateTime creationDate, Rank ranking, string avatarURL) + { + this.id = id; + this.passwordHash = passwordHash; + this.passwordSalt = passwordSalt; + this.username = username; + this.email = email; + this.lastLogin = lastLogin; + this.creationDate = creationDate; + this.ranking = ranking; + this.avatarURL = avatarURL; + this.posts = posts; + + this.lastLoginString = SharedLibrary.Utilities.timePassed(lastLogin); + } + + public int getID() + { + return this.id; + } + + public string getPasswordSalt() + { + return this.passwordSalt; + } + + public string getPasswordHash() + { + return this.passwordHash; + } + } + + public struct UserInfo + { + public string username; + public string email; + public string matchedUsername; + public Rank rank; + } +} diff --git a/MessageboardPlugin/forum/category.html b/MessageboardPlugin/forum/category.html new file mode 100644 index 000000000..ea3137904 --- /dev/null +++ b/MessageboardPlugin/forum/category.html @@ -0,0 +1,86 @@ +
+
+ +
+ + +
+ + Post +
+
+
+
+
+
+
+
+ + + + + diff --git a/MessageboardPlugin/forum/home.html b/MessageboardPlugin/forum/home.html new file mode 100644 index 000000000..8f9511ee0 --- /dev/null +++ b/MessageboardPlugin/forum/home.html @@ -0,0 +1,60 @@ +
+ +
+
+ + +
+
+
+ +
+
+ Online Users +
+
+
+
+ +
+ + diff --git a/MessageboardPlugin/forum/login.html b/MessageboardPlugin/forum/login.html new file mode 100644 index 000000000..0beeb644a --- /dev/null +++ b/MessageboardPlugin/forum/login.html @@ -0,0 +1,59 @@ + + + diff --git a/MessageboardPlugin/forum/postthread.html b/MessageboardPlugin/forum/postthread.html new file mode 100644 index 000000000..1ce18012e --- /dev/null +++ b/MessageboardPlugin/forum/postthread.html @@ -0,0 +1,56 @@ +
+
+
+ + Post New Thread +
+ +
+
+ +
+
+ + +
+ + +
+
+
+ + diff --git a/MessageboardPlugin/forum/register.html b/MessageboardPlugin/forum/register.html new file mode 100644 index 000000000..3560d26d4 --- /dev/null +++ b/MessageboardPlugin/forum/register.html @@ -0,0 +1,92 @@ + + + + diff --git a/MessageboardPlugin/forum/thread.html b/MessageboardPlugin/forum/thread.html new file mode 100644 index 000000000..7b10c0abe --- /dev/null +++ b/MessageboardPlugin/forum/thread.html @@ -0,0 +1,126 @@ +
+
Home »
+
+
+
+
+ +
+ _
+ _ +
+
+
_
+
+ + Reply +
+
+
_
+
+
+
+ + diff --git a/MessageboardPlugin/forum/user.html b/MessageboardPlugin/forum/user.html new file mode 100644 index 000000000..9a0619523 --- /dev/null +++ b/MessageboardPlugin/forum/user.html @@ -0,0 +1,56 @@ + + +
+
+
+
+ _ +
+ +
+ _ +
+ +
+ _ +
+ +
+ _ +
+ +
+ _ +
+
+
+
+ +
+
+
+
+
+ + + diff --git a/MessageboardPlugin/packages.config b/MessageboardPlugin/packages.config new file mode 100644 index 000000000..24c7fe550 --- /dev/null +++ b/MessageboardPlugin/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Release Build/lib/AdminInterface.dll b/Release Build/lib/AdminInterface.dll deleted file mode 100644 index caef8306d..000000000 Binary files a/Release Build/lib/AdminInterface.dll and /dev/null differ diff --git a/SharedLibrary/Command.cs b/SharedLibrary/Command.cs index e35932aa7..ac5e626f3 100644 --- a/SharedLibrary/Command.cs +++ b/SharedLibrary/Command.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading.Tasks; namespace SharedLibrary { @@ -18,7 +19,7 @@ namespace SharedLibrary } //Execute the command - abstract public void Execute(Event E); + abstract public Task ExecuteAsync(Event E); public String Name { get; private set; } public String Description { get; private set; } diff --git a/Admin/Command.cs b/SharedLibrary/Commands/NativeCommands.cs similarity index 54% rename from Admin/Command.cs rename to SharedLibrary/Commands/NativeCommands.cs index bc587150b..02af2c584 100644 --- a/Admin/Command.cs +++ b/SharedLibrary/Commands/NativeCommands.cs @@ -2,24 +2,37 @@ using System.Collections.Generic; using System.Text; using SharedLibrary; +using SharedLibrary.Network; +using System.Threading.Tasks; -namespace IW4MAdmin +namespace SharedLibrary.Commands { + class Quit : Command + { + public Quit(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override async Task ExecuteAsync(Event E) + { + E.Owner.Manager.Stop(); + } + } + class Owner : Command { + public Owner(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { if (E.Owner.clientDB.getOwner() == null) { E.Origin.setLevel(Player.Permission.Owner); - E.Origin.Tell("Congratulations, you have claimed ownership of this server!"); + await E.Origin.Tell("Congratulations, you have claimed ownership of this server!"); E.Owner.owner = E.Origin; E.Owner.clientDB.updatePlayer(E.Origin); } else - E.Origin.Tell("This server already has an owner!"); + await E.Origin.Tell("This server already has an owner!"); } } @@ -27,15 +40,13 @@ namespace IW4MAdmin { public Warn(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { - E.Target.lastOffense = SharedLibrary.Utilities.removeWords(E.Data, 1); + E.Target.lastOffense = E.Data.RemoveWords(1); if (E.Origin.Level <= E.Target.Level) - E.Origin.Tell("You cannot warn " + E.Target.Name); + await E.Origin.Tell("You cannot warn " + E.Target.Name); else - { - E.Target.Warn(E.Target.lastOffense, E.Origin); - } + await E.Target.Warn(E.Target.lastOffense, E.Origin); } } @@ -43,12 +54,12 @@ namespace IW4MAdmin { public WarnClear(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { E.Target.lastOffense = String.Empty; E.Target.Warnings = 0; String Message = String.Format("All warning cleared for {0}", E.Target.Name); - E.Owner.Broadcast(Message); + await E.Owner.Broadcast(Message); } } @@ -56,13 +67,13 @@ namespace IW4MAdmin { public Kick(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { - E.Target.lastOffense = SharedLibrary.Utilities.removeWords(E.Data, 1); + E.Target.lastOffense = SharedLibrary.Utilities.RemoveWords(E.Data, 1); if (E.Origin.Level > E.Target.Level) - E.Target.Kick(E.Target.lastOffense, E.Origin); + await E.Target.Kick(E.Target.lastOffense, E.Origin); else - E.Origin.Tell("You cannot kick " + E.Target.Name); + await E.Origin.Tell("You cannot kick " + E.Target.Name); } } @@ -70,9 +81,9 @@ namespace IW4MAdmin { public Say(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { - E.Owner.Broadcast("^1" + E.Origin.Name + " - ^6" + E.Data + "^7"); + await E.Owner.Broadcast("^1" + E.Origin.Name + " - ^6" + E.Data + "^7"); } } @@ -80,14 +91,14 @@ namespace IW4MAdmin { public TempBan(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { - E.Target.lastOffense = SharedLibrary.Utilities.removeWords(E.Data, 1); - String Message = "^1Player Temporarily Banned: ^5" + E.Target.lastOffense + "^7 (1 hour)"; + E.Target.lastOffense = SharedLibrary.Utilities.RemoveWords(E.Data, 1); + String Message = E.Target.lastOffense; if (E.Origin.Level > E.Target.Level) - E.Target.tempBan(Message, E.Origin); + await E.Target.TempBan(Message, E.Origin); else - E.Origin.Tell("You cannot temp ban " + E.Target.Name); + await E.Origin.Tell("You cannot temp ban " + E.Target.Name); } } @@ -95,22 +106,22 @@ namespace IW4MAdmin { public SBan(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { - E.Target.lastOffense = SharedLibrary.Utilities.removeWords(E.Data, 1); + E.Target.lastOffense = SharedLibrary.Utilities.RemoveWords(E.Data, 1); E.Target.lastEvent = E; // needs to be fixed String Message; if (E.Owner.Website == null) Message = "^1Player Banned: ^5" + E.Target.lastOffense; else - Message = "^1Player Banned: ^5" + E.Target.lastOffense + "^7 (appeal at" + E.Owner.Website + ")"; + Message = "^1Player Banned: ^5" + E.Target.lastOffense; if (E.Origin.Level > E.Target.Level) { - E.Target.Ban(Message, E.Origin); - E.Origin.Tell(String.Format("Sucessfully banned ^5{0} ^7({1})", E.Target.Name, E.Target.npID)); + await E.Target.Ban(Message, E.Origin); + await E.Origin.Tell(String.Format("Sucessfully banned ^5{0} ^7({1})", E.Target.Name, E.Target.npID)); } else - E.Origin.Tell("You cannot ban " + E.Target.Name); + await E.Origin.Tell("You cannot ban " + E.Target.Name); } } @@ -118,12 +129,10 @@ namespace IW4MAdmin { public Unban(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { - if (E.Owner.Unban(E.Data.Trim(), E.Target)) - E.Origin.Tell("Successfully unbanned " + E.Target.Name); - else - E.Origin.Tell("Unable to find a ban for that GUID"); + await E.Owner.Unban(E.Data.Trim(), E.Target); + await E.Origin.Tell($"Successfully unbanned {E.Target.Name}::{E.Target.npID}"); } } @@ -131,10 +140,10 @@ namespace IW4MAdmin { public WhoAmI(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { String You = String.Format("{0} [^3#{1}^7] {2} [^3@{3}^7] [{4}^7] IP: {5}", E.Origin.Name, E.Origin.clientID, E.Origin.npID, E.Origin.databaseID, SharedLibrary.Utilities.levelToColor(E.Origin.Level), E.Origin.IP); - E.Origin.Tell(You); + await E.Origin.Tell(You); } } @@ -142,28 +151,31 @@ namespace IW4MAdmin { public List(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { StringBuilder playerList = new StringBuilder(); - lock (E.Owner.getPlayers()) + int count = 0; + for (int i = 0; i < E.Owner.Players.Count; i++) { - int count = 0; - foreach (Player P in E.Owner.getPlayers()) + var P = E.Owner.Players[i]; + + if (P == null) + continue; + + if (P.Masked) + playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.levelToColor(Player.Permission.User), P.clientID, P.Name, SharedLibrary.Utilities.getSpaces(Player.Permission.SeniorAdmin.ToString().Length - Player.Permission.User.ToString().Length)); + else + playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.levelToColor(P.Level), P.clientID, P.Name, SharedLibrary.Utilities.getSpaces(Player.Permission.SeniorAdmin.ToString().Length - P.Level.ToString().Length)); + + if (count == 2 || E.Owner.getPlayers().Count == 1) { - if (P == null) - continue; - - playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", SharedLibrary.Utilities.levelToColor(P.Level), P.clientID, P.Name, SharedLibrary.Utilities.getSpaces(Player.Permission.SeniorAdmin.ToString().Length - P.Level.ToString().Length)); - if (count == 2) - { - E.Origin.Tell(playerList.ToString()); - count = 0; - playerList = new StringBuilder(); - continue; - } - - count++; + await E.Origin.Tell(playerList.ToString()); + count = 0; + playerList = new StringBuilder(); + continue; } + + count++; } } } @@ -172,48 +184,48 @@ namespace IW4MAdmin { public Help(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { String cmd = E.Data.Trim(); if (cmd.Length > 2) { bool found = false; - foreach (Command C in E.Owner.getCommands()) + foreach (Command C in E.Owner.Manager.GetCommands()) { if (C.Name.Contains(cmd) || C.Name == cmd) { - E.Origin.Tell(" [^3" + C.Name + "^7] " + C.Description); + await E.Origin.Tell(" [^3" + C.Name + "^7] " + C.Description); found = true; } } if (!found) - E.Origin.Tell("Could not find that command"); + await E.Origin.Tell("Could not find that command"); } else { int count = 0; StringBuilder helpResponse = new StringBuilder(); - List test = E.Owner.getCommands(); + List CommandList = E.Owner.Manager.GetCommands(); - foreach (Command C in test) + foreach (Command C in CommandList) { if (E.Origin.Level >= C.Permission) { helpResponse.Append(" [^3" + C.Name + "^7] "); if (count >= 4) { - E.Origin.Tell(helpResponse.ToString()); + await E.Origin.Tell(helpResponse.ToString()); helpResponse = new StringBuilder(); count = 0; } count++; } } - E.Origin.Tell(helpResponse.ToString()); - E.Origin.Tell("Type !help to get command usage example"); + await E.Origin.Tell(helpResponse.ToString()); + await E.Origin.Tell("Type !help to get command usage example"); } } } @@ -222,22 +234,23 @@ namespace IW4MAdmin { public FastRestart(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { - E.Owner.Broadcast("Performing fast restart in 5 seconds..."); - E.Owner.fastRestart(5); + await E.Owner.Broadcast("Performing fast restart..."); + await Task.Delay(3000); + await E.Owner.ExecuteCommandAsync("fast_restart"); } - } class MapRotate : Command { public MapRotate(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { - E.Owner.Broadcast("Performing map rotate in 5 seconds..."); - E.Owner.mapRotate(5); + await E.Owner.Broadcast("Performing map rotate..."); + await Task.Delay(3000); + await E.Owner.ExecuteCommandAsync("map_rotate"); } } @@ -245,27 +258,41 @@ namespace IW4MAdmin { public SetLevel(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { if (E.Target == E.Origin) { - E.Origin.Tell("You can't set your own level, silly."); + await E.Origin.Tell("You can't set your own level, silly."); return; } - Player.Permission newPerm = SharedLibrary.Utilities.matchPermission(SharedLibrary.Utilities.removeWords(E.Data, 1)); + Player.Permission newPerm = Utilities.matchPermission(Utilities.RemoveWords(E.Data, 1)); if (newPerm > Player.Permission.Banned) { E.Target.setLevel(newPerm); - E.Target.Tell("Congratulations! You have been promoted to ^3" + newPerm); - E.Origin.Tell(E.Target.Name + " was successfully promoted!"); + // prevent saving of old permissions on disconnect + foreach (var server in E.Owner.Manager.GetServers()) + { + foreach (var player in server.getPlayers()) + { + if (player != null && player.npID == E.Target.npID) + { + player.setLevel(newPerm); + await E.Target.Tell("Congratulations! You have been promoted to ^3" + newPerm); + } + } + } + + await E.Target.Tell("Congratulations! You have been promoted to ^3" + newPerm); + await E.Origin.Tell(E.Target.Name + " was successfully promoted!"); + //NEEED TO MOVE E.Owner.clientDB.updatePlayer(E.Target); } else - E.Origin.Tell("Invalid group specified."); + await E.Origin.Tell("Invalid group specified."); } } @@ -273,9 +300,9 @@ namespace IW4MAdmin { public Usage(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { - E.Origin.Tell("IW4M Admin is using " + Math.Round(((System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64 / 2048f) / 1200f), 1) + "MB"); + await E.Origin.Tell("IW4M Admin is using " + Math.Round(((System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64 / 2048f) / 1200f), 1) + "MB"); } } @@ -283,10 +310,10 @@ namespace IW4MAdmin { public Uptime(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { TimeSpan uptime = DateTime.Now - System.Diagnostics.Process.GetCurrentProcess().StartTime; - E.Origin.Tell(String.Format("IW4M Admin has been up for {0} days, {1} hours, and {2} minutes", uptime.Days, uptime.Hours, uptime.Minutes)); + await E.Origin.Tell(String.Format("IW4M Admin has been up for {0} days, {1} hours, and {2} minutes", uptime.Days, uptime.Hours, uptime.Minutes)); } } @@ -294,19 +321,13 @@ namespace IW4MAdmin { public Admins(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { List activePlayers = E.Owner.getPlayers(); - lock (activePlayers) - { - foreach (Player P in E.Owner.getPlayers()) - { - if (P != null && P.Level > Player.Permission.Flagged && !P.Masked) - { - E.Origin.Tell(String.Format("[^3{0}^7] {1}", SharedLibrary.Utilities.levelToColor(P.Level), P.Name)); - } - } - } + + foreach (Player P in E.Owner.getPlayers()) + if (P != null && P.Level > Player.Permission.Flagged && !P.Masked) + await E.Origin.Tell(String.Format("[^3{0}^7] {1}", Utilities.levelToColor(P.Level), P.Name)); } } @@ -314,23 +335,23 @@ namespace IW4MAdmin { public MapCMD(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { string newMap = E.Data.Trim().ToLower(); foreach (Map m in E.Owner.maps) { if (m.Name.ToLower() == newMap || m.Alias.ToLower() == newMap) { - E.Owner.Broadcast("Changing to map ^2" + m.Alias); - SharedLibrary.Utilities.Wait(3); - E.Owner.Map(m.Name); + await E.Owner.Broadcast("Changing to map ^2" + m.Alias); + await Task.Delay(5000); + await E.Owner.LoadMap(m.Name); return; } } - E.Owner.Broadcast("Attempting to change to unknown map ^1" + newMap); - SharedLibrary.Utilities.Wait(3); - E.Owner.Map(newMap); + await E.Owner.Broadcast("Attempting to change to unknown map ^1" + newMap); + await Task.Delay(5000); + await E.Owner.LoadMap(newMap); } } @@ -338,20 +359,20 @@ namespace IW4MAdmin { public Find(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { var db_players = E.Owner.clientDB.findPlayers(E.Data.Trim()); if (db_players == null) { - E.Origin.Tell("No players found"); + await E.Origin.Tell("No players found"); return; } foreach (Player P in db_players) { String mesg = String.Format("[^3{0}^7] [^3@{1}^7] - [{2}^7] - {3} | last seen {4} ago", P.Name, P.databaseID, SharedLibrary.Utilities.levelToColor(P.Level), P.IP, P.getLastConnection()); - E.Origin.Tell(mesg); + await E.Origin.Tell(mesg); } } } @@ -360,13 +381,13 @@ namespace IW4MAdmin { public FindAll(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { E.Data = E.Data.Trim(); if (E.Data.Length < 4) { - E.Origin.Tell("You must enter at least 4 letters"); + await E.Origin.Tell("You must enter at least 4 letters"); return; } @@ -375,7 +396,7 @@ namespace IW4MAdmin if (db_aliases == null) { - E.Origin.Tell("No players found"); + await E.Origin.Tell("No players found"); return; } @@ -397,7 +418,7 @@ namespace IW4MAdmin if (Current != null) { String mesg = String.Format("^1{0} ^7now goes by ^5{1}^7 [^3{2}^7]", lookingFor, Current.Name, Current.databaseID); - E.Origin.Tell(mesg); + await E.Origin.Tell(mesg); } } } @@ -407,14 +428,14 @@ namespace IW4MAdmin { public Rules(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { if (E.Owner.rules.Count < 1) - E.Origin.Tell("This server has not set any rules."); + await E.Origin.Tell("This server has not set any rules."); else { foreach (String r in E.Owner.rules) - E.Origin.Tell("- " + r); + await E.Origin.Tell("- " + r); } } } @@ -423,12 +444,11 @@ namespace IW4MAdmin { public PrivateMessage(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { - E.Data = SharedLibrary.Utilities.removeWords(E.Data, 1); - E.Target.Alert(); - E.Target.Tell("^1" + E.Origin.Name + " ^3[PM]^7 - " + E.Data); - E.Origin.Tell(String.Format("To ^3{0} ^7-> {1}", E.Target.Name, E.Data)); + E.Data = Utilities.RemoveWords(E.Data, 1); + await E.Target.Tell("^1" + E.Origin.Name + " ^3[PM]^7 - " + E.Data); + await E.Origin.Tell(String.Format("To ^3{0} ^7-> {1}", E.Target.Name, E.Data)); } } @@ -436,12 +456,12 @@ namespace IW4MAdmin { public Reload(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) - { + public override async Task ExecuteAsync(Event E) + { if (E.Owner.Reload()) - E.Origin.Tell("Sucessfully reloaded configs!"); + await E.Origin.Tell("Sucessfully reloaded configs!"); else - E.Origin.Tell("Unable to reload configs :("); + await E.Origin.Tell("Unable to reload configs :("); } } @@ -449,9 +469,9 @@ namespace IW4MAdmin { public Balance(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { - E.Origin.currentServer.executeCommand(String.Format("admin_lastevent {0};{1}", "balance", E.Origin.npID)); //Let gsc do the magic + await E.Owner.ExecuteCommandAsync(String.Format("admin_lastevent {0};{1}", "balance", E.Origin.npID)); //Let gsc do the magic } } @@ -459,9 +479,9 @@ namespace IW4MAdmin { public GoTo(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { - E.Origin.currentServer.executeCommand(String.Format("admin_lastevent {0};{1};{2};{3}", "goto", E.Origin.npID, E.Target.Name, E.Data)); //Let gsc do the magic + await E.Owner.ExecuteCommandAsync(String.Format("admin_lastevent {0};{1};{2};{3}", "goto", E.Origin.npID, E.Target.Name, E.Data)); //Let gsc do the magic } } @@ -469,24 +489,24 @@ namespace IW4MAdmin { public Flag(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { if (E.Target.Level >= E.Origin.Level) { - E.Origin.Tell("You cannot flag " + E.Target.Name); + await E.Origin.Tell("You cannot flag " + E.Target.Name); return; } if (E.Target.Level == Player.Permission.Flagged) { E.Target.setLevel(Player.Permission.User); - E.Origin.Tell("You have ^5unflagged ^7" + E.Target.Name); + await E.Origin.Tell("You have ^5unflagged ^7" + E.Target.Name); } else { E.Target.setLevel(Player.Permission.Flagged); - E.Origin.Tell("You have ^5flagged ^7" + E.Target.Name); + await E.Origin.Tell("You have ^5flagged ^7" + E.Target.Name); } E.Owner.clientDB.updatePlayer(E.Target); @@ -497,35 +517,33 @@ namespace IW4MAdmin { public _Report(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { - if (E.Owner.Reports.Find(x => x.Origin == E.Origin) != null) + if (E.Owner.Reports.Find(x => (x.Origin == E.Origin && x.Target.npID == E.Target.npID)) != null) { - E.Origin.Tell("You have already reported this player"); + await E.Origin.Tell("You have already reported this player"); return; } if (E.Target == E.Origin) { - E.Origin.Tell("You cannot report yourself, silly."); + await E.Origin.Tell("You cannot report yourself, silly."); return; } if (E.Target.Level > E.Origin.Level) { - E.Origin.Tell("You cannot report " + E.Target.Name); + await E.Origin.Tell("You cannot report " + E.Target.Name); return; } - E.Data = SharedLibrary.Utilities.removeWords(E.Data, 1); + E.Data = Utilities.RemoveWords(E.Data, 1); E.Owner.Reports.Add(new Report(E.Target, E.Origin, E.Data)); - Connection Screenshot = new Connection(String.Format("http://server.nbsclan.org/screen.php?id={0}&name={1}?save=1", SharedLibrary.Utilities.getForumIDFromStr(E.Target.npID), E.Origin.Name)); - String Response = Screenshot.Read(); + await E.Origin.Tell("Successfully reported " + E.Target.Name); + await E.Owner.ExecuteEvent(new Event(Event.GType.Report, E.Data, E.Origin, E.Target, E.Owner)); - E.Origin.Tell("Successfully reported " + E.Target.Name); - - E.Owner.ToAdmins(String.Format("^5{0}^7->^1{1}^7: {2}", E.Origin.Name, E.Target.Name, E.Data)); + await E.Owner.ToAdmins(String.Format("^5{0}^7->^1{1}^7: {2}", E.Origin.Name, E.Target.Name, E.Data)); } } @@ -533,29 +551,23 @@ namespace IW4MAdmin { public Reports(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { if (E.Data != null && E.Data.ToLower().Contains("clear")) { E.Owner.Reports = new List(); - E.Origin.Tell("Reports successfully cleared!"); + await E.Origin.Tell("Reports successfully cleared!"); return; } if (E.Owner.Reports.Count < 1) { - E.Origin.Tell("No players reported yet."); + await E.Origin.Tell("No players reported yet."); return; } - int count = E.Owner.Reports.Count - 1; - for (int i = 0; i <= count; i++) - { - if (count > 8) - i = count - 8; - Report R = E.Owner.Reports[i]; - E.Origin.Tell(String.Format("^5{0}^7->^1{1}^7: {2}", R.Origin.Name, R.Target.Name, R.Reason)); - } + foreach (Report R in E.Owner.Reports) + await E.Origin.Tell(String.Format("^5{0}^7->^1{1}^7: {2}", R.Origin.Name, R.Target.Name, R.Reason)); } } @@ -563,10 +575,10 @@ namespace IW4MAdmin { public _Tell(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { - E.Data = SharedLibrary.Utilities.removeWords(E.Data, 1); - E.Origin.currentServer.executeCommand(String.Format("admin_lastevent tell;{0};{1};{2}", E.Origin.npID, E.Target.npID, E.Data)); + E.Data = Utilities.RemoveWords(E.Data, 1); + await E.Owner.ExecuteCommandAsync(String.Format("admin_lastevent tell;{0};{1};{2}", E.Origin.npID, E.Target.npID, E.Data)); } } @@ -574,17 +586,17 @@ namespace IW4MAdmin { public Mask(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { if (E.Origin.Masked) { E.Origin.Masked = false; - E.Origin.Tell("You are now unmasked"); + await E.Origin.Tell("You are now unmasked"); } else { E.Origin.Masked = true; - E.Origin.Tell("You are now masked"); + await E.Origin.Tell("You are now masked"); } } } @@ -593,11 +605,11 @@ namespace IW4MAdmin { public BanInfo(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { if (E.Target == null) { - E.Origin.Tell("No bans for that player."); + await E.Origin.Tell("No bans for that player."); return; } @@ -605,7 +617,7 @@ namespace IW4MAdmin if (B == null) { - E.Origin.Tell("No active ban was found for that player."); + await E.Origin.Tell("No active ban was found for that player."); return; } @@ -613,11 +625,11 @@ namespace IW4MAdmin if (Banner == null) { - E.Origin.Tell("Ban was found for the player, but origin of the ban is unavailable."); + await E.Origin.Tell("Ban was found for the player, but origin of the ban is unavailable."); return; } - E.Origin.Tell(String.Format("^1{0} ^7was banned by ^5{1} ^7for: {2}", E.Target.Name, Banner.Name, B.Reason)); + await E.Origin.Tell(String.Format("^1{0} ^7was banned by ^5{1} ^7for: {2}", E.Target.Name, Banner.Name, B.Reason)); } } @@ -625,17 +637,17 @@ namespace IW4MAdmin { public Alias(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { E.Target.Alias = E.Owner.aliasDB.getPlayer(E.Target.databaseID); if (E.Target.Alias == null) { - E.Target.Tell("Could not find alias info for that player."); + await E.Target.Tell("Could not find alias info for that player."); return; } - E.Target.Tell("[^3" + E.Target.Name + "^7]"); + await E.Target.Tell("[^3" + E.Target.Name + "^7]"); StringBuilder message = new StringBuilder(); List playerAliases = E.Owner.getPlayerAliases(E.Target); @@ -650,7 +662,7 @@ namespace IW4MAdmin message.Append(S + " | "); } } - E.Origin.Tell(message.ToString()); + await E.Origin.Tell(message.ToString()); message = new StringBuilder(); @@ -667,7 +679,7 @@ namespace IW4MAdmin } } - E.Origin.Tell(message.ToString()); + await E.Origin.Tell(message.ToString()); } } } @@ -676,24 +688,33 @@ namespace IW4MAdmin { public _RCON(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { - E.Origin.currentServer.executeCommand(E.Data.Trim()); - E.Origin.Tell("Successfuly sent RCON command!"); + await E.Origin.currentServer.ExecuteCommandAsync(E.Data.Trim()); + await E.Origin.Tell("Successfuly sent RCON command!"); } } - class Plugins : Command + class Link : Command { - public Plugins(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + public Link(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } - public override void Execute(Event E) + public override async Task ExecuteAsync(Event E) { - E.Origin.Tell("^5Loaded Plugins:"); - foreach (Plugin P in PluginImporter.potentialPlugins) + if (E.Data.Contains("show")) { - E.Origin.Tell(String.Format("^3{0} ^7[^3{1}^7] by ^5{2}^7", P.Name, P.Version, P.Author)); + if (E.Origin.UID == null || E.Origin.UID.Length == 0) + await E.Origin.Tell("You have not linked an ID"); + else + await E.Origin.Tell("Your ID is " + E.Origin.UID); } + else if (E.Origin.registerUID(E.Data)) + { + E.Owner.clientDB.updatePlayer(E.Origin); + await E.Origin.Tell("Your ID has been linked"); + } + else + await E.Origin.Tell("That ID is invalid"); } } } diff --git a/SharedLibrary/Database.cs b/SharedLibrary/Database.cs index b631ff8b5..4559a0128 100644 --- a/SharedLibrary/Database.cs +++ b/SharedLibrary/Database.cs @@ -18,11 +18,11 @@ namespace SharedLibrary Con = new SQLiteConnection(DBCon); } - catch(System.DllNotFoundException) + catch(DllNotFoundException) { Console.WriteLine("Fatal Error: could not locate the SQLite DLL(s)!\nEnsure they are located in the 'lib' folder"); Utilities.Wait(5); - System.Environment.Exit(0); + Environment.Exit(0); } Open = false; @@ -33,50 +33,75 @@ namespace SharedLibrary protected bool Insert(String tableName, Dictionary data) { - String columns = ""; - String values = ""; - Boolean returnCode = true; - foreach (KeyValuePair val in data) + string names = ""; + string parameters = ""; + foreach (string key in data.Keys) { - columns += String.Format(" {0},", val.Key); - values += String.Format(" '{0}',", val.Value); + names += key + ','; + parameters += '@' + key + ','; } - columns = columns.Substring(0, columns.Length - 1); - values = values.Substring(0, values.Length - 1); + names = names.Substring(0, names.Length - 1); + parameters = parameters.Substring(0, parameters.Length - 1); + + SQLiteCommand insertcmd = new SQLiteCommand(); + insertcmd.Connection = this.Con; + insertcmd.CommandText = String.Format("INSERT INTO `{0}` ({1}) VALUES ({2});", tableName, names, parameters); + + foreach (string key in data.Keys) + { + insertcmd.Parameters.AddWithValue('@' + key, data[key]); + } + try { - this.ExecuteNonQuery(String.Format("insert into {0}({1}) values({2});", tableName, columns, values)); + Con.Open(); + insertcmd.ExecuteNonQuery(); + Con.Close(); + return true; } - catch (Exception fail) + + catch (Exception) { - Console.WriteLine(fail.Message); - returnCode = false; + //LOGME + return false; } - return returnCode; + } - protected bool Update(String tableName, Dictionary data, String where) + protected bool Update(String tableName, Dictionary data, KeyValuePair where) { - String vals = ""; - Boolean returnCode = true; - if (data.Count >= 1) + string parameters = ""; + foreach (string key in data.Keys) { - foreach (KeyValuePair val in data) - { - vals += String.Format(" {0} = '{1}',", val.Key, val.Value); - } - vals = vals.Substring(0, vals.Length - 1); + parameters += key + '=' + '@' + key + ','; } + + parameters = parameters.Substring(0, parameters.Length - 1); + + SQLiteCommand updatecmd = new SQLiteCommand(); + updatecmd.Connection = this.Con; + updatecmd.CommandText = String.Format("UPDATE `{0}` SET {1} WHERE {2}=@{2}", tableName, parameters, where.Key); + + foreach (string key in data.Keys) + { + updatecmd.Parameters.AddWithValue('@' + key, data[key]); + } + + updatecmd.Parameters.AddWithValue('@' + where.Key, where.Value); + try { - ExecuteNonQuery(String.Format("update {0} set {1} where {2};", tableName, vals, where)); + Con.Open(); + updatecmd.ExecuteNonQuery(); + Con.Close(); + return true; } - catch (Exception fail) + + catch (Exception e) { - Console.WriteLine(fail.Message); - returnCode = false; + //LOGME + return false; } - return returnCode; } protected DataRow getDataRow(String Q) @@ -89,17 +114,53 @@ namespace SharedLibrary { waitForClose(); int rowsUpdated = 0; + Request = Request.Replace("!'", "").Replace("!", "") ; + try + { + lock (Con) + { + Con.Open(); + SQLiteCommand CMD = new SQLiteCommand(Con); + CMD.CommandText = Request; + rowsUpdated = CMD.ExecuteNonQuery(); + Con.Close(); + } + return rowsUpdated; + } - lock (Con) + catch (Exception E) + { + Console.WriteLine(E.Message); + Console.WriteLine(E.StackTrace); + Console.WriteLine(Request); + return 0; + } + } + + protected DataTable GetDataTable(string tableName, KeyValuePair where) + { + DataTable dt = new DataTable(); + SQLiteCommand updatecmd = new SQLiteCommand(); + updatecmd.Connection = this.Con; + updatecmd.CommandText = String.Format("SELECT * FROM {0} WHERE `{1}`=@{1};", tableName, where.Key); + updatecmd.Parameters.AddWithValue('@' + where.Key, where.Value); + + try { Con.Open(); - SQLiteCommand CMD = new SQLiteCommand(Con); - CMD.CommandText = Request; - rowsUpdated = CMD.ExecuteNonQuery(); + SQLiteDataReader reader = updatecmd.ExecuteReader(); + dt.Load(reader); + reader.Close(); Con.Close(); } - return rowsUpdated; + catch (Exception e) + { + //LOGME + Console.Write("Couldnotexecute"); + } + + return dt; } protected DataTable GetDataTable(String sql) @@ -123,7 +184,7 @@ namespace SharedLibrary } catch (Exception e) { - Console.WriteLine(e.Message); + Console.WriteLine(e.Message + " GetDataTable"); return new DataTable(); } return dt; @@ -153,7 +214,7 @@ namespace SharedLibrary { if (!File.Exists(FileName)) { - String Create = "CREATE TABLE [CLIENTS] ( [Name] TEXT NULL, [npID] TEXT NULL, [Number] INTEGER PRIMARY KEY AUTOINCREMENT, [Level] INT DEFAULT 0 NULL, [LastOffense] TEXT NULL, [Connections] INT DEFAULT 1 NULL, [IP] TEXT NULL, [LastConnection] TEXT NULL);"; + String Create = "CREATE TABLE [CLIENTS] ( [Name] TEXT NULL, [npID] TEXT NULL, [Number] INTEGER PRIMARY KEY AUTOINCREMENT, [Level] INT DEFAULT 0 NULL, [LastOffense] TEXT NULL, [Connections] INT DEFAULT 1 NULL, [IP] TEXT NULL, [LastConnection] TEXT NULL, [UID] TEXT NULL, [Masked] INT DEFAULT 0);"; ExecuteNonQuery(Create); Create = "CREATE TABLE [BANS] ( [TYPE] TEXT NULL, [Reason] TEXT NULL, [npID] TEXT NULL, [bannedByID] TEXT NULL, [IP] TEXT NULL, [TIME] TEXT NULL);"; ExecuteNonQuery(Create); @@ -172,13 +233,33 @@ namespace SharedLibrary DateTime lastCon = DateTime.MinValue; DateTime.TryParse(ResponseRow["LastConnection"].ToString(), out lastCon); - return new Player(ResponseRow["Name"].ToString(), ResponseRow["npID"].ToString(), cNum, (Player.Permission)(ResponseRow["Level"]), Convert.ToInt32(ResponseRow["Number"]), ResponseRow["LastOffense"].ToString(), (int)ResponseRow["Connections"], ResponseRow["IP"].ToString(), lastCon); + return new Player(ResponseRow["Name"].ToString(), ResponseRow["npID"].ToString(), cNum, (Player.Permission)(ResponseRow["Level"]), Convert.ToInt32(ResponseRow["Number"]), ResponseRow["LastOffense"].ToString(), (int)ResponseRow["Connections"], ResponseRow["IP"].ToString(), lastCon, ResponseRow["UID"].ToString(), ResponseRow["Masked"].ToString() == "1"); } else return null; } + public List getRecentPlayers() + { + List returnssss = new List(); + String Query = String.Format("SELECT * FROM CLIENTS ORDER BY LastConnection desc LIMIT 25"); + DataTable Result = GetDataTable(Query); + + if (Result != null && Result.Rows.Count > 0) + { + foreach (DataRow ResponseRow in Result.Rows) + { + DateTime lastCon = DateTime.MinValue; + DateTime.TryParse(ResponseRow["LastConnection"].ToString(), out lastCon); + + returnssss.Add(new Player(ResponseRow["Name"].ToString(), ResponseRow["npID"].ToString(), -1, (Player.Permission)(ResponseRow["Level"]), Convert.ToInt32(ResponseRow["Number"]), ResponseRow["LastOffense"].ToString(), (int)ResponseRow["Connections"], ResponseRow["IP"].ToString(), lastCon, ResponseRow["UID"].ToString(), ResponseRow["Masked"].ToString() == "1")); + } + } + + return returnssss; + } + public List getPlayers(List npIDs) { List returnssss = new List(); @@ -194,7 +275,7 @@ namespace SharedLibrary DateTime lastCon = DateTime.MinValue; DateTime.TryParse(ResponseRow["LastConnection"].ToString(), out lastCon); - returnssss.Add(new Player(ResponseRow["Name"].ToString(), ResponseRow["npID"].ToString(), -1, (Player.Permission)(ResponseRow["Level"]), Convert.ToInt32(ResponseRow["Number"]), ResponseRow["LastOffense"].ToString(), (int)ResponseRow["Connections"], ResponseRow["IP"].ToString(), lastCon)); + returnssss.Add(new Player(ResponseRow["Name"].ToString(), ResponseRow["npID"].ToString(), -1, (Player.Permission)(ResponseRow["Level"]), Convert.ToInt32(ResponseRow["Number"]), ResponseRow["LastOffense"].ToString(), (int)ResponseRow["Connections"], ResponseRow["IP"].ToString(), lastCon, ResponseRow["UID"].ToString(), ResponseRow["Masked"].ToString() == "1")); } } @@ -216,7 +297,7 @@ namespace SharedLibrary DateTime lastCon = DateTime.MinValue; DateTime.TryParse(ResponseRow["LastConnection"].ToString(), out lastCon); - returnssss.Add(new Player(ResponseRow["Name"].ToString(), ResponseRow["npID"].ToString(), -1, (Player.Permission)(ResponseRow["Level"]), Convert.ToInt32(ResponseRow["Number"]), ResponseRow["LastOffense"].ToString(), (int)ResponseRow["Connections"], ResponseRow["IP"].ToString(), lastCon)); + returnssss.Add(new Player(ResponseRow["Name"].ToString(), ResponseRow["npID"].ToString(), -1, (Player.Permission)(ResponseRow["Level"]), Convert.ToInt32(ResponseRow["Number"]), ResponseRow["LastOffense"].ToString(), (int)ResponseRow["Connections"], ResponseRow["IP"].ToString(), lastCon, ResponseRow["UID"].ToString(), ResponseRow["Masked"].ToString() == "1")); } } @@ -242,7 +323,7 @@ namespace SharedLibrary LC = DateTime.MinValue; } - return new Player(p["Name"].ToString(), p["npID"].ToString(), -1, (Player.Permission)(p["Level"]), Convert.ToInt32(p["Number"]), p["LastOffense"].ToString(), Convert.ToInt32(p["Connections"]), p["IP"].ToString(), LC); + return new Player(p["Name"].ToString(), p["npID"].ToString(), -1, (Player.Permission)(p["Level"]), Convert.ToInt32(p["Number"]), p["LastOffense"].ToString(), Convert.ToInt32(p["Connections"]), p["IP"].ToString(), LC, p["UID"].ToString(), p["Masked"].ToString() == "1"); } else @@ -264,7 +345,7 @@ namespace SharedLibrary try { LC = DateTime.Parse(p["LastConnection"].ToString()); - lastKnown.Add(new Player(p["Name"].ToString(), p["npID"].ToString(), -1, (Player.Permission)(p["Level"]), Convert.ToInt32(p["Number"]), p["LastOffense"].ToString(), Convert.ToInt32((DateTime.Now - LC).TotalSeconds), p["IP"].ToString(), LC)); + lastKnown.Add(new Player(p["Name"].ToString(), p["npID"].ToString(), -1, (Player.Permission)(p["Level"]), Convert.ToInt32(p["Number"]), p["LastOffense"].ToString(), Convert.ToInt32((DateTime.Now - LC).TotalSeconds), p["IP"].ToString(), LC, p["UID"].ToString(), p["Masked"].ToString() == "1")); } catch (Exception) @@ -301,16 +382,21 @@ namespace SharedLibrary foreach (DataRow p in Result.Rows) { DateTime LC; + string Masked = null; try { LC = DateTime.Parse(p["LastConnection"].ToString()); + Masked = p["Masked"].ToString(); + } catch (Exception) { + if (Masked == null) + Masked = "0"; + LC = DateTime.MinValue; } - - Players.Add(new Player(p["Name"].ToString(), p["npID"].ToString(), -1, (Player.Permission)(p["Level"]), Convert.ToInt32(p["Number"]), p["LastOffense"].ToString(), Convert.ToInt32(p["Connections"]), p["IP"].ToString(), LC)); + Players.Add(new Player(p["Name"].ToString(), p["npID"].ToString(), -1, (Player.Permission)(p["Level"]), Convert.ToInt32(p["Number"]), p["LastOffense"].ToString(), Convert.ToInt32(p["Connections"]), p["IP"].ToString(), LC, p["IP"].ToString(), Masked == "1")); } return Players; } @@ -352,7 +438,7 @@ namespace SharedLibrary if (Row["TYPE"].ToString().Length != 0) BanType = (Penalty.Type)Enum.Parse(typeof(Penalty.Type), Row["TYPE"].ToString()); - Bans.Add(new Penalty(BanType, Row["Reason"].ToString(), Row["npID"].ToString(), Row["bannedByID"].ToString(), DateTime.Parse(Row["TIME"].ToString()), Row["IP"].ToString())); + Bans.Add(new Penalty(BanType, Row["Reason"].ToString().Trim(), Row["npID"].ToString(), Row["bannedByID"].ToString(), DateTime.Parse(Row["TIME"].ToString()), Row["IP"].ToString())); } @@ -363,11 +449,11 @@ namespace SharedLibrary public List getAdmins() { List Admins = new List(); - String Query = String.Format("SELECT * FROM CLIENTS WHERE LEVEL > '{0}'", 1); + String Query = String.Format("SELECT * FROM CLIENTS WHERE Level >= '{0}'", (int)Player.Permission.Moderator); DataTable Result = GetDataTable(Query); foreach (DataRow P in Result.Rows) - Admins.Add(new Player(P["Name"].ToString(), P["npID"].ToString(), (Player.Permission)P["Level"], P["IP"].ToString())); + Admins.Add(new Player(P["Name"].ToString(), P["npID"].ToString(), (Player.Permission)P["Level"], P["IP"].ToString(), P["UID"].ToString())); return Admins; } @@ -394,6 +480,8 @@ namespace SharedLibrary newPlayer.Add("Connections", 1); newPlayer.Add("IP", P.IP); newPlayer.Add("LastConnection", Utilities.DateTimeSQLite(DateTime.Now)); + newPlayer.Add("UID", P.UID); + newPlayer.Add("Masked", Convert.ToInt32(P.Masked)); Insert("CLIENTS", newPlayer); } @@ -410,8 +498,10 @@ namespace SharedLibrary updatedPlayer.Add("Connections", P.Connections); updatedPlayer.Add("IP", P.IP); updatedPlayer.Add("LastConnection", Utilities.DateTimeSQLite(DateTime.Now)); + updatedPlayer.Add("UID", P.UID); + updatedPlayer.Add("Masked", Convert.ToInt32(P.Masked)); - Update("CLIENTS", updatedPlayer, String.Format("npID = '{0}'", P.npID)); + Update("CLIENTS", updatedPlayer, new KeyValuePair("npID", P.npID )); } @@ -420,7 +510,7 @@ namespace SharedLibrary { Dictionary newBan = new Dictionary(); - newBan.Add("Reason", B.Reason); + newBan.Add("Reason", Utilities.removeNastyChars(B.Reason)); newBan.Add("npID", B.npID); newBan.Add("bannedByID", B.bannedByID); newBan.Add("IP", B.IP); @@ -515,7 +605,7 @@ namespace SharedLibrary Dictionary newPlayer = new Dictionary(); newPlayer.Add("Number", Alias.Number); - newPlayer.Add("NAMES", String.Join(";", Alias.Names)); + newPlayer.Add("NAMES", Utilities.removeNastyChars(String.Join(";", Alias.Names))); newPlayer.Add("IPS", String.Join(";", Alias.IPS)); Insert("ALIASES", newPlayer); @@ -529,7 +619,7 @@ namespace SharedLibrary updatedPlayer.Add("NAMES", String.Join(";", Alias.Names)); updatedPlayer.Add("IPS", String.Join(";", Alias.IPS)); - Update("ALIASES", updatedPlayer, String.Format("Number = '{0}'", Alias.Number)); + Update("ALIASES", updatedPlayer, new KeyValuePair("Number", Alias.Number)); } } } diff --git a/SharedLibrary/Dvar.cs b/SharedLibrary/Dvar.cs index 7a33448a8..8cc3da622 100644 --- a/SharedLibrary/Dvar.cs +++ b/SharedLibrary/Dvar.cs @@ -16,4 +16,15 @@ namespace SharedLibrary public int min; public int max; } + + public class _DVAR + { + public string Name { get; private set; } + public T Value; + + public _DVAR(string name) + { + Name = name; + } + } } diff --git a/SharedLibrary/Event.cs b/SharedLibrary/Event.cs index a5adc9a35..802399455 100644 --- a/SharedLibrary/Event.cs +++ b/SharedLibrary/Event.cs @@ -5,13 +5,15 @@ using System.Text.RegularExpressions; namespace SharedLibrary { + [Serializable] public class Chat { - public Chat(Player O, String M, DateTime D) + public Chat(string O, String M, DateTime D) { - Origin = O; + Name = O; Message = M; Time = D; + } public String timeString() @@ -19,11 +21,49 @@ namespace SharedLibrary return Time.ToShortTimeString(); } - public Player Origin { get; private set; } + //public Player Origin { get; private set; } public String Message { get; private set; } public DateTime Time { get; private set; } + public string Name; } + [Serializable] + public struct RestEvent + { + public RestEvent(eType Ty, eVersion V, string M, string T, string O, string Ta) + { + Type = Ty; + Version = V; + Message = M; + Title = T; + Origin = O; + Target = Ta; + + ID = Math.Abs(DateTime.Now.GetHashCode()); + } + + public enum eType + { + NOTIFICATION, + STATUS, + ALERT, + } + + public enum eVersion + { + IW4MAdmin + } + + public eType Type; + public eVersion Version; + public string Message; + public string Title; + public string Origin; + public string Target; + public int ID; + } + + public class Event { public enum GType @@ -44,7 +84,11 @@ namespace SharedLibrary Tell, Kick, Ban, + Remote, Unknown, + + //FROM PLAYER + Report } public Event(GType t, string d, Player O, Player T, Server S) @@ -100,7 +144,7 @@ namespace SharedLibrary if (eventType == ":") return new Event(GType.MapEnd, line[0], new Player("WORLD", "WORLD", 0, 0), null, SV); - if (line[0].Split('\\').Length > 5) // blaze it + if (line[0].Contains("InitGame")) // blaze it return new Event(GType.MapChange, line[0], new Player("WORLD", "WORLD", 0, 0), null, SV); @@ -121,5 +165,6 @@ namespace SharedLibrary public Player Origin; public Player Target; public Server Owner; + public Boolean Remote = false; } } diff --git a/SharedLibrary/Exceptions/CommandException.cs b/SharedLibrary/Exceptions/CommandException.cs new file mode 100644 index 000000000..b56b9df71 --- /dev/null +++ b/SharedLibrary/Exceptions/CommandException.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharedLibrary.Exceptions +{ + public class CommandException : ServerException + { + public CommandException(string msg) : base(msg) { } + // .data contains + // "command_name" + } +} diff --git a/SharedLibrary/Exceptions/DvarException.cs b/SharedLibrary/Exceptions/DvarException.cs new file mode 100644 index 000000000..d1556716b --- /dev/null +++ b/SharedLibrary/Exceptions/DvarException.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharedLibrary.Exceptions +{ + public class DvarException : ServerException + { + public DvarException(string msg) : base(msg) { } + } +} diff --git a/SharedLibrary/Exceptions/NetworkException.cs b/SharedLibrary/Exceptions/NetworkException.cs new file mode 100644 index 000000000..79292471e --- /dev/null +++ b/SharedLibrary/Exceptions/NetworkException.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharedLibrary.Exceptions +{ + public class NetworkException : ServerException + { + public NetworkException(string msg) : base(msg) { } + } +} diff --git a/SharedLibrary/Exceptions/ServerException.cs b/SharedLibrary/Exceptions/ServerException.cs new file mode 100644 index 000000000..17c8363c2 --- /dev/null +++ b/SharedLibrary/Exceptions/ServerException.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharedLibrary.Exceptions +{ + public class ServerException : Exception + { + public ServerException(string msg) : base(msg) { } + } +} diff --git a/SharedLibrary/Extensions/IPlugin.cs b/SharedLibrary/Extensions/IPlugin.cs new file mode 100644 index 000000000..d732053ad --- /dev/null +++ b/SharedLibrary/Extensions/IPlugin.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading.Tasks; + +namespace SharedLibrary.Extensions +{ + public interface IPlugin + { + Task OnLoad(); + Task OnUnload(); + Task OnEvent(Event E, Server S); + Task OnTick(Server S); + + //for logging purposes + String Name { get; } + float Version { get; } + String Author { get; } + } +} diff --git a/SharedLibrary/File.cs b/SharedLibrary/File.cs index 24dbc1208..0aab1f53e 100644 --- a/SharedLibrary/File.cs +++ b/SharedLibrary/File.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using System.IO; +using System.Net; namespace SharedLibrary { @@ -10,18 +11,24 @@ namespace SharedLibrary public IFile(String fileName) { //Not safe for directories with more than one folder but meh - _Directory = fileName.Split('\\')[0]; - Name = (fileName.Split('\\'))[fileName.Split('\\').Length - 1]; + string[] asd = fileName.Split('/'); - if (!Directory.Exists(_Directory)) - Directory.CreateDirectory(_Directory); + if (asd[0] != "") + _Directory = asd[0]; + else + _Directory = asd[2]; + + Name = (fileName.Split('/'))[fileName.Split('/').Length - 1]; + + //if (!Directory.Exists(_Directory)) + // Directory.CreateDirectory(_Directory); if (!File.Exists(fileName)) { try { - FileStream penis = File.Create(fileName); - penis.Close(); + //FileStream penis = File.Create(fileName); + //penis.Close(); } catch @@ -49,6 +56,16 @@ namespace SharedLibrary sze = 0; } + public IFile() + { + WebClient request = new WebClient(); + string url = $"http://raidmax.org/logs/IW4X/games_mp.log"; + byte[] newFileData = request.DownloadData(url); + + Handle = new StreamReader(new MemoryStream(newFileData)); + sze = Handle.BaseStream.Length; + } + public long getSize() { sze = Handle.BaseStream.Length; @@ -59,8 +76,16 @@ namespace SharedLibrary { if (writeHandle != null) { - writeHandle.WriteLine(line); - writeHandle.Flush(); + try + { + writeHandle.WriteLine(line); + writeHandle.Flush(); + } + + catch (Exception E) + { + Console.WriteLine("Error during flush", E.Message); + } } } diff --git a/SharedLibrary/Interfaces/IManager.cs b/SharedLibrary/Interfaces/IManager.cs new file mode 100644 index 000000000..6a3b4e46f --- /dev/null +++ b/SharedLibrary/Interfaces/IManager.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharedLibrary.Interfaces +{ + public interface IManager + { + void Init(); + void Start(); + void Stop(); + List GetServers(); + List GetCommands(); + } +} diff --git a/SharedLibrary/Interfaces/ISerializable.cs b/SharedLibrary/Interfaces/ISerializable.cs new file mode 100644 index 000000000..c3fd65a31 --- /dev/null +++ b/SharedLibrary/Interfaces/ISerializable.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; + +namespace SharedLibrary.Interfaces +{ + interface ISerializable + { + void Write(); + } + + public class SerializeException : Exception + { + public SerializeException(string msg) : base(msg) { } + } + + public class Serialize : ISerializable + { + public static T Read(string filename) + { + try + { + string configText = File.ReadAllText(filename); + return Newtonsoft.Json.JsonConvert.DeserializeObject(configText); + } + + catch (Exception e) + { + throw new SerializeException($"Could not desialize file {filename}: {e.Message}"); + } + } + + public void Write() + { + try + { + string configText = Newtonsoft.Json.JsonConvert.SerializeObject(this); + File.WriteAllText(Filename(), configText); + } + + catch (Exception e) + { + throw new SerializeException($"Could not serialize file {Filename()}: {e.Message}"); + } + } + + public virtual string Filename() { return this.ToString(); } + } +} diff --git a/SharedLibrary/Map.cs b/SharedLibrary/Map.cs index 4b588691d..de4a65474 100644 --- a/SharedLibrary/Map.cs +++ b/SharedLibrary/Map.cs @@ -15,5 +15,10 @@ namespace SharedLibrary public String Name { get; private set; } public String Alias { get; private set; } + + public override string ToString() + { + return Alias; + } } } diff --git a/SharedLibrary/Ban.cs b/SharedLibrary/Penalty.cs similarity index 95% rename from SharedLibrary/Ban.cs rename to SharedLibrary/Penalty.cs index fc7af8fcf..ac5b1a33f 100644 --- a/SharedLibrary/Ban.cs +++ b/SharedLibrary/Penalty.cs @@ -9,7 +9,7 @@ namespace SharedLibrary { public Penalty(Type BType, String Reas, String TargID, String From, DateTime time, String ip) { - Reason = Reas; + Reason = Reas.Replace("!",""); npID = TargID; bannedByID = From; When = time; diff --git a/SharedLibrary/Player.cs b/SharedLibrary/Player.cs index e2d2681aa..f6c7a82c8 100644 --- a/SharedLibrary/Player.cs +++ b/SharedLibrary/Player.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using System.Linq; +using System.Threading.Tasks; namespace SharedLibrary { @@ -35,6 +36,11 @@ namespace SharedLibrary Console = 8, } + public override bool Equals(object obj) + { + return ((Player)obj).npID == this.npID; + } + public Player(string n, string id, int num, int l) { Name = n; @@ -59,12 +65,14 @@ namespace SharedLibrary LastConnection = DateTime.Now; } - public Player(string n, string id, Player.Permission P, String I) + public Player(String n, String id, Player.Permission P, String I, String UID) { Name = n; npID = id; Level = P; IP = I; + clientID = -1; + this.UID = UID; } public Player(string n, string id, int num, Player.Permission l, int cind, String lo, int con, String IP2) @@ -85,7 +93,7 @@ namespace SharedLibrary LastConnection = DateTime.Now; } - public Player(string n, string id, int num, Player.Permission l, int cind, String lo, int con, String IP2, DateTime LC) + public Player(string n, string id, int num, Player.Permission l, int cind, String lo, int con, String IP2, DateTime LC, string UID, bool masked) { Name = n; npID = id; @@ -101,6 +109,16 @@ namespace SharedLibrary Warnings = 0; Masked = false; LastConnection = LC; + this.UID = UID.Trim(); + Masked = masked; + } + + public bool registerUID(String UID) + { + if (UID.Length > 5) + this.UID = UID; + + return this.UID == UID; } public String getLastConnection() @@ -124,34 +142,29 @@ namespace SharedLibrary Level = Perm; } - public void Tell(String Message) + public async Task Tell(String Message) { - lastEvent.Owner.Tell(Message, this); + await lastEvent.Owner.Tell(Message, this); } - public void Kick(String Message, Player Sender) + public async Task Kick(String Message, Player Sender) { - lastEvent.Owner.Kick(Message, this, Sender); + await lastEvent.Owner.Kick(Message, this, Sender); } - public void tempBan(String Message, Player Sender) + public async Task TempBan(String Message, Player Sender) { - lastEvent.Owner.tempBan(Message, this, Sender); + await lastEvent.Owner.TempBan(Message, this, Sender); } - public void Warn(String Message, Player Sender) + public async Task Warn(String Message, Player Sender) { - lastEvent.Owner.Warn(Message, this, Sender); + await lastEvent.Owner.Warn(Message, this, Sender); } - public void Ban(String Message, Player Sender) + public async Task Ban(String Message, Player Sender) { - lastEvent.Owner.Ban(Message, this, Sender); - } - - public void Alert() - { - lastEvent.Owner.Alert(this); + await lastEvent.Owner.Ban(Message, this, Sender); } public String Name { get; private set; } @@ -161,6 +174,7 @@ namespace SharedLibrary public int databaseID { get; private set; } public int Connections { get; set; } public String IP { get; private set; } + public String UID { get; private set; } public DateTime LastConnection { get; private set; } public Server currentServer { get; private set; } @@ -169,5 +183,6 @@ namespace SharedLibrary public int Warnings; public Aliases Alias; public bool Masked; + public int selectedServer; } } diff --git a/SharedLibrary/Plugin.cs b/SharedLibrary/Plugin.cs deleted file mode 100644 index cdf7640e6..000000000 --- a/SharedLibrary/Plugin.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace SharedLibrary -{ - public abstract class Plugin - { - public abstract void onLoad(); - public abstract void onUnload(); - public abstract void onEvent(Event E); - - //for logging purposes - public abstract String Name { get; } - public abstract float Version { get; } - public abstract String Author { get; } - } -} diff --git a/SharedLibrary/RCON.cs b/SharedLibrary/RCON.cs new file mode 100644 index 000000000..9589aec96 --- /dev/null +++ b/SharedLibrary/RCON.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using System.Text.RegularExpressions; +using System.Net.Sockets; + +namespace SharedLibrary.Network +{ + public static class RCON + { + enum QueryType + { + GET_STATUS, + GET_INFO, + DVAR, + COMMAND, + } + + public static List PlayersFromStatus(String[] Status) + { + List StatusPlayers = new List(); + + foreach (String S in Status) + { + String responseLine = S.Trim(); + + if (Regex.Matches(responseLine, @"\d+$", RegexOptions.IgnoreCase).Count > 0 && responseLine.Length > 72) // its a client line! + { + String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + int cID = -1; + String cName = Utilities.StripColors(responseLine.Substring(46, 18)).Trim(); + String npID = responseLine.Substring(29, 17).Trim(); // DONT TOUCH PLZ + int.TryParse(playerInfo[0], out cID); + String cIP = responseLine.Substring(72, 20).Trim().Split(':')[0]; + + Player P = new Player(cName, npID, cID, cIP); + StatusPlayers.Add(P); + } + } + + return StatusPlayers; + } + + static string[] SendQuery(QueryType Type, Server QueryServer, string Parameters = "") + { + var ServerOOBConnection = new System.Net.Sockets.UdpClient(); + ServerOOBConnection.Client.SendTimeout = 1000; + ServerOOBConnection.Client.ReceiveTimeout = 1000; + var Endpoint = new IPEndPoint(IPAddress.Parse(QueryServer.getIP()), QueryServer.getPort()); + + string QueryString = String.Empty; + + switch (Type) + { + case QueryType.DVAR: + case QueryType.COMMAND: + QueryString = $"ÿÿÿÿrcon {QueryServer.Password} {Parameters}"; + break; + case QueryType.GET_STATUS: + QueryString = "ÿÿÿÿ getstatus"; + break; + } + + byte[] Payload = GetRequestBytes(QueryString); + + int attempts = 0; + retry: + + try + { + + ServerOOBConnection.Connect(Endpoint); + ServerOOBConnection.Send(Payload, Payload.Length); + + byte[] ReceiveBuffer = new byte[8192]; + StringBuilder QueryResponseString = new StringBuilder(); + + do + { + ReceiveBuffer = ServerOOBConnection.Receive(ref Endpoint); + QueryResponseString.Append(Encoding.ASCII.GetString(ReceiveBuffer).TrimEnd('\0')); + } while (ServerOOBConnection.Available > 0); + + ServerOOBConnection.Close(); + + if (QueryResponseString.ToString().Contains("Invalid password")) + throw new Exceptions.NetworkException("RCON password is invalid"); + + int num = int.Parse("0a", System.Globalization.NumberStyles.AllowHexSpecifier); + string[] SplitResponse = QueryResponseString.ToString().Split(new char[] { (char)num }, StringSplitOptions.RemoveEmptyEntries); + return SplitResponse; + } + + catch (SocketException) + { + attempts++; + if (attempts > 5) + { + var e = new Exceptions.NetworkException("Cannot communicate with server"); + e.Data["server_address"] = ServerOOBConnection.Client.RemoteEndPoint.ToString(); + ServerOOBConnection.Close(); + throw e; + } + + Thread.Sleep(1000); + goto retry; + } + } + + public static async Task<_DVAR> GetDvarAsync(this Server server, string dvarName) + { + string[] LineSplit = await Task.FromResult(SendQuery(QueryType.DVAR, server, dvarName)); + + if (LineSplit.Length != 3) + { + var e = new Exceptions.DvarException("DVAR does not exist"); + e.Data["dvar_name"] = dvarName; + throw e; + } + + string[] ValueSplit = LineSplit[1].Split(new char[] { '"' }, StringSplitOptions.RemoveEmptyEntries); + + if (ValueSplit.Length != 5) + { + var e = new Exceptions.DvarException("DVAR does not exist"); + e.Data["dvar_name"] = dvarName; + throw e; + } + + string DvarName = Regex.Replace(ValueSplit[0], @"\^[0-9]", ""); + string DvarCurrentValue = Regex.Replace(ValueSplit[2], @"\^[0-9]", ""); + string DvarDefaultValue = Regex.Replace(ValueSplit[4], @"\^[0-9]", ""); + + return new _DVAR(DvarName) { Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T)) }; + } + + public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue) + { + await Task.FromResult(SendQuery(QueryType.DVAR, server, $"{dvarName} {dvarValue}")); + } + + public static async Task ExecuteCommandAsync(this Server server, string commandName) + { + await Task.FromResult(SendQuery(QueryType.COMMAND, server, commandName)); + } + + public static async Task> GetStatusAsync(this Server server) + { + string[] response = await Task.FromResult(SendQuery(QueryType.DVAR, server, "status")); + return PlayersFromStatus(response); + } + + + static byte[] GetRequestBytes(string Request) + { + + Byte[] initialRequestBytes = Encoding.Unicode.GetBytes(Request); + Byte[] fixedRequest = new Byte[initialRequestBytes.Length / 2]; + + for (int i = 0; i < initialRequestBytes.Length; i++) + if (initialRequestBytes[i] != 0) + fixedRequest[i / 2] = initialRequestBytes[i]; + + return fixedRequest; + + } + } +} diff --git a/SharedLibrary/Server.cs b/SharedLibrary/Server.cs index 86a030987..ae63520db 100644 --- a/SharedLibrary/Server.cs +++ b/SharedLibrary/Server.cs @@ -1,30 +1,37 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Text; +using System.Threading; + +using SharedLibrary.Network; +using SharedLibrary.Commands; +using System.Threading.Tasks; namespace SharedLibrary { + [Guid("61d3829e-fcbe-44d3-bb7c-51db8c2d7ac5")] public abstract class Server { - public Server(string address, int port, string password, int H, int PID) + public Server(Interfaces.IManager mgr, string address, int port, string password) { - this.PID = PID; - Handle = H; + Password = password; IP = address; Port = port; - clientnum = 0; - logFile = new IFile("admin_" + port + ".log", true); + Manager = mgr; + ClientNum = 0; + logFile = new IFile($"Logs/{address}_{port}.log", true); #if DEBUG Log = new Log(logFile, Log.Level.Debug, port); #else Log = new Log(logFile, Log.Level.Production, port); #endif - clientDB = new ClientsDB("clients.rm"); - aliasDB = new AliasesDB("aliases.rm"); + clientDB = new ClientsDB("Database/clients.rm"); + aliasDB = new AliasesDB("Database/aliases.rm"); Bans = new List(); - players = new List(new Player[18]); + Players = new List(new Player[18]); events = new Queue(); Macros = new Dictionary(); Reports = new List(); @@ -33,22 +40,54 @@ namespace SharedLibrary chatHistory = new List(); lastWebChat = DateTime.Now; nextMessage = 0; - initCommands(); initMacros(); initMessages(); initMaps(); initRules(); + + var commands = mgr.GetCommands(); + + owner = clientDB.getOwner(); + + if (owner == null) + commands.Add(new Owner("owner", "claim ownership of the server", "owner", Player.Permission.User, 0, false)); + + commands.Add(new Quit("quit", "quit IW4MAdmin", "q", Player.Permission.Owner, 0, false)); + commands.Add(new Kick("kick", "kick a player by name. syntax: !kick .", "k", Player.Permission.Trusted, 2, true)); + commands.Add(new Say("say", "broadcast message to all players. syntax: !say .", "s", Player.Permission.Moderator, 1, false)); + commands.Add(new TempBan("tempban", "temporarily ban a player for 1 hour. syntax: !tempban .", "tb", Player.Permission.Moderator, 2, true)); + commands.Add(new SBan("ban", "permanently ban a player from the server. syntax: !ban ", "b", Player.Permission.SeniorAdmin, 2, true)); + commands.Add(new WhoAmI("whoami", "give information about yourself. syntax: !whoami.", "who", Player.Permission.User, 0, false)); + commands.Add(new List("list", "list active clients syntax: !list.", "l", Player.Permission.Moderator, 0, false)); + commands.Add(new Help("help", "list all available commands. syntax: !help.", "h", Player.Permission.User, 0, false)); + commands.Add(new FastRestart("fastrestart", "fast restart current map. syntax: !fastrestart.", "fr", Player.Permission.Moderator, 0, false)); + commands.Add(new MapRotate("maprotate", "cycle to the next map in rotation. syntax: !maprotate.", "mr", Player.Permission.Administrator, 0, false)); + commands.Add(new SetLevel("setlevel", "set player to specified administration level. syntax: !setlevel .", "sl", Player.Permission.Owner, 2, true)); + commands.Add(new Usage("usage", "get current application memory usage. syntax: !usage.", "us", Player.Permission.Moderator, 0, false)); + commands.Add(new Uptime("uptime", "get current application running time. syntax: !uptime.", "up", Player.Permission.Moderator, 0, false)); + commands.Add(new Warn("warn", "warn player for infringing rules syntax: !warn .", "w", Player.Permission.Trusted, 2, true)); + commands.Add(new WarnClear("warnclear", "remove all warning for a player syntax: !warnclear .", "wc", Player.Permission.Trusted, 1, true)); + commands.Add(new Unban("unban", "unban player by database id. syntax: !unban @.", "ub", Player.Permission.SeniorAdmin, 1, true)); + commands.Add(new Admins("admins", "list currently connected admins. syntax: !admins.", "a", Player.Permission.User, 0, false)); + commands.Add(new MapCMD("map", "change to specified map. syntax: !map", "m", Player.Permission.Administrator, 1, false)); + commands.Add(new Find("find", "find player in database. syntax: !find ", "f", Player.Permission.SeniorAdmin, 1, false)); + commands.Add(new Rules("rules", "list server rules. syntax: !rules", "r", Player.Permission.User, 0, false)); + commands.Add(new PrivateMessage("privatemessage", "send message to other player. syntax: !pm ", "pm", Player.Permission.User, 2, true)); + commands.Add(new Flag("flag", "flag a suspicious player and announce to admins on join . syntax !flag :", "flag", Player.Permission.Moderator, 1, true)); + commands.Add(new _Report("report", "report a player for suspicious behaivor. syntax !report ", "rep", Player.Permission.User, 2, true)); + commands.Add(new Reports("reports", "get most recent reports. syntax !reports", "reports", Player.Permission.Moderator, 0, false)); + commands.Add(new _Tell("tell", "send onscreen message to player. syntax !tell ", "t", Player.Permission.Moderator, 2, true)); + commands.Add(new Mask("mask", "hide your online presence from online admin list. syntax: !mask", "mask", Player.Permission.Administrator, 0, false)); + commands.Add(new BanInfo("baninfo", "get information about a ban for a player. syntax: !baninfo ", "bi", Player.Permission.Moderator, 1, true)); + commands.Add(new Alias("alias", "get past aliases and ips of a player. syntax: !alias ", "known", Player.Permission.Moderator, 1, true)); + commands.Add(new _RCON("rcon", "send rcon command to server. syntax: !rcon ", "rcon", Player.Permission.Owner, 1, false)); + commands.Add(new FindAll("findall", "find a player by their aliase(s). syntax: !findall ", "fa", Player.Permission.Moderator, 1, false)); } //Returns the current server name -- *STRING* public String getName() { - return hostname; - } - - public String getMap() - { - return mapname; + return Hostname; } public String getGametype() @@ -71,29 +110,13 @@ namespace SharedLibrary //Returns number of active clients on server -- *INT* public int getNumPlayers() { - return clientnum; - } - - //Returns the list of commands - public List getCommands() - { - return commands; + return ClientNum; } //Returns list of all current players public List getPlayers() { - return players; - } - - public int getClientNum() - { - return clientnum; - } - - public int getMaxClients() - { - return maxClients; + return Players.FindAll(x => x != null); } //Returns list of all active bans (loaded at runtime) @@ -123,21 +146,19 @@ namespace SharedLibrary return clientDB.getPlayers(databaseIDs); } - abstract public void Stop(); - /// /// Add a player to the server's player list /// /// Player pulled from memory reading /// True if player added sucessfully, false otherwise - abstract public bool addPlayer(Player P); + abstract public Task AddPlayer(Player P); /// /// Remove player by client number /// /// Client ID of player to be removed /// true if removal succeded, false otherwise - abstract public bool removePlayer(int cNum); + abstract public Task RemovePlayer(int cNum); /// /// Get the player from the server's list by line from game long @@ -154,9 +175,9 @@ namespace SharedLibrary /// Matching player if found public Player clientFromName(String pName) { - lock (players) + lock (Players) { - foreach (var P in players) + foreach (var P in Players) { if (P != null && P.Name.ToLower().Contains(pName.ToLower())) return P; @@ -179,123 +200,93 @@ namespace SharedLibrary /// Event parameter /// Command requested from the event /// - abstract public Command processCommand(Event E, Command C); + abstract public Task ProcessCommand(Event E, Command C); - /// - /// Execute a command on the server - /// - /// Command to execute - abstract public void executeCommand(String CMD); - - /// - /// Retrieve a Dvar from the server - /// - /// Name of Dvar to retrieve - /// Dvar if found - abstract public dvar getDvar(String DvarName); - - /// - /// Set a Dvar on the server - /// - /// Name of the - /// - abstract public void setDvar(String Dvar, String Value); - - /// - /// Main loop for the monitoring processes of the server ( handles events and connects/disconnects ) - /// - abstract public void Monitor(); + virtual public Task ProcessUpdatesAsync() + { + return null; + } /// /// Set up the basic variables ( base path / hostname / etc ) that allow the monitor thread to work /// /// True if no issues initializing, false otherwise - abstract public bool intializeBasics(); + //abstract public bool intializeBasics(); /// /// Process any server event /// /// Event /// True on sucess - abstract public bool processEvent(Event E); + abstract protected Task ProcessEvent(Event E); + abstract public Task ExecuteEvent(Event E); /// /// Reloads all the server configurations /// /// True on sucess abstract public bool Reload(); + abstract public bool _Reload(); /// /// Send a message to all players /// /// Message to be sent to all players - public void Broadcast(String Message) + public async Task Broadcast(String Message) { - executeCommand("sayraw " + Message); + await this.ExecuteCommandAsync($"sayraw {Message}"); } - + /// /// Send a message to a particular players /// /// Message to send /// Player to send message to - public void Tell(String Message, Player Target) + public async Task Tell(String Message, Player Target) { - if (Target.clientID > -1 && Message.Length > 0) - executeCommand("tellraw " + Target.clientID + " " + Message + "^7"); + if (Target.clientID > -1 && Message.Length > 0 && Target.Level != Player.Permission.Console && !Target.lastEvent.Remote) + await this.ExecuteCommandAsync($"tellraw {Target.clientID} {Message}^7"); if (Target.Level == Player.Permission.Console) { Console.ForegroundColor = ConsoleColor.Cyan; - Console.WriteLine(Utilities.stripColors(Message)); + Console.WriteLine(Utilities.StripColors(Message)); Console.ForegroundColor = ConsoleColor.Gray; } + + if (Target.lastEvent.Remote) + commandResult.Enqueue(Utilities.StripColors(Message)); } /// /// Send a message to all admins on the server /// /// Message to send out - public void ToAdmins(String message) + public async Task ToAdmins(String message) { - lock (players) // threading can modify list while we do this + foreach (Player P in Players) { - foreach (Player P in players) - { - if (P == null) - continue; + if (P == null) + continue; - if (P.Level > Player.Permission.Flagged) - { - P.Alert(); - P.Tell(message); - } - } + if (P.Level > Player.Permission.Flagged) + await P.Tell(message); } } - /// - /// Alert a player via gsc implementation - /// - /// - public void Alert(Player P) - { - executeCommand("admin_lastevent alert;" + P.npID + ";0;mp_killstreak_nuclearstrike"); - } - /// /// Kick a player from the server /// /// Reason for kicking /// Player to kick - abstract public void Kick(String Reason, Player Target, Player Origin); + abstract public Task Kick(String Reason, Player Target, Player Origin); /// /// Temporarily ban a player ( default 1 hour ) from the server /// /// Reason for banning the player /// The player to ban - abstract public void tempBan(String Reason, Player Target, Player Origin); + abstract public Task TempBan(String Reason, Player Target, Player Origin); /// /// Perm ban a player from the server @@ -303,9 +294,9 @@ namespace SharedLibrary /// The reason for the ban /// The person to ban /// The person who banned the target - abstract public void Ban(String Reason, Player Target, Player Origin); + abstract public Task Ban(String Reason, Player Target, Player Origin); - abstract public void Warn(String Reason, Player Target, Player Origin); + abstract public Task Warn(String Reason, Player Target, Player Origin); /// /// Unban a player by npID / GUID @@ -313,43 +304,20 @@ namespace SharedLibrary /// npID of the player /// I don't remember what this is for /// - abstract public bool Unban(String npID, Player Target); - - /// - /// Fast restart the server with a specified delay - /// - /// - public void fastRestart(int delay) - { - Utilities.Wait(delay); - executeCommand("fast_restart"); - } - - /// - /// Rotate the server to the next map with specified delay - /// - /// - public void mapRotate(int delay) - { - Utilities.Wait(delay); - executeCommand("map_rotate"); - } - - /// - /// Map rotate without delay - /// - public void mapRotate() - { - mapRotate(0); - } + abstract public Task Unban(String npID, Player Target); /// /// Change the current searver map /// /// Non-localized map name - public void Map(String mapName) + public async Task LoadMap(string mapName) { - executeCommand("map " + mapName); + await this.ExecuteCommandAsync($"map {mapName}"); + } + + public async Task LoadMap(Map newMap) + { + await this.ExecuteCommandAsync($"map {newMap.Name}"); } public void webChat(Player P, String Message) @@ -359,13 +327,13 @@ namespace SharedLibrary if ((requestTime - lastWebChat).TotalSeconds > 1) { Broadcast("^1[WEBCHAT] ^5" + P.Name + "^7 - " + Message); - while (chatHistory.Count > Math.Ceiling((double)clientnum / 2)) + while (chatHistory.Count > Math.Ceiling((double)ClientNum / 2)) chatHistory.RemoveAt(0); if (Message.Length > 50) Message = Message.Substring(0, 50) + "..."; - chatHistory.Add(new Chat(P, Utilities.stripColors(Message), DateTime.Now)); + chatHistory.Add(new Chat(P.Name, Utilities.StripColors(Message), DateTime.Now)); lastWebChat = DateTime.Now; } } @@ -382,7 +350,7 @@ namespace SharedLibrary { maps = new List(); - IFile mapfile = new IFile("config\\maps.cfg"); + IFile mapfile = new IFile("config/maps.cfg"); String[] _maps = mapfile.readAll(); mapfile.Close(); if (_maps.Length > 2) // readAll returns minimum one empty string @@ -408,7 +376,7 @@ namespace SharedLibrary { messages = new List(); - IFile messageCFG = new IFile("config\\messages.cfg"); + IFile messageCFG = new IFile("config/messages.cfg"); String[] lines = messageCFG.readAll(); messageCFG.Close(); @@ -445,7 +413,7 @@ namespace SharedLibrary { rules = new List(); - IFile ruleFile = new IFile("config\\rules.cfg"); + IFile ruleFile = new IFile("config/rules.cfg"); String[] _rules = ruleFile.readAll(); ruleFile.Close(); if (_rules.Length > 2) // readAll returns minimum one empty string @@ -468,6 +436,7 @@ namespace SharedLibrary abstract public void initCommands(); //Objects + public Interfaces.IManager Manager { get; protected set; } public Log Log { get; private set; } public List Bans; public Player owner; @@ -484,21 +453,22 @@ namespace SharedLibrary //Info protected String IP; protected int Port; - protected String hostname; - protected String mapname; - protected int clientnum; - protected List players; - protected List commands; + public String Hostname { get; protected set; } + public Map CurrentMap { get; protected set; } + protected string FSGame; + public int ClientNum { get; protected set; } + public int MaxClients { get; protected set; } + public List Players { get; protected set; } protected List messages; protected int messageTime; protected TimeSpan lastMessage; protected DateTime lastPoll; protected int nextMessage; - protected String IW_Ver; - protected int maxClients; + protected Dictionary Macros; protected DateTime lastWebChat; - protected int Handle; + public string Password { get; private set; } + public int Handle { get; private set; } protected int PID; protected IFile logFile; @@ -507,12 +477,13 @@ namespace SharedLibrary public bool isRunning; // Log stuff - protected String Basepath; protected String Mod; - protected String logPath; // Databases public ClientsDB clientDB; public AliasesDB aliasDB; + + //Remote + public Queue commandResult = new Queue(); } } diff --git a/SharedLibrary/SharedLibrary.csproj b/SharedLibrary/SharedLibrary.csproj index ac3569b5b..9ab076a7b 100644 --- a/SharedLibrary/SharedLibrary.csproj +++ b/SharedLibrary/SharedLibrary.csproj @@ -9,8 +9,9 @@ Properties SharedLibrary SharedLibrary - v4.0 + v4.5 512 + AnyCPU @@ -21,6 +22,7 @@ DEBUG;TRACE prompt 4 + false AnyCPU @@ -30,11 +32,16 @@ TRACE prompt 4 + false + false + + ..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll + @@ -47,7 +54,14 @@ - + + + + + + + + @@ -57,15 +71,25 @@ - + + + + + + - copy /Y "$(TargetDir)$(TargetFileName)" "$(SolutionDir)Admin\lib\SharedLibrary.dll" + mkdir "$(SolutionDir)Admin\$(OutDir)plugins +copy /Y "$(TargetDir)$(TargetName).dll" "$(SolutionDir)BUILD\lib" +copy /Y "$(TargetDir)$(TargetName).dll" "$(SolutionDir)Admin\lib" +copy /Y "$(TargetDir)System.Data.SQLite.dll" "$(SolutionDir)BUILD\lib" +copy /Y "$(TargetDir)Newtonsoft.Json.dll" "$(SolutionDir)BUILD\lib" +copy /Y "$(TargetDir)Newtonsoft.Json.dll" "$(SolutionDir)Admin\lib" + \ No newline at end of file diff --git a/Webfront Plugin/Framework.cs b/Webfront Plugin/Framework.cs deleted file mode 100644 index c2be811b8..000000000 --- a/Webfront Plugin/Framework.cs +++ /dev/null @@ -1,620 +0,0 @@ -using System; -using System.Collections.Generic; -using SharedLibrary; -using System.Text; -using System.Text.RegularExpressions; -using System.Linq; -using System.Collections.Specialized; - -namespace Webfront_Plugin -{ - class Framework - { - private List activeServers; - - public Framework() - { - activeServers = new List(); - } - - public void addServer(Server S) - { - activeServers.Add(S); - } - - public void removeServer(Server S) - { - if (S != null && activeServers.Contains(S)) - { - S.Stop(); - activeServers.Remove(S); - } - } - - public List getServers() - { - return activeServers; - } - - private String processTemplate(String Input, String Param) - { - try - { - Server requestedServer = null; - int requestPageNum = 0; - int ID = 0; - String Query = ""; - - if (Param != null) - { - NameValueCollection querySet = System.Web.HttpUtility.ParseQueryString(Param); - - if (querySet["server"] != null) - requestedServer = activeServers.Find(x => x.pID() == Int32.Parse(querySet["server"])); - else - requestedServer = activeServers.First(); - - if (querySet["page"] != null) - requestPageNum = Int32.Parse(querySet["page"]); - - if (querySet["id"] != null) - ID = Int32.Parse(querySet["id"]); - - if (querySet["query"] != null) - Query = querySet["query"]; - } - - String Pattern = @"\{\{.+\}\}"; - Regex Search = new Regex(Pattern, RegexOptions.IgnoreCase); - - MatchCollection Matches = Search.Matches(Input); - - foreach (Match match in Matches) - { - Input = processReplacements(Input, match.Value, requestPageNum, ID, Query, requestedServer); - } - - return Input; - } - - catch (Exception E) - { - Page Error = new error(); - return Error.Load().Replace("{{ERROR}}", E.Message); - } - } - - private String parsePagination(int totalItems, int itemsPerPage, int currentPage, String Page) - { - StringBuilder output = new StringBuilder(); - - output.Append("
"); - - if (currentPage > 0) - output.AppendFormat("PREV", Page, currentPage - 1); - - double totalPages = Math.Ceiling(((float)totalItems / itemsPerPage)); - - output.Append("" + (currentPage + 1) + "/" + totalPages + ""); - - if ((currentPage + 1) < totalPages) - output.AppendFormat("NEXT", Page, currentPage + 1); - - output.Append("
"); - - return output.ToString(); - } - - private String processReplacements(String Input, String Macro, int curPage, int ID, String Query, params Server[] Servers) - { - bool Authenticated = false; - bool UserPrivelege = false; - - if (Servers[0] != null && Manager.lastIP != null) - { - Player User = Servers[0].clientDB.getPlayer(Manager.lastIP.ToString()); - if (User != null && User.Level > Player.Permission.Flagged) - Authenticated = true; - if (User != null && User.Level == Player.Permission.User) - UserPrivelege = true; - } - - if (Macro.Length < 5) - return ""; - - String Looking = Macro.Substring(2, Macro.Length - 4); - - if (Looking == "SERVERS") - { - int cycleFix = 0; - StringBuilder buffer = new StringBuilder(); - - foreach (Server S in activeServers) - { - StringBuilder players = new StringBuilder(); - if (S.getClientNum() > 0) - { - int count = 0; - double currentPlayers = S.statusPlayers.Count; - - players.Append(""); - - foreach (Player P in S.getPlayers()) - { - if (P == null) - continue; - - if (count % 2 == 0) - { - switch (cycleFix) - { - case 0: - players.Append(""); - cycleFix = 1; - break; - case 1: - players.Append(""); - cycleFix = 0; - break; - } - } - - players.AppendFormat("", P.databaseID, SharedLibrary.Utilities.nameHTMLFormatted(P)); - - if (count % 2 != 0) - { - players.Append(""); - } - - count++; - - } - players.Append("
{1}
"); - } - buffer.AppendFormat(@" - - - - - - - - -
{0}{1}{2}{3}PenaltiesHistory
- {5}", - - S.getName(), S.getMap(), S.getClientNum() + "/" + S.getMaxClients(), SharedLibrary.Utilities.gametypeLocalized(S.getGametype()), S.pID(), players.ToString()); - - if (S.getClientNum() > 0) - { - buffer.AppendFormat("
", S.pID(), '\"'); - if (UserPrivelege || Authenticated) - buffer.AppendFormat("
", S.pID(), '\"'); - } - buffer.Append("
"); - } - return Input.Replace(Macro, buffer.ToString()); - } - - if(Looking == "CHAT") - { - StringBuilder chatMessages = new StringBuilder(); - chatMessages.Append(""); - if (Servers.Length > 0 && Servers[0] != null) - { - foreach (Chat Message in Servers[0].chatHistory) - chatMessages.AppendFormat("", SharedLibrary.Utilities.nameHTMLFormatted(Message.Origin), Message.Message, Message.timeString()); - } - - chatMessages.Append("
{0}{1}{2}
"); - return chatMessages.ToString(); - } - - if (Looking == "PLAYER") - { - StringBuilder buffer = new StringBuilder(); - Server S = activeServers[0]; - - buffer.Append(""); - List matchingPlayers = new List(); - - if (ID > 0) - matchingPlayers.Add(S.clientDB.getPlayer(ID)); - - else if (Query.Length > 2) - { - matchingPlayers = S.clientDB.findPlayers(Query); - - if (matchingPlayers == null) - matchingPlayers = new List(); - - List matchedDatabaseIDs = new List(); - - foreach (Aliases matchingAlias in S.aliasDB.findPlayers(Query)) - matchedDatabaseIDs.Add(matchingAlias.Number); - - foreach (Player matchingP in S.clientDB.getPlayers(matchedDatabaseIDs)) - { - if (matchingPlayers.Find(x => x.databaseID == matchingP.databaseID) == null) - matchingPlayers.Add(matchingP); - } - } - - if (matchingPlayers == null) - buffer.Append("
NameAliasesIPRatingLevelConnectionsLast SeenProfile
"); - - else - { - foreach (Player Player in matchingPlayers) - { - if (Player == null) - continue; - - buffer.Append(""); - StringBuilder Names = new StringBuilder(); - - - List nameAlias = new List(); - List IPAlias = new List(); - StringBuilder IPs = new StringBuilder(); - - if (Authenticated) - { - List allAlliases = S.getAliases(Player); - - foreach (Aliases A in allAlliases) - { - - foreach (String Name in A.Names.Distinct()) - nameAlias.Add(Name); - - foreach (String IP in A.IPS.Distinct()) - IPAlias.Add(IP); - - } - - Names.Append("Show Aliases"); - Names.Append("
"); - foreach (String Name in nameAlias.Distinct()) - Names.AppendFormat("{0}
", Utilities.stripColors(Name)); - Names.Append("
"); - - IPs.Append("Show IPs"); - IPs.Append("
"); - foreach (String IP in IPAlias) - IPs.AppendFormat("{0}
", IP); - IPs.Append("
"); - } - - if (!Authenticated) - { - Names.Append("Hidden"); - IPs.Append("Hidden"); - } - - Int64 forumID = 0; - if (Player.npID.Length == 16) - { - forumID = Int64.Parse(Player.npID.Substring(0, 16), System.Globalization.NumberStyles.AllowHexSpecifier); - forumID = forumID - 76561197960265728; - } - - String Screenshot = String.Empty; - - //if (logged) - Screenshot = String.Format("
", forumID, Player.Name); - - buffer.AppendFormat("{0}{10}{1}{2}{3}{4}{5}{6} ago{8}", Player.Name, Names, IPs, 0, SharedLibrary.Utilities.levelHTMLFormatted(Player.Level), Player.Connections, Player.getLastConnection(), forumID, Player.Name, "/player?id=" + Player.databaseID, Screenshot); - buffer.Append(""); - } - - buffer.Append(""); - return Input.Replace(Macro, buffer.ToString()); - } - } - - if (Looking == "BANS") - { - StringBuilder buffer = new StringBuilder(); - Server S = activeServers[0]; - - buffer.Append(""); - int limitPerPage = 30; - int Pagination = curPage; - int totalBans = S.Bans.Count; - int range; - int start = Pagination * limitPerPage; - int cycleFix = 0; - - if (totalBans <= limitPerPage) - range = totalBans; - else if ((totalBans - start) < limitPerPage) - range = (totalBans - start); - else - range = limitPerPage; - - List Bans = new List(); - - if (totalBans > 0) - Bans = S.Bans.OrderByDescending(x => x.When).ToList().GetRange(start, range); - - if (Bans.Count == 0) - buffer.Append("No bans yet."); - - else - { - buffer.Append("

{{TIME}}


"); - - if (Bans[0] != null) - buffer = buffer.Replace("{{TIME}}", "From " + SharedLibrary.Utilities.timePassed(Bans[0].When) + " ago" + " — " + totalBans + " total"); - - List npIDs = new List(); - - foreach (Penalty B in Bans) - npIDs.Add(B.npID); - - - List bannedPlayers = S.clientDB.getPlayers(npIDs); - - for (int i = 0; i < Bans.Count; i++) - { - if (Bans[i] == null) - continue; - - Player P = bannedPlayers.Where(x => x.npID == Bans[i].npID).First(); - Player B; - - if (P.npID == Bans[i].bannedByID || Bans[i].bannedByID == "") - B = new Player("IW4MAdmin", "", 0, SharedLibrary.Player.Permission.Banned, 0, "", 0, ""); - - else - B = S.clientDB.getPlayer(Bans[i].bannedByID, -1); - - if (P == null) - P = new Player("Unknown", "n/a", 0, 0, 0, "Unknown", 0, ""); - if (B == null) - B = new Player("Unknown", "n/a", 0, 0, 0, "Unknown", 0, ""); - - if (P.lastOffense == String.Empty) - P.lastOffense = "Evade"; - - if (P != null && B != null) - { - - String Prefix; - if (cycleFix % 2 == 0) - Prefix = "class=row-grey"; - else - Prefix = "class=row-white"; - - String Link = "/player?id=" + P.databaseID; - buffer.AppendFormat("", P.Name, Bans[i].Reason.Substring(0, Math.Min(70, Bans[i].Reason.Length)), SharedLibrary.Utilities.nameHTMLFormatted(B), Bans[i].getWhen(), Prefix, Link, Utilities.penaltyHTMLFormatted(Bans[i].BType)); - cycleFix++; - } - } - } - buffer.Append("
NameTypeOffensePenalty ByTime
{0}{6}{1}{2}{3}

"); - - if (totalBans > limitPerPage) - buffer.Append(parsePagination(totalBans, limitPerPage, Pagination, "bans")); - - return Input.Replace(Macro, buffer.ToString()); - } - - if (Looking == "GRAPH") - { - StringBuilder buffer = new StringBuilder(); - buffer.Append("
"); - buffer.Append(""); - return Input.Replace(Macro, buffer.ToString()); - } - - if (Looking == "TITLE") - return Input.Replace(Macro, "IW4MAdmin by RaidMax"); - - if (Looking == "VERSION") - return Input.Replace(Macro, "1.1"); - - if (Looking == "PUBBANS" || Looking == "PUBBANSR") - { - String pubBans = "=========================================\r\nIW4MAdmin Public Banlist\r\n=========================================\r\n"; - foreach (Penalty P in activeServers[0].Bans.OrderByDescending(x => x.When).ToList()) - { - if (P.BType == Penalty.Type.Ban) - pubBans += String.Format("{0};{1};{2};{3}\r\n",P.npID, P.IP, P.Reason.Trim(), P.When); - if (Looking == "PUBBANSR") - pubBans += "
"; - } - - return Input.Replace(Macro, pubBans); - } - - return "PLACEHOLDER"; - - } - - public String processRequest(Kayak.Http.HttpRequestHead request) - { - Page requestedPage = new notfound(); - Page Header = new header(); - Page Footer = new footer(); - - if (request.Path == "/") - requestedPage = new main(); - - else - { - string p = request.Path.ToLower().Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries)[0]; - switch (p) - { - case "bans": - requestedPage = new bans(); - break; - case "player": - requestedPage = new player(); - break; - case "graph": - requestedPage = new graph(); - return processTemplate(requestedPage.Load(), request.QueryString); - case "chat": - requestedPage = new chat(); - return processTemplate(requestedPage.Load(), request.QueryString); - case "error": - requestedPage = new error(); - break; - case "pubbans": - return processTemplate("{{PUBBANS}}", null); - case "pubbansr": - return processTemplate("{{PUBBANSR}}", null); - default: - requestedPage = new notfound(); - break; - } - } - - return processTemplate(Header.Load(), null) + processTemplate(requestedPage.Load(), request.QueryString) + processTemplate(Footer.Load(), null); - } - } - - abstract class Page - { - public abstract String Load(); - public abstract String Name { get; } - - protected String loadHTML() - { - IFile HTML = new IFile("webfront\\" + this.Name + ".html"); - String Contents = HTML.getLines(); - HTML.Close(); - return Contents; - } - } - - class notfound : Page - { - public override String Name - { - get { return "notfound"; } - } - - public override String Load() - { - return loadHTML(); - } - } - - class main : Page - { - public override String Name - { - get { return "main"; } - } - - public override String Load() - { - return loadHTML(); - } - } - - class bans : Page - { - public override String Name - { - get { return "bans"; } - } - - public override String Load() - { - return loadHTML(); - } - } - - class header : Page - { - public override String Name - { - get { return "header"; } - } - - public override String Load() - { - return loadHTML(); - } - } - - class footer : Page - { - public override String Name - { - get { return "footer"; } - } - - public override String Load() - { - return loadHTML(); - } - } - - class player : Page - { - public override String Name - { - get { return "player"; } - } - - public override String Load() - { - return loadHTML(); - } - } - - class graph : Page - { - public override String Name - { - get { return "graph"; } - } - - public override String Load() - { - return loadHTML(); - } - } - - class chat : Page - { - public override String Name - { - get { return "chat"; } - } - - public override String Load() - { - return "{{CHAT}}"; - } - } - - class error : Page - { - public override String Name - { - get { return "error"; } - } - - public override String Load() - { - return loadHTML(); - } - } -} diff --git a/Webfront Plugin/Main.cs b/Webfront Plugin/Main.cs deleted file mode 100644 index d87ede3ef..000000000 --- a/Webfront Plugin/Main.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using SharedLibrary; -using System.Threading; -using System.Collections.Generic; - -namespace Webfront_Plugin -{ - public class Webfront : Plugin - { - private static Thread webManagerThread; - - public override void onEvent(Event E) - { - if (E.Type == Event.GType.Start) - { - Manager.webFront.removeServer(Manager.webFront.getServers().Find(x => x.getPort() == E.Owner.getPort())); - Manager.webFront.addServer(E.Owner); - E.Owner.Log.Write("Webfront now listening", Log.Level.Production); - } - if (E.Type == Event.GType.Stop) - { - Manager.webFront.removeServer(E.Owner); - E.Owner.Log.Write("Webfront has lost access to server", Log.Level.Production); - } - } - - public override void onLoad() - { - webManagerThread = new Thread(new ThreadStart(Manager.Init)); - webManagerThread.Name = "Webfront"; - - webManagerThread.Start(); - } - - public override void onUnload() - { - Manager.webScheduler.Stop(); - webManagerThread.Join(); - } - - public override String Name - { - get { return "Webfront"; } - } - - public override float Version - { - get { return 0.1f; } - } - - public override string Author - { - get - { - return "RaidMax"; - } - } - } -} diff --git a/Webfront Plugin/Properties/AssemblyInfo.cs b/Webfront Plugin/Properties/AssemblyInfo.cs deleted file mode 100644 index 13bcddb9d..000000000 --- a/Webfront Plugin/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Webfront Plugin")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Webfront Plugin")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("03a62b7b-361a-4232-8db7-4e00b9e7a31a")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Welcome Plugin/CountryLookup.cs b/Welcome Plugin/CountryLookup.cs index 15dddec33..1f9dc772d 100644 --- a/Welcome Plugin/CountryLookup.cs +++ b/Welcome Plugin/CountryLookup.cs @@ -60,7 +60,7 @@ namespace CountryLookupProj { fileInput = new FileStream(fileName, FileMode.Open, FileAccess.Read); } - catch (FileNotFoundException e) + catch (FileNotFoundException) { Console.WriteLine("File " + fileName + " not found."); } @@ -73,7 +73,7 @@ namespace CountryLookupProj { addr = IPAddress.Parse(str); } - catch (FormatException e) + catch (FormatException) { return "--"; } @@ -109,7 +109,7 @@ namespace CountryLookupProj { addr = IPAddress.Parse(str); } - catch (FormatException e) + catch (FormatException) { return "N/A"; } @@ -134,7 +134,7 @@ namespace CountryLookupProj fileInput.Seek(6 * offset, 0); fileInput.Read(buf, 0, 6); } - catch (IOException e) + catch (IOException) { Console.WriteLine("IO Exception"); } diff --git a/Welcome Plugin/GeoIP.dat b/Welcome Plugin/GeoIP.dat new file mode 100644 index 000000000..9813562ce Binary files /dev/null and b/Welcome Plugin/GeoIP.dat differ diff --git a/Welcome Plugin/Main.cs b/Welcome Plugin/Main.cs deleted file mode 100644 index 63450c3d2..000000000 --- a/Welcome Plugin/Main.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using SharedLibrary; - -namespace Welcome_Plugin -{ - public class Main : Plugin - { - public override string Author - { - get - { - return "RaidMax"; - } - } - - public override float Version - { - get - { - return 1.0f; - } - } - - public override string Name - { - get - { - return "Welcome Plugin"; - } - } - - public override void onEvent(Event E) - { - if (E.Type == Event.GType.Connect) - { - Player newPlayer = E.Origin; - - if (newPlayer.Level > Player.Permission.User) - E.Owner.Broadcast(Utilities.levelToColor(newPlayer.Level) + " ^5" + newPlayer.Name + " ^7has joined the server."); - - else - { - CountryLookupProj.CountryLookup CLT = new CountryLookupProj.CountryLookup("GeoIP.dat"); - E.Owner.Broadcast("^5" + newPlayer.Name + " ^7hails from ^5" + CLT.lookupCountryName(newPlayer.IP)); - } - } - } - - public override void onLoad() - { - return; - } - - public override void onUnload() - { - return; - } - } -} diff --git a/Welcome Plugin/Plugin.cs b/Welcome Plugin/Plugin.cs new file mode 100644 index 000000000..266bb1293 --- /dev/null +++ b/Welcome Plugin/Plugin.cs @@ -0,0 +1,69 @@ +using System; +using SharedLibrary; +using SharedLibrary.Extensions; +using System.Threading.Tasks; + +namespace Welcome_Plugin +{ + public class Plugin : IPlugin + { + public string Author + { + get + { + return "RaidMax"; + } + } + + public float Version + { + get + { + return 1.0f; + } + } + + public string Name + { + get + { + return "Welcome Plugin"; + } + } + + public async Task OnLoad() + { + return; + } + + public async Task OnUnload() + { + return; + } + + public async Task OnTick(Server S) + { + return; + } + + public async Task OnEvent(Event E, Server S) + { + if (E.Type == Event.GType.Connect) + { + Player newPlayer = E.Origin; + + if (newPlayer.Level >= Player.Permission.Trusted && !E.Origin.Masked) + await E.Owner.Broadcast(Utilities.levelToColor(newPlayer.Level) + " ^5" + newPlayer.Name + " ^7has joined the server."); + + if (newPlayer.Level == Player.Permission.Flagged) + await E.Owner.ToAdmins($"^1NOTICE: ^7Flagged player ^5{newPlayer.Name}^7 has joined!"); + + else + { + CountryLookupProj.CountryLookup CLT = new CountryLookupProj.CountryLookup("Plugins/GeoIP.dat"); + await E.Owner.Broadcast($"^5{newPlayer.Name} ^7hails from ^5{CLT.lookupCountryName(newPlayer.IP)}"); + } + } + } + } +} diff --git a/Welcome Plugin/Welcome Plugin.csproj b/Welcome Plugin/Welcome Plugin.csproj index 8afb8cddc..f05210edf 100644 --- a/Welcome Plugin/Welcome Plugin.csproj +++ b/Welcome Plugin/Welcome Plugin.csproj @@ -8,9 +8,10 @@ Library Properties Welcome_Plugin - Welcome Plugin - v4.0 + WelcomePlugin + v4.5 512 + true @@ -20,6 +21,7 @@ DEBUG;TRACE prompt 4 + false pdbonly @@ -28,10 +30,11 @@ TRACE prompt 4 + false - - ..\Admin\lib\SharedLibrary.dll + + ..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll @@ -43,10 +46,25 @@ - + + + + + + + + {d51eeceb-438a-47da-870f-7d7b41bc24d6} + SharedLibrary + False + + + + copy /Y "$(TargetDir)$(TargetName).dll" "$(SolutionDir)BUILD\plugins\" +copy /Y "$(ProjectDir)GeoIP.dat" "$(SolutionDir)Admin\bin\$(ConfigurationName)\GeoIP.dat" +