support UTF8 in log files

sayteam registered as say event
updated readme
This commit is contained in:
RaidMax 2018-04-19 17:52:48 -05:00
parent 438718507b
commit 7c691c2327
11 changed files with 190 additions and 612 deletions

View File

@ -12,7 +12,7 @@ namespace Application.EventParsers
public GameEvent GetEvent(Server server, string logLine) public GameEvent GetEvent(Server server, string logLine)
{ {
string[] lineSplit = logLine.Split(';'); string[] lineSplit = logLine.Split(';');
string cleanedEventLine = Regex.Replace(lineSplit[0], @"[0-9]+:[0-9]+\ ", ""); string cleanedEventLine = Regex.Replace(lineSplit[0], @"[0-9]+:[0-9]+\ ", "").Trim();
if (cleanedEventLine[0] == 'K') if (cleanedEventLine[0] == 'K')
{ {
@ -29,7 +29,7 @@ namespace Application.EventParsers
} }
} }
if (lineSplit[0].Substring(lineSplit[0].Length - 3).Trim() == "say") if (cleanedEventLine == "say" || cleanedEventLine == "sayteam")
{ {
return new GameEvent() return new GameEvent()
{ {
@ -37,7 +37,7 @@ namespace Application.EventParsers
Data = lineSplit[4].Replace("\x15", ""), Data = lineSplit[4].Replace("\x15", ""),
Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)), Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)),
Owner = server, Owner = server,
Message = lineSplit[4] Message = lineSplit[4].Replace("\x15", "")
}; };
} }

View File

@ -39,7 +39,7 @@ namespace Application.EventParsers
}; };
} }
if (lineSplit[0] == "say") if (lineSplit[0] == "say" || lineSplit[0] == "sayteam")
{ {
return new GameEvent() return new GameEvent()
{ {

View File

@ -648,7 +648,7 @@ namespace IW4MAdmin
CustomCallback = await ScriptLoaded(); CustomCallback = await ScriptLoaded();
string mainPath = EventParser.GetGameDir(); string mainPath = EventParser.GetGameDir();
#if DEBUG #if DEBUG
basepath.Value = @"\\192.168.88.253\Call of Duty Black Ops II"; basepath.Value = @"D:\";
#endif #endif
string logPath = game.Value == string.Empty ? string logPath = game.Value == string.Empty ?
$"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{mainPath}{Path.DirectorySeparatorChar}{logfile.Value}" : $"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{mainPath}{Path.DirectorySeparatorChar}{logfile.Value}" :

View File

@ -12,8 +12,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
version.txt = version.txt version.txt = version.txt
EndProjectSection EndProjectSection
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Plugins\Tests\Tests.csproj", "{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedLibraryCore", "SharedLibraryCore\SharedLibraryCore.csproj", "{AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedLibraryCore", "SharedLibraryCore\SharedLibraryCore.csproj", "{AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebfrontCore", "WebfrontCore\WebfrontCore.csproj", "{D59AC1F1-2FB9-4BE7-813E-0CCCC4FE9067}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebfrontCore", "WebfrontCore\WebfrontCore.csproj", "{D59AC1F1-2FB9-4BE7-813E-0CCCC4FE9067}"
@ -30,6 +28,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Login", "Plugins\Login\Logi
EndProject EndProject
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "Master", "Master\Master.pyproj", "{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}" Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "Master", "Master\Master.pyproj", "{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Plugins\Tests\Tests.csproj", "{B72DEBFB-9D48-4076-8FF5-1FD72A830845}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -46,26 +46,6 @@ Global
Release|x86 = Release|x86 Release|x86 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Debug|x64.ActiveCfg = Debug|Any CPU
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Debug|x64.Build.0 = Debug|Any CPU
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Debug|x86.ActiveCfg = Debug|x86
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Debug|x86.Build.0 = Debug|x86
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Prerelease|Any CPU.ActiveCfg = Release-Stable|Any CPU
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Prerelease|Mixed Platforms.ActiveCfg = Release-Stable|x86
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Prerelease|x64.ActiveCfg = Release-Stable|Any CPU
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Prerelease|x86.ActiveCfg = Release-Stable|x86
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release|Any CPU.ActiveCfg = Release-Stable|Any CPU
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release|Any CPU.Build.0 = Release-Stable|Any CPU
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release|Mixed Platforms.ActiveCfg = Release-Stable|x86
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release|Mixed Platforms.Build.0 = Release-Stable|x86
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release|x64.ActiveCfg = Release-Stable|Any CPU
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release|x64.Build.0 = Release-Stable|Any CPU
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release|x86.ActiveCfg = Release-Stable|x86
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}.Release|x86.Build.0 = Release-Stable|x86
{AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}.Debug|Any CPU.Build.0 = Debug|Any CPU {AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU {AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
@ -258,16 +238,38 @@ Global
{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Release|x64.Build.0 = Release|Any CPU {F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Release|x64.Build.0 = Release|Any CPU
{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Release|x86.ActiveCfg = Release|Any CPU {F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Release|x86.ActiveCfg = Release|Any CPU
{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Release|x86.Build.0 = Release|Any CPU {F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Release|x86.Build.0 = Release|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Debug|x64.ActiveCfg = Debug|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Debug|x64.Build.0 = Debug|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Debug|x86.ActiveCfg = Debug|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Debug|x86.Build.0 = Debug|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Prerelease|Any CPU.ActiveCfg = Debug|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Prerelease|Mixed Platforms.ActiveCfg = Debug|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Prerelease|Mixed Platforms.Build.0 = Debug|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Prerelease|x64.ActiveCfg = Debug|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Prerelease|x64.Build.0 = Debug|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Prerelease|x86.ActiveCfg = Debug|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Prerelease|x86.Build.0 = Debug|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|x64.ActiveCfg = Release|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|x64.Build.0 = Release|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|x86.ActiveCfg = Release|Any CPU
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{B8C2A759-8663-4F6F-9BA4-19595F5E12C1} = {26E8B310-269E-46D4-A612-24601F16065F}
{98BE4A81-8AFD-4957-83F7-009D353C6BCB} = {26E8B310-269E-46D4-A612-24601F16065F} {98BE4A81-8AFD-4957-83F7-009D353C6BCB} = {26E8B310-269E-46D4-A612-24601F16065F}
{179140D3-97AA-4CB4-8BF6-A0C73CA75701} = {26E8B310-269E-46D4-A612-24601F16065F} {179140D3-97AA-4CB4-8BF6-A0C73CA75701} = {26E8B310-269E-46D4-A612-24601F16065F}
{958FF7EC-0226-4E85-A85B-B84EC768197D} = {26E8B310-269E-46D4-A612-24601F16065F} {958FF7EC-0226-4E85-A85B-B84EC768197D} = {26E8B310-269E-46D4-A612-24601F16065F}
{D9F2ED28-6FA5-40CA-9912-E7A849147AB1} = {26E8B310-269E-46D4-A612-24601F16065F} {D9F2ED28-6FA5-40CA-9912-E7A849147AB1} = {26E8B310-269E-46D4-A612-24601F16065F}
{B72DEBFB-9D48-4076-8FF5-1FD72A830845} = {26E8B310-269E-46D4-A612-24601F16065F}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {84F8F8E0-1F73-41E0-BD8D-BB6676E2EE87} SolutionGuid = {84F8F8E0-1F73-41E0-BD8D-BB6676E2EE87}

View File

@ -12,6 +12,10 @@
<Configurations>Debug;Release;Prerelease</Configurations> <Configurations>Debug;Release;Prerelease</Configurations>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE;DEBUG;NETCOREAPP2_0</DefineConstants>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" /> <ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -1,20 +1,11 @@
#if DEBUG #if DEBUG
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.IO;
using SharedLibrary; using SharedLibraryCore;
using SharedLibrary.Interfaces; using SharedLibraryCore.Interfaces;
using SharedLibrary.Helpers; using SharedLibraryCore.Helpers;
using SharedLibrary.Objects;
using System.Text.RegularExpressions;
using StatsPlugin.Models;
using SharedLibrary.Services;
using SharedLibrary.Database.Models;
using SharedLibrary.Database;
namespace IW4MAdmin.Plugins namespace IW4MAdmin.Plugins
{ {
@ -26,12 +17,9 @@ namespace IW4MAdmin.Plugins
public string Author => "RaidMax"; public string Author => "RaidMax";
private DateTime Interval; public async Task OnEventAsync(GameEvent E, Server S)
public async Task OnEventAsync(Event E, Server S)
{ {
return; if (E.Type == GameEvent.EventType.Start)
if (E.Type == Event.GType.Start)
{ {
#region PLAYER_HISTORY #region PLAYER_HISTORY
var rand = new Random(GetHashCode()); var rand = new Random(GetHashCode());
@ -61,361 +49,11 @@ namespace IW4MAdmin.Plugins
} }
} }
public async Task OnLoadAsync(IManager manager) public Task OnLoadAsync(IManager manager) => Task.CompletedTask;
{
// #if DO_IMPORT
var svc = new GenericRepository<EFServer>();
svc.Insert(new EFServer()
{
Active = true,
Port = 28960,
ServerId = Math.Abs("127.0.0.1:28960".GetHashCode()),
});
svc.Insert(new EFServer()
{
Active = true,
Port = 28965,
ServerId = Math.Abs("127.0.0.1:28965".GetHashCode()),
});
svc.Insert(new EFServer()
{
Active = true,
Port = 28970,
ServerId = Math.Abs("127.0.0.1:28970".GetHashCode()),
});
svc.SaveChanges();
// #endif
Interval = DateTime.Now;
var clients = new List<Player>();
var oldClients = new Dictionary<int, Player>();
#region CLIENTS
if (File.Exists("import_clients.csv"))
{
manager.GetLogger().WriteVerbose("Beginning import of existing clients");
var lines = File.ReadAllLines("import_clients.csv").Skip(1);
foreach (string line in lines)
{
string[] fields = Regex.Replace(line, "\".*\"", "").Split(',');
fields.All(f =>
{
f = f.StripColors().Trim();
return true;
});
if (fields.Length != 11)
{
manager.GetLogger().WriteError("Invalid client import file... aborting import");
return;
}
if (fields[1].Substring(0, 5) == "01100" || fields[0] == string.Empty || fields[1] == string.Empty || fields[6] == string.Empty)
continue;
if (!Regex.Match(fields[6], @"^\d+\.\d+\.\d+.\d+$").Success)
fields[6] = "0";
var client = new Player()
{
Name = fields[0],
NetworkId = fields[1].ConvertLong(),
IPAddress = fields[6].ConvertToIP(),
Level = (Player.Permission)Convert.ToInt32(fields[3]),
Connections = Convert.ToInt32(fields[5]),
LastConnection = DateTime.Parse(fields[7]),
};
clients.Add(client);
oldClients.Add(Convert.ToInt32(fields[2]), client);
}
clients = clients.Distinct().ToList();
// #if DO_IMPORT
/*clients = clients
.GroupBy(c => new { c.Name, c.IPAddress })
.Select(c => c.FirstOrDefault())
.ToList();*/
//newClients = clients.ToList();
//newClients.ForEach(c => c.ClientId = 0);
manager.GetLogger().WriteVerbose($"Read {clients.Count} clients for import");
try
{
SharedLibrary.Database.Importer.ImportClients(clients);
}
catch (Exception e)
{
manager.GetLogger().WriteError("Saving imported clients failed");
}
// #endif
}
#endregion
// load the entire database lol
var ctx = new DatabaseContext();
ctx.Configuration.ProxyCreationEnabled = false;
var cls = ctx.Clients.Include("AliasLink.Children").ToList(); //manager.GetClientService().Find(c => c.Active).Result;
ctx.Dispose();
#region ALIASES
if (File.Exists("import_aliases.csv"))
{
manager.GetLogger().WriteVerbose("Beginning import of existing aliases");
var aliases = new List<EFAlias>();
var lines = File.ReadAllLines("import_aliases.csv").Skip(1);
foreach (string line in lines)
{
string[] fields = Regex.Replace(line, "\".*\"", "").Split(',');
fields.All(f =>
{
f = f.StripColors().Trim();
return true;
});
if (fields.Length != 3)
{
manager.GetLogger().WriteError("Invalid alias import file... aborting import");
return;
}
try
{
int number = Int32.Parse(fields[0]);
var names = fields[1].Split(';').Where(n => n != String.Empty && n.Length > 2);
var oldClient = oldClients[number];
var newClient = cls.FirstOrDefault(c => c.NetworkId == oldClient.NetworkId);
foreach (string name in names)
{
// this is slow :D
if (newClient.AliasLink.Children.FirstOrDefault(n => n.Name == name) != null) continue;
var alias = new EFAlias()
{
Active = true,
DateAdded = DateTime.UtcNow,
Name = name,
LinkId = newClient.AliasLinkId,
IPAddress = newClient.IPAddress
};
aliases.Add(alias);
}
}
catch (KeyNotFoundException)
{
continue;
}
catch (Exception)
{
manager.GetLogger().WriteVerbose($"Could not import alias with line {line}");
}
}
SharedLibrary.Database.Importer.ImportSQLite(aliases);
}
#endregion
#region PENALTIES
if (File.Exists("import_penalties.csv"))
{
var penalties = new List<Penalty>();
manager.GetLogger().WriteVerbose("Beginning import of existing penalties");
foreach (string line in File.ReadAllLines("import_penalties.csv").Skip(1))
{
string comma = Regex.Match(line, "\".*,.*\"").Value.Replace(",", "");
string[] fields = Regex.Replace(line, "\".*,.*\"", comma).Split(',');
fields.All(f =>
{
f = f.StripColors().Trim();
return true;
});
if (fields.Length != 7)
{
manager.GetLogger().WriteError("Invalid penalty import file... aborting import");
return;
}
if (fields[2].Substring(0, 5) == "01100" || fields[2].Contains("0000000"))
continue;
try
{
var expires = DateTime.Parse(fields[6]);
var when = DateTime.Parse(fields[5]);
var penaltyType = (Penalty.PenaltyType)Int32.Parse(fields[0]);
if (penaltyType == Penalty.PenaltyType.Ban)
expires = DateTime.MaxValue;
var penalty = new Penalty()
{
Type = penaltyType,
Expires = expires == DateTime.MinValue ? when : expires,
Punisher = new SharedLibrary.Database.Models.EFClient() { NetworkId = fields[3].ConvertLong() },
Offender = new SharedLibrary.Database.Models.EFClient() { NetworkId = fields[2].ConvertLong() },
Offense = fields[1].Replace("\"", "").Trim(),
Active = true,
When = when,
};
penalties.Add(penalty);
}
catch (Exception e)
{
manager.GetLogger().WriteVerbose($"Could not import penalty with line {line}");
}
}
//#if DO_IMPORT
SharedLibrary.Database.Importer.ImportPenalties(penalties);
manager.GetLogger().WriteVerbose($"Imported {penalties.Count} penalties");
//#endif
}
#endregion
#region CHATHISTORY
if (File.Exists("import_chathistory.csv"))
{
var chatHistory = new List<EFClientMessage>();
manager.GetLogger().WriteVerbose("Beginning import of existing messages");
foreach (string line in File.ReadAllLines("import_chathistory.csv").Skip(1))
{
string comma = Regex.Match(line, "\".*,.*\"").Value.Replace(",", "");
string[] fields = Regex.Replace(line, "\".*,.*\"", comma).Split(',');
fields.All(f =>
{
f = f.StripColors().Trim();
return true;
});
if (fields.Length != 4)
{
manager.GetLogger().WriteError("Invalid chat history import file... aborting import");
return;
}
try
{
int cId = Convert.ToInt32(fields[0]);
var linkedClient = oldClients[cId];
var newcl = cls.FirstOrDefault(c => c.NetworkId == linkedClient.NetworkId);
if (newcl == null)
newcl = cls.FirstOrDefault(c => c.Name == linkedClient.Name && c.IPAddress == linkedClient.IPAddress);
int newCId = newcl.ClientId;
var chatMessage = new EFClientMessage()
{
Active = true,
ClientId = newCId,
Message = fields[1],
TimeSent = DateTime.Parse(fields[3]),
ServerId = Math.Abs($"127.0.0.1:{Convert.ToInt32(fields[2]).ToString()}".GetHashCode())
};
chatHistory.Add(chatMessage);
}
catch (Exception e)
{
manager.GetLogger().WriteVerbose($"Could not import chatmessage with line {line}");
}
}
manager.GetLogger().WriteVerbose($"Read {chatHistory.Count} messages for import");
SharedLibrary.Database.Importer.ImportSQLite(chatHistory);
}
#endregion
#region STATS
foreach (string file in Directory.GetFiles(Environment.CurrentDirectory))
{
if (Regex.Match(file, @"import_stats_[0-9]+.csv").Success)
{
int port = Int32.Parse(Regex.Match(file, "[0-9]{5}").Value);
var stats = new List<EFClientStatistics>();
manager.GetLogger().WriteVerbose("Beginning import of existing client stats");
var lines = File.ReadAllLines(file).Skip(1);
foreach (string line in lines)
{
string[] fields = line.Split(',');
if (fields.Length != 9)
{
manager.GetLogger().WriteError("Invalid client import file... aborting import");
return;
}
try
{
if (fields[0].Substring(0, 5) == "01100")
continue;
long id = fields[0].ConvertLong();
var client = cls.Single(c => c.NetworkId == id);
var time = Convert.ToInt32(fields[8]);
double spm = time < 60 ? 0 : Math.Round(Convert.ToInt32(fields[1]) * 100.0 / time, 3);
if (spm > 1000)
spm = 0;
var st = new EFClientStatistics()
{
Active = true,
ClientId = client.ClientId,
ServerId = Math.Abs($"127.0.0.1:{port}".GetHashCode()),
Kills = Convert.ToInt32(fields[1]),
Deaths = Convert.ToInt32(fields[2]),
SPM = spm,
Skill = 0,
TimePlayed = time * 60
};
// client.TotalConnectionTime += time;
stats.Add(st);
stats = stats.AsEnumerable()
.GroupBy(c => new { c.ClientId })
.Select(c => c.FirstOrDefault()).ToList();
var cl = await manager.GetClientService().Get(st.ClientId);
cl.TotalConnectionTime += time * 60;
await manager.GetClientService().Update(cl);
}
catch (Exception e)
{
continue;
}
}
manager.GetLogger().WriteVerbose($"Read {stats.Count} clients stats for import");
try
{
SharedLibrary.Database.Importer.ImportSQLite(stats);
}
catch (Exception e)
{
manager.GetLogger().WriteError("Saving imported stats failed");
}
}
}
#endregion
}
public async Task OnTickAsync(Server S) public async Task OnTickAsync(Server S)
{ {
return; /*
if ((DateTime.Now - Interval).TotalSeconds > 1) if ((DateTime.Now - Interval).TotalSeconds > 1)
{ {
var rand = new Random(); var rand = new Random();
@ -492,13 +130,10 @@ namespace IW4MAdmin.Plugins
} }
} }
} }
*/
} }
public async Task OnUnloadAsync() public Task OnUnloadAsync() => Task.CompletedTask;
{
}
} }
} }
#endif #endif

View File

@ -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("Tests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Tests")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[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("b8c2a759-8663-4f6f-9ba4-19595f5e12c1")]
// 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")]

View File

@ -1,97 +1,22 @@
<?xml version="1.0" encoding="utf-8"?> <Project Sdk="Microsoft.NET.Sdk">
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{B8C2A759-8663-4F6F-9BA4-19595F5E12C1}</ProjectGuid>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder> <TargetFramework>netcoreapp2.0</TargetFramework>
<RootNamespace>Tests</RootNamespace> <ApplicationIcon />
<AssemblyName>Tests</AssemblyName> <StartupObject />
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>full</DebugType> <DefineConstants>TRACE;DEBUG;NETCOREAPP2_0</DefineConstants>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release-Nightly|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release-Nightly|x86' ">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release-Stable|AnyCPU'">
<OutputPath>bin\Release-Stable\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release-Stable|x86'">
<OutputPath>bin\x86\Release-Stable\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup> </PropertyGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy &quot;$(TargetPath)&quot; &quot;$(SolutionDir)BUILD\Plugins&quot;" />
</Target>
<ItemGroup> <ItemGroup>
<Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Include="Plugin.cs" /> </Project>
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\SharedLibrary\SharedLibrary.csproj">
<Project>{d51eeceb-438a-47da-870f-7d7b41bc24d6}</Project>
<Name>SharedLibrary</Name>
</ProjectReference>
<ProjectReference Include="..\SimpleStats\StatsPlugin.csproj">
<Project>{4785ab75-66f3-4391-985d-63a5a049a0fa}</Project>
<Name>StatsPlugin</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>if $(ConfigurationName) == Debug (copy /Y "$(TargetDir)$(TargetName).dll" "$(SolutionDir)BUILD\plugins\")</PostBuildEvent>
</PropertyGroup>
</Project>

207
README.md
View File

@ -1,111 +1,155 @@
# <span style="color: #007ACC;">IW4MAdmin</span>
### <span style="color: #007ACC; opacity:0.75;">Quick Start Guide</span> # IW4MAdmin
### Version 1.5 ### Quick Start Guide
### Version 2.0
_______ _______
### About
**IW4MAdmin** is an administration tool for [IW4x](https://iw4xcachep26muba.onion.link/), [T6M](https://plutonium.pw/), and most Call of Duty® dedicated servers. It allows complete control of your server; from changing maps, to banning players, **IW4MAdmin** monitors and records activity on your server(s). With plugin support, extending its functionality is a breeze.
### Setup ### Setup
IW4MAdmin requires minimal configuration to run. There is only one prerequisite. **IW4MAdmin** requires minimal configuration to run. There is only one prerequisite.
1. [.NET Framework 4.5](https://www.microsoft.com/en-us/download/details.aspx?id=30653) *or newer* * [.NET Core 2.0 Runtime](https://www.microsoft.com/net/download/dotnet-core/runtime-2.0.5) *or newer*
Extract `IW4MAdmin.zip` 1. Extract `IW4MAdmin-<version>.zip`
Run `IW4MAdmin.exe` 2. Open command prompt or terminal in the extracted folder
3. Run `>dotnet IW4MAdmin.dll`
___ ___
### Configuration ### Configuration
_If you wish to customize your experience of IW4MAdmin, the following configuration files will allow you to changes core options._ #### Initial Configuration
When **IW4MAdmin** is launched for the _first time_, you will be prompted to setup your configuration.
`maps.cfg` `Enable webfront`
* This is the configuration file that links an IW4 map file name to its common/in-game name * Enables you to monitor and control your server(s) through a web interface [defaults to `http://127.0.0.1:1624`]
* This can be safely modified to add additional SP/DLC maps
`messages.cfg` `Enable multiple owners`
* This is the configuration file that broadcasts messages to your server at a set time * Enables more than one client to be promoted to level of `Owner`
* The _first line_ specifies the amount of time between messages (in seconds)
* Every new line is interpreted as a new message
* Color codes are allowed in the messages
* Tokens are denoted by double braces: {{TOKEN}}
`rules.cfg` `Enable stepped privilege hierarchy`
* This is the configuration file that sets the server's rules. * Allows privileged clients to promote other clients to the level below their current level
* Every new line is interpreted as a new rule
* All rules are _global_ across servers
`web.cfg` `Enable custom say name`
* This is the configuration file that specifies the web front bindings * Shows a prefix to every message send by **IW4MAdmin** -- `[Admin] message`
* The first line specifies the `IP` or `Hostname` to bind to * _This feature requires you specify a custom say name_
* The second line specifies the `port` to bind to
`Enable client VPNs`
* Allow clients to use a [VPN](https://en.wikipedia.org/wiki/Virtual_private_network)
* _This feature requires an active api key on [iphub.info](https://iphub.info/)_
`Enable discord link`
* Shows a link to your server's discord on the webfront
* _This feature requires an invite link to your discord server_
#### Advanced Configuration
If you wish to further customize your experience of **IW4MAdmin**, the following configuration file(s) will allow you to changes core options using any text-editor.
#### `IW4MAdminSettings.json`-- _this file is created after initial setup_
* This file uses the [JSON](https://en.wikipedia.org/wiki/JSON#JSON_sample) specification, so please validate it before running **IW4MAdmin**
`WebfrontBindUrl`
* Specifies the address and port the webfront will listen on.
* The value can be an [IP Address](https://en.wikipedia.org/wiki/IP_address):port or [Domain Name](https://en.wikipedia.org/wiki/Domain_name):port
`Servers`
* Specifies the list of servers **IW4MAdmin** will monitor
* `IPAddress`
* Specifies the IP Address of the particular server
* `Port`
* Specifies the port of the particular server
* `Password`
* Specifies the `rcon_password` of the particular server
* `AutoMessages`
* Specifies the list of messages that are broadcasted to the particular server
* `Rules`
* Specifies the list of rules that apply to the particular server
`AutoMessagePeriod`
* Specifies (in seconds) how often messages should be broadcasted to the server(s)
`AutoMessages`
* Specifies the list of messages that are broadcasted to **all** servers
`GlobalRules`
* Specifies the list of rules that apply to **all** servers`
`Maps`
* Specifies the list of maps for each supported game
* `Name`
* Specifies the name of the map as returned by the game
* `Alias`
* Specifies the display name of the map (as seen while loading in)
___ ___
### Commands ### Commands
|Name |Alias|Description |Requires Target|Syntax |Required Level| |Name |Alias|Description |Requires Target|Syntax |Required Level|
|--------------| -----| --------------------------------------------------------| -----------------| -------------| ----------------| |--------------| -----| --------------------------------------------------------| -----------------| -------------| ----------------|
|disabletrusted|dt|disable trusted player group for the server|False|!dt |Owner| |prune|pa|demote any admins that have not connected recently (defaults to 30 days)|False|!pa \<optional inactive days\>|Owner|
|enabletrusted|et|enable trusted player group for the server|False|!et |Owner|
|prune|p|demote any admins that have not connected recently (defaults to 30 days)|False|!p \<optional inactive days\>|Owner|
|quit|q|quit IW4MAdmin|False|!q |Owner| |quit|q|quit IW4MAdmin|False|!q |Owner|
|rcon|rcon|send rcon command to server|False|!rcon \<command\>|Owner| |rcon|rcon|send rcon command to server|False|!rcon \<command\>|Owner|
|reload|rl|reload configuration files|False|!rl |Owner|
|setlevel|sl|set player to specified administration level|True|!sl \<player\> \<level\>|Owner|
|ban|b|permanently ban a player from the server|True|!b \<player\> \<reason\>|SeniorAdmin| |ban|b|permanently ban a player from the server|True|!b \<player\> \<reason\>|SeniorAdmin|
|fredisable|frd|disable fast restarting at the end of a map|False|!frd |SeniorAdmin| |unban|ub|unban player by database id|True|!ub \<databaseID\> \<reason\>|SeniorAdmin|
|frenable|fre|enable fast restarting at the end of a map|False|!fre |SeniorAdmin|
|unban|ub|unban player by database id|True|!ub \<databaseID\>|SeniorAdmin|
|find|f|find player in database|False|!f \<player\>|Administrator| |find|f|find player in database|False|!f \<player\>|Administrator|
|findall|fa|find a player by their aliase(s)|False|!fa \<player\>|Administrator| |killserver|kill|kill the game server|False|!kill |Administrator|
|map|m|change to specified map|False|!m \<map\>|Administrator| |map|m|change to specified map|False|!m \<map\>|Administrator|
|maprotate|mr|cycle to the next map in rotation|False|!mr |Administrator| |maprotate|mr|cycle to the next map in rotation|False|!mr |Administrator|
|mask|hide|hide your online presence from online admin list|False|!hide |Administrator|
|plugins|p|view all loaded plugins|False|!p |Administrator| |plugins|p|view all loaded plugins|False|!p |Administrator|
|alias|known|get past aliases and ips of a player|True|!known \<player\>|Moderator| |alias|known|get past aliases and ips of a player|True|!known \<player\>|Moderator|
|baninfo|bi|get information about a ban for a player|True|!bi \<player\>|Moderator| |baninfo|bi|get information about a ban for a player|True|!bi \<player\>|Moderator|
|fastrestart|fr|fast restart current map|False|!fr |Moderator| |fastrestart|fr|fast restart current map|False|!fr |Moderator|
|flag|fp|flag a suspicious player and announce to admins on join|True|!fp \<player\> \<reason\>|Moderator| |flag|fp|flag a suspicious player and announce to admins on join|True|!fp \<player\> \<reason\>|Moderator|
|list|l|list active clients|False|!l |Moderator| |list|l|list active clients|False|!l |Moderator|
|mask|hide|hide your presence as an administrator|False|!hide |Moderator|
|reports|reps|get or clear recent reports|False|!reps \<optional clear\>|Moderator| |reports|reps|get or clear recent reports|False|!reps \<optional clear\>|Moderator|
|say|s|broadcast message to all players|False|!s \<message\>|Moderator| |say|s|broadcast message to all players|False|!s \<message\>|Moderator|
|setlevel|sl|set player to specified administration level|True|!sl \<player\> \<level\>|Moderator|
|setpassword|sp|set your authentication password|False|!sp \<password\>|Moderator|
|tempban|tb|temporarily ban a player for specified time (defaults to 1 hour)|True|!tb \<player\> \<duration (m\|h\|d\|w\|y)\> \<reason\>|Moderator| |tempban|tb|temporarily ban a player for specified time (defaults to 1 hour)|True|!tb \<player\> \<duration (m\|h\|d\|w\|y)\> \<reason\>|Moderator|
|uptime|up|get current application running time|False|!up |Moderator| |uptime|up|get current application running time|False|!up |Moderator|
|usage|us|get current application memory usage|False|!us |Moderator| |usage|us|get current application memory usage|False|!us |Moderator|
|kick|k|kick a player by name|True|!k \<player\> \<reason\>|Trusted| |kick|k|kick a player by name|True|!k \<player\> \<reason\>|Trusted|
|login|l|login using password|False|!l \<password\>|Trusted|
|warn|w|warn player for infringing rules|True|!w \<player\> \<reason\>|Trusted| |warn|w|warn player for infringing rules|True|!w \<player\> \<reason\>|Trusted|
|warnclear|wc|remove all warning for a player|True|!wc \<player\>|Trusted| |warnclear|wc|remove all warning for a player|True|!wc \<player\>|Trusted|
|admins|a|list currently connected admins|False|!a |User| |admins|a|list currently connected admins|False|!a |User|
|getexternalip|ip|view your external IP address|False|!ip |User| |getexternalip|ip|view your external IP address|False|!ip |User|
|help|h|list all available commands|False|!h \<optional command\>|User| |help|h|list all available commands|False|!h \<optional command\>|User|
|ping|pi|get client's ping|False|!pi \<optional client\>|User|
|privatemessage|pm|send message to other player|True|!pm \<player\> \<message\>|User| |privatemessage|pm|send message to other player|True|!pm \<player\> \<message\>|User|
|report|rep|report a player for suspicious behavior|True|!rep \<player\> \<reason\>|User| |report|rep|report a player for suspicious behavior|True|!rep \<player\> \<reason\>|User|
|resetstats|rs|reset your stats to factory-new|False|!rs |User| |resetstats|rs|reset your stats to factory-new|False|!rs |User|
|rules|r|list server rules|False|!r |User| |rules|r|list server rules|False|!r |User|
|stats|xlrstats|view your stats|False|!xlrstats \<optional player\>|User| |stats|xlrstats|view your stats|False|!xlrstats \<optional player\>|User|
|topstats|ts|view the top 5 players on this server|False|!ts |User| |topstats|ts|view the top 5 players on this server|False|!ts |User|
|vote|v|vote for the next map|False|!v \<map\>|User|
|votecancel|vc|cancel your vote for the next map|False|!vc |User|
|whoami|who|give information about yourself.|False|!who |User| |whoami|who|give information about yourself.|False|!who |User|
_These commands include all shipped plugin commands._
---
#### Player Identification #### Player Identification
All players are identified 4 seperate ways All players are identified 4 seperate ways
1. `npID/GUID/XUID` - The ID corresponding to the player's hardware or forum account 1. `npID/GUID/XUID` - The ID corresponding to the player's hardware or forum account
2. `IP` - The player's IP Address 2. `IP` - The player's IP Address
3. `Database ID` - The internal reference to a player, generated by IW4MAdmin 3. `Client ID` - The internal reference to a player, generated by **IW4MAdmin**
4. `Name` - The visible player name as it appears in game 4. `Name` - The visible player name as it appears in game
For most commands players are identified by their `Name` For most commands players are identified by their `Name`
However, if they are currently offline, or their name contains un-typable characters, their `Database ID` must be used However, if they are currently offline, or their name contains un-typable characters, their `Client ID` must be used
The `dbID` is specified by prefixing a player's reference number with `@`. The `Client ID` is specified by prefixing a player's reference number with `@`.
For example, `@123` would reference the player with a `dbID` of 123. For example, `@123` would reference the player with a `Client ID` of 123.
Players can also be referenced by `clientID`, which is simply their slot (0 - 17) While in-game, [layers can also be referenced by `Client Number`, which is simply their slot [0 - 17]
**All commands that require a `target` look at the `first argument` for a form of player identification** **All commands that require a `target` look at the `first argument` for a form of player identification**
---
#### Additional Command Parameters #### Additional Command Examples
`setlevel` `setlevel`
- _shortcut_ - `sl` - _shortcut_ - `sl`
- _Parameter 1_ - Player to modify level of - _Parameter 1_ - Player to modify level of
- _Parameter 2_ - Level to set the player to ```[ User, Trusted, Moderator, Administrator, SeniorAdmin ]``` - _Parameter 2_ - Level to set the player to ```[ User, Trusted, Moderator, Administrator, SeniorAdmin, Owner ]```
- _Example_ - `!setlevel Player1 SeniorAdmin`, `!sl @123 Moderator` - _Example_ - `!setlevel Player1 SeniorAdmin`, `!sl @123 Moderator`
- **NOTE** - It has been purposefully designed that there should only be **1 Owner** ( owner cannot set another player's level to owner unless the configuration option is enabled during setup) - **NOTE** - An `owner` cannot set another player's level to `owner` unless the configuration option is enabled during setup
`ban` `ban`
- _Shortcut_ - `b` - _Shortcut_ - `b`
@ -126,58 +170,57 @@ Players can also be referenced by `clientID`, which is simply their slot (0 - 17
___ ___
### Plugins ### Plugins
#### EventAPI
- This plugin adds a page to the webfront that serves JSON content in the form of server events
- The page is located at 127.0.0.1/api/events
- JSON Object Structure
* **eventCount** - Number of events in the generated response ( 0 or 1 )
* **Event** - The event object corresponding to generated event ( will be null if eventCount = 0 )
* _Version_ - The supported version of the Event Object ( IW4MAdmin = 0 )
* _Type_ - The type of Event Object ( Notification = 0, Status = 1, Alert = 2 )
* _Message_ - The string contents of the Event Object ( ie chat message text )
* _Title_ - The string header/title of the Event Object ( optional )
* _Origin_ - The string origin of the Event Object ( ie player name or sv_hostname )
* _Target_ - The string target of the Event Object ( ie reported player's name )
* _ID_ - The int ID of the Event Object ( will be unique unless two events are generated simultaneously )
- Optional Parameters
* appending a `GET` parameter of `status=1` to your request will generate a list of currently monitored servers
* For example: 127.0.0.1/api/events?status=1
* The contents of the response will be in the `Message` property of the response
- Each event will be consumed ( eaten by the request, so save the event if you need to use it later )
- The plugin additionally scans chat messages for phrases that indicate a cheater on the server
- If enough matching phrases are detected, an alert will be generated
- No commands are added by this plugin
- Additional Features will be added in the future
#### Welcome #### Welcome
- This plugin uses geolocation data to welcome a player based on their IP's country - This plugin uses geo-location data to welcome a player based on their country of origin
- All privileged users ( Trusted or higher ) recieve a specialized welcome message as well - All privileged users ( Trusted or higher ) receive a specialized welcome message as well
- Welcome messages can be customized in `WelcomePluginSettings.json`
#### Stats #### Stats
- This plugin calculates basic player performance, skill approximation, and kill/death ratio - This plugin calculates basic player performance, skill approximation, and kill/death ratio
- Total play-time is stored by this plugin
- After 3 days ( 36 hours ) of total play-time, a user earns the `Trusted` rank ( will be optional in a later release )
**Commands added by this plugin** **Commands added by this plugin**
|Name |Alias|Description |Requires Target|Syntax |Required Level| |Name |Alias|Description |Requires Target|Syntax |Required Level|
|--------------| -----| --------------------------------------------------------| -----------------| -------------| ----------------| |--------------| -----| --------------------------------------------------------| -----------------| -------------| ----------------|
|disabletrusted|dt|disable trusted player group for the server|False|!dt |Owner|
|enabletrusted|et|enable trusted player group for the server|False|!et |Owner|
|prune|p|demote any admins that have not connected recently (defaults to 30 days)|False|!p \<optional inactive days\>|Owner|
|resetstats|rs|reset your stats to factory-new|False|!rs |User| |resetstats|rs|reset your stats to factory-new|False|!rs |User|
|stats|xlrstats|view your stats|False|!xlrstats \<optional player\>|User| |stats|xlrstats|view your stats|False|!xlrstats \<optional player\>|User|
|topstats|ts|view the top 5 players on this server|False|!ts |User| |topstats|ts|view the top 5 players on this server|False|!ts |User|
- To qualify for top stats, a player must meet the following criteria - To qualify for top stats, a client must have played for at least `1 hour` and connected within the past `30 days`.
* `Skill` > 10
* `Kills` > 150
* `Play Time` > 1 hour
- Each server has seperated stats and can be reset by deleting `stats_<port>.rm` #### Login
- This plugin deters GUID spoofing by requiring privileged users to login with their password before executing commands
- A password must be set using the `setpassword` command before logging in
**Commands added by this plugin**
|Name |Alias|Description |Requires Target|Syntax |Required Level|
|--------------| -----| --------------------------------------------------------| -----------------| -------------| ----------------|
|login|l|login using password|False|!l \<password\>|Trusted|
#### Profanity Determent
- This plugin warns and kicks players for using profanity
- Profane words and warning message can be specified in `ProfanityDetermentSettings.json`
___ ___
### Webfront
`Home`
* Shows an overview of the monitored server(s)
`Penalties`
* Shows a chronological ordered list of client penalties (scrolling down loads older penalties)
`Admins`
* Shows a list of privileged clients
`Login`
* Allows privileged users to login using their `Client ID` and password set via `setpassword`
`Profile`
* Shows a client's information and history
---
### Misc ### Misc
#### Database Storage #### Database Storage
All unique client information is stored in `clients.rm`. Should you need to reset your database, this file can simply be deleted. All **IW4MAdmin** information is stored in `Database.db`. Should you need to reset your database, this file can simply be deleted. Additionally, this file should be preserved during updates to retain client information.
Player aliases and previous ips are stored in `aliases.rm`.

View File

@ -39,7 +39,7 @@ namespace SharedLibraryCore
if (fileName != string.Empty) if (fileName != string.Empty)
{ {
Name = fileName; Name = fileName;
Handle = new StreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, true), Encoding.UTF7); Handle = new StreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, true), Encoding.UTF8);
sze = Handle.BaseStream.Length; sze = Handle.BaseStream.Length;
} }

View File

@ -267,6 +267,11 @@ namespace SharedLibraryCore
return Game.UKN; return Game.UKN;
} }
public static string EscapeMarkdown(this string markdownString)
{
return markdownString.Replace("<", "\\<").Replace(">", "\\>").Replace("|", "\\|");
}
public static TimeSpan ParseTimespan(this string input) public static TimeSpan ParseTimespan(this string input)
{ {
var expressionMatch = Regex.Match(input, @"[0-9]+.\b"); var expressionMatch = Regex.Match(input, @"[0-9]+.\b");