Compare commits

...

No commits in common. "v2.0.3" and "master" have entirely different histories.

49 changed files with 4813 additions and 660 deletions

View File

@ -1,12 +1,23 @@
image: fedora/latest
packages:
- dotnet
- wget
- unzip
sources:
- https://git.sr.ht/~jkcclemens/NoSoliciting
secrets:
- 92fe0dd0-db40-41e0-903a-a18489f75548
tasks:
- build: |
- download-dalamud: |
mkdir dalamud
cd dalamud
wget https://github.com/goatcorp/dalamud-distrib/raw/main/latest.zip
unzip latest.zip
rm latest.zip
- build-plugin: |
cd NoSoliciting/NoSoliciting
dotnet build -c Release -p:IsCI=true
- build-trainer: |
cd NoSoliciting/NoSoliciting.Trainer
dotnet build -c Release
- test: |
@ -22,4 +33,5 @@ tasks:
ssh actions@warm.kitteh.space model update "$(git rev-parse HEAD)" < NoSoliciting.Trainer/model.zip
fi
artifacts:
- NoSoliciting/NoSoliciting/bin/Release/net5-windows/NoSoliciting/latest.zip
- NoSoliciting/NoSoliciting.Trainer/model.zip

1
.gitattributes vendored
View File

@ -1,2 +1,3 @@
* text eol=lf
*.dll binary
*.png binary

View File

@ -1,32 +0,0 @@
using System.Text;
using JKang.IpcServiceFramework;
using JKang.IpcServiceFramework.Services;
using Newtonsoft.Json;
namespace NoSoliciting.Interface {
public class BetterIpcSerialiser : IIpcMessageSerializer {
private static readonly JsonSerializerSettings Settings = new() {
TypeNameHandling = TypeNameHandling.Auto,
};
public byte[] SerializeRequest(IpcRequest request) {
var json = JsonConvert.SerializeObject(request, Formatting.None, Settings);
return Encoding.UTF8.GetBytes(json);
}
public IpcResponse? DeserializeResponse(byte[] binary) {
var json = Encoding.UTF8.GetString(binary);
return JsonConvert.DeserializeObject<IpcResponse>(json, Settings);
}
public IpcRequest? DeserializeRequest(byte[] binary) {
var json = Encoding.UTF8.GetString(binary);
return JsonConvert.DeserializeObject<IpcRequest>(json, Settings);
}
public byte[] SerializeResponse(IpcResponse response) {
var json = JsonConvert.SerializeObject(response, Formatting.None, Settings);
return Encoding.UTF8.GetBytes(json);
}
}
}

View File

@ -6,9 +6,8 @@ using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.ML.Data;
using Microsoft.ML.Transforms;
using NoSoliciting.Interface;
namespace NoSoliciting.Internal.Interface {
namespace NoSoliciting.Interface {
[SuppressMessage("ReSharper", "UnusedMember.Global")]
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
@ -127,7 +126,7 @@ namespace NoSoliciting.Internal.Interface {
var normalised = NoSolUtil.Normalise(this.Message);
output.PartyFinder = this.Channel == 0;
output.Shout = this.Channel == 11 || this.Channel == 30;
output.Shout = this.Channel is 11 or 30;
output.ContainsWard = WardWords.Any(word => word.IsMatch(normalised));
output.ContainsPlot = PlotWords.Any(word => word.IsMatch(normalised));
output.ContainsHousingNumbers = NumbersRegex.IsMatch(normalised);
@ -145,7 +144,7 @@ namespace NoSoliciting.Internal.Interface {
public string Category { get; set; } = "UNKNOWN";
[ColumnName("Score")]
public float[] Probabilities { get; set; } = new float[0];
public float[] Probabilities { get; set; } = Array.Empty<float>();
}
internal static class Ext {

View File

@ -1,5 +1,7 @@
namespace NoSoliciting.Interface {
public interface IClassifier {
using System;
namespace NoSoliciting.Interface {
public interface IClassifier : IDisposable {
void Initialise(byte[] data);
string Classify(ushort channel, string message);

View File

@ -58,7 +58,7 @@ namespace NoSoliciting.Interface {
'~', '-',
};
private static string Spacify(string input) {
public static string Spacify(this string input) {
return SpaceSymbols.Aggregate(input, (current, sym) => current.Replace(sym, ' '));
}
@ -114,7 +114,7 @@ namespace NoSoliciting.Interface {
var normalised = input.Normalize(NormalizationForm.FormKD);
// replace several symbols with spaces instead
return spacify ? Spacify(normalised) : normalised;
return spacify ? normalised.Spacify() : normalised;
}
}
}

4
NoSoliciting.Interface/NoSoliciting.Interface.csproj Normal file → Executable file
View File

@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;net5</TargetFrameworks>
<TargetFramework>net7</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JKang.IpcServiceFramework.Core" Version="3.1.0" />
<PackageReference Include="Microsoft.ML" Version="2.0.1" />
</ItemGroup>
</Project>

View File

@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net5;net48</TargetFrameworks>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.ML" Version="1.5.5"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NoSoliciting.Interface\NoSoliciting.Interface.csproj"/>
</ItemGroup>
</Project>

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Costura>
<ExcludeAssemblies>
Costura
CpuMathNative
LdaNative
</ExcludeAssemblies>
</Costura>
</Weavers>

View File

@ -1,31 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net48</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<Version>1.1.0</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\NoSoliciting.Interface\NoSoliciting.Interface.csproj" />
<ProjectReference Include="..\NoSoliciting.Internal.Interface\NoSoliciting.Internal.Interface.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Costura.Fody" Version="4.1.0" PrivateAssets="all" />
<PackageReference Include="Fody" Version="6.5.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="JKang.IpcServiceFramework.Hosting.NamedPipe" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
<PackageReference Include="Microsoft.ML" Version="1.5.5" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="costura64\*.dll" />
</ItemGroup>
</Project>

View File

@ -1,87 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using JKang.IpcServiceFramework.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.ML;
using NoSoliciting.Interface;
using NoSoliciting.Internal.Interface;
namespace NoSoliciting.MessageClassifier {
internal static class Program {
private static void Main(string[] args) {
if (!int.TryParse(args[0], out var gamePid)) {
Console.WriteLine("No game PID provided.");
return;
}
var gameName = args[1];
var pipeId = args[2];
var host = Host.CreateDefaultBuilder()
.ConfigureServices(services => {
services.AddSingleton<IClassifier, ClassifierService>();
})
.ConfigureIpcHost(builder => {
builder.AddNamedPipeEndpoint<IClassifier>(options => {
options.PipeName = $"NoSoliciting.MessageClassifier-{pipeId}";
options.Serializer = new BetterIpcSerialiser();
});
})
.Build();
Task.Run(async () => {
while (true) {
Process process;
try {
process = Process.GetProcessById(gamePid);
} catch (Exception) {
await host.StopAsync();
return;
}
if (process.ProcessName != gameName) {
await host.StopAsync();
return;
}
await Task.Delay(5_000);
}
});
host.Run();
}
}
internal class ClassifierService : IClassifier, IDisposable {
private MLContext Context { get; set; } = null!;
private ITransformer Model { get; set; } = null!;
private DataViewSchema Schema { get; set; } = null!;
private PredictionEngine<Data, Prediction>? PredictionEngine { get; set; }
public void Initialise(byte[] data) {
if (this.PredictionEngine != null) {
this.PredictionEngine.Dispose();
this.PredictionEngine = null;
}
this.Context = new MLContext();
this.Context.ComponentCatalog.RegisterAssembly(typeof(Data).Assembly);
using var stream = new MemoryStream(data);
var model = this.Context.Model.Load(stream, out var schema);
this.Model = model;
this.Schema = schema;
this.PredictionEngine = this.Context.Model.CreatePredictionEngine<Data, Prediction>(this.Model, this.Schema);
}
public string Classify(ushort channel, string message) {
return this.PredictionEngine?.Predict(new Data(channel, message))?.Category ?? "UNKNOWN";
}
public void Dispose() {
this.PredictionEngine?.Dispose();
}
}
}

View File

@ -2,20 +2,19 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5</TargetFramework>
<TargetFramework>net7</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ConsoleTables" Version="2.4.2"/>
<PackageReference Include="CsvHelper" Version="27.0.1"/>
<PackageReference Include="Microsoft.ML" Version="1.5.5"/>
<PackageReference Include="ConsoleTables" Version="2.5.0"/>
<PackageReference Include="CsvHelper" Version="30.0.1"/>
<PackageReference Include="Microsoft.ML" Version="2.0.1"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NoSoliciting.Interface\NoSoliciting.Interface.csproj"/>
<ProjectReference Include="..\NoSoliciting.Internal.Interface\NoSoliciting.Internal.Interface.csproj"/>
</ItemGroup>
</Project>

View File

@ -1,21 +1,27 @@
using System;
using System.Buffers.Text;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using ConsoleTables;
using CsvHelper;
using CsvHelper.Configuration;
using Microsoft.ML;
using Microsoft.ML.Data;
using Microsoft.ML.Transforms.Text;
using NoSoliciting.Internal.Interface;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using NoSoliciting.Interface;
namespace NoSoliciting.Trainer {
internal static class Program {
private static readonly string[] StopWords = {
"discord",
"gg",
"twitch",
"tv",
"lgbt",
"lgbtq",
"lgbtqia",
@ -34,6 +40,20 @@ namespace NoSoliciting.Trainer {
CreateModel,
Interactive,
InteractiveFull,
Normalise,
}
[Serializable]
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
private class ReportInput {
public uint ReportVersion { get; } = 2;
public uint ModelVersion { get; set; }
public DateTime Timestamp { get; set; }
public ushort Type { get; set; }
public List<byte> Sender { get; set; }
public List<byte> Content { get; set; }
public string? Reason { get; set; }
public string? SuggestedClassification { get; set; }
}
private static void Main(string[] args) {
@ -42,8 +62,23 @@ namespace NoSoliciting.Trainer {
"create-model" => Mode.CreateModel,
"interactive" => Mode.Interactive,
"interactive-full" => Mode.InteractiveFull,
"normalise" => Mode.Normalise,
_ => throw new ArgumentException("invalid argument"),
};
if (mode == Mode.Normalise) {
Console.WriteLine("Ready");
while (true) {
Console.Write("> ");
var input = Console.ReadLine();
var bytes = Convert.FromBase64String(input!);
var toNormalise = Encoding.UTF8.GetString(bytes);
var normalised = NoSolUtil.Normalise(toNormalise);
Console.WriteLine(normalised);
}
}
var path = "../../../data.csv";
if (args.Length > 1) {
@ -139,7 +174,9 @@ namespace NoSoliciting.Trainer {
.Append(ctx.Transforms.Conversion.ConvertType("HasPlot", nameof(Data.Computed.ContainsPlot)))
.Append(ctx.Transforms.Conversion.ConvertType("HasNumbers", nameof(Data.Computed.ContainsHousingNumbers)))
.Append(ctx.Transforms.Concatenate("Features", "FeaturisedMessage", "CPartyFinder", "CShout", "CTrade", "HasWard", "HasPlot", "HasNumbers", "CSketch"))
.Append(ctx.MulticlassClassification.Trainers.SdcaMaximumEntropy(exampleWeightColumnName: "Weight"))
// macro 81.8 micro 84.6 (Tf weighting) - slow
// .Append(ctx.MulticlassClassification.Trainers.SdcaMaximumEntropy(exampleWeightColumnName: "Weight", l1Regularization: 0, l2Regularization: 0, maximumNumberOfIterations: 2_500))
.Append(ctx.MulticlassClassification.Trainers.SdcaMaximumEntropy(exampleWeightColumnName: "Weight", l1Regularization: 0, l2Regularization: 0, maximumNumberOfIterations: null))
.Append(ctx.Transforms.Conversion.MapKeyToValue("PredictedLabel"));
var train = mode switch {
@ -217,6 +254,12 @@ namespace NoSoliciting.Trainer {
continue;
}
var size = Base64.GetMaxDecodedFromUtf8Length(parts[1].Length);
var buf = new byte[size];
if (Convert.TryFromBase64String(parts[1], buf, out var written)) {
parts[1] = Encoding.UTF8.GetString(buf[..written]);
}
var input = new Data(channel, parts[1]);
var pred = predEngine.Predict(input);

View File

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace NoSoliciting.Trainer {
public static class XivString {
private const byte Start = 2;
private const byte End = 3;
public static string GetText(IEnumerable<byte> bytes) {
var stringBytes = new List<byte>();
var reader = new BinaryReader(new MemoryStream(bytes.ToArray()));
while (reader.BaseStream.Position < reader.BaseStream.Length) {
var b = reader.ReadByte();
if (b == Start) {
reader.ReadByte(); // kind
var len = GetInteger(reader); // data length
reader.ReadBytes((int) len); // data
var end = reader.ReadByte(); // end
if (end != End) {
throw new ArgumentException("Input was not a valid XivString");
}
continue;
}
stringBytes.Add(b);
}
return Encoding.UTF8.GetString(stringBytes.ToArray());
}
// Thanks, Dalamud
public static uint GetInteger(BinaryReader input) {
uint marker = input.ReadByte();
if (marker < 0xD0) {
return marker - 1;
}
// the game adds 0xF0 marker for values >= 0xCF
// uasge of 0xD0-0xEF is unknown, should we throw here?
// if (marker < 0xF0) throw new NotSupportedException();
marker = (marker + 1) & 0b1111;
var ret = new byte[4];
for (var i = 3; i >= 0; i--) {
ret[i] = (marker & (1 << i)) == 0 ? (byte) 0 : input.ReadByte();
}
return BitConverter.ToUInt32(ret, 0);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -11,10 +11,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NoSoliciting.Interface", "N
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NoSoliciting.Trainer", "NoSoliciting.Trainer\NoSoliciting.Trainer.csproj", "{3D774127-F7A9-4B6D-AB2F-3AAF80D15586}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NoSoliciting.MessageClassifier", "NoSoliciting.MessageClassifier\NoSoliciting.MessageClassifier.csproj", "{16689469-6A74-4197-818A-EA44697BD815}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NoSoliciting.Internal.Interface", "NoSoliciting.Internal.Interface\NoSoliciting.Internal.Interface.csproj", "{742F1B3F-030F-4886-B05D-0D41D4DDA8FD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -33,14 +29,6 @@ Global
{3D774127-F7A9-4B6D-AB2F-3AAF80D15586}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3D774127-F7A9-4B6D-AB2F-3AAF80D15586}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3D774127-F7A9-4B6D-AB2F-3AAF80D15586}.Release|Any CPU.Build.0 = Release|Any CPU
{16689469-6A74-4197-818A-EA44697BD815}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{16689469-6A74-4197-818A-EA44697BD815}.Debug|Any CPU.Build.0 = Debug|Any CPU
{16689469-6A74-4197-818A-EA44697BD815}.Release|Any CPU.ActiveCfg = Release|Any CPU
{16689469-6A74-4197-818A-EA44697BD815}.Release|Any CPU.Build.0 = Release|Any CPU
{742F1B3F-030F-4886-B05D-0D41D4DDA8FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{742F1B3F-030F-4886-B05D-0D41D4DDA8FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{742F1B3F-030F-4886-B05D-0D41D4DDA8FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{742F1B3F-030F-4886-B05D-0D41D4DDA8FD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -8,23 +8,23 @@ namespace NoSoliciting {
internal Commands(Plugin plugin) {
this.Plugin = plugin;
this.Plugin.Interface.CommandManager.AddHandler("/prmt", new CommandInfo(this.OnCommand) {
this.Plugin.CommandManager.AddHandler("/prmt", new CommandInfo(this.OnCommand) {
HelpMessage = "Opens the NoSoliciting configuration (deprecated)",
ShowInHelp = false,
});
this.Plugin.Interface.CommandManager.AddHandler("/nosol", new CommandInfo(this.OnCommand) {
this.Plugin.CommandManager.AddHandler("/nosol", new CommandInfo(this.OnCommand) {
HelpMessage = "Opens the NoSoliciting configuration",
});
}
public void Dispose() {
this.Plugin.Interface.CommandManager.RemoveHandler("/nosol");
this.Plugin.Interface.CommandManager.RemoveHandler("/prmt");
this.Plugin.CommandManager.RemoveHandler("/nosol");
this.Plugin.CommandManager.RemoveHandler("/prmt");
}
private void OnCommand(string command, string args) {
if (command == "/prmt") {
this.Plugin.Interface.Framework.Gui.Chat.PrintError($"[{this.Plugin.Name}] The /prmt command is deprecated and will be removed. Please use /nosol instead.");
this.Plugin.ChatGui.PrintError($"[{Plugin.Name}] The /prmt command is deprecated and will be removed. Please use /nosol instead.");
}
if (args == "report") {

View File

@ -1,9 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using NoSoliciting.Interface;
using Dalamud.ContextMenu;
using NoSoliciting.Resources;
using XivCommon.Functions.ContextMenu;
namespace NoSoliciting {
public class ContextMenu : IDisposable {
@ -12,14 +10,14 @@ namespace NoSoliciting {
internal ContextMenu(Plugin plugin) {
this.Plugin = plugin;
this.Plugin.Common.Functions.ContextMenu.OpenContextMenu += this.OnOpenContextMenu;
this.Plugin.DalamudContextMenu.OnOpenGameObjectContextMenu += this.OnOpenContextMenu;
}
public void Dispose() {
this.Plugin.Common.Functions.ContextMenu.OpenContextMenu -= this.OnOpenContextMenu;
this.Plugin.DalamudContextMenu.OnOpenGameObjectContextMenu -= this.OnOpenContextMenu;
}
private void OnOpenContextMenu(ContextMenuOpenArgs args) {
private void OnOpenContextMenu(GameObjectContextMenuOpenArgs args) {
if (args.ParentAddonName != "LookingForGroup") {
return;
}
@ -29,10 +27,10 @@ namespace NoSoliciting {
}
var label = Language.ReportToNoSoliciting;
args.Items.Add(new NormalContextMenuItem(label, this.Report));
args.AddCustomItem(new GameObjectContextMenuItem(label, this.Report));
}
private void Report(ContextMenuItemSelectedArgs args) {
private void Report(GameObjectContextMenuItemSelectedArgs args) {
if (args.ContentIdLower == 0) {
return;
}
@ -50,21 +48,7 @@ namespace NoSoliciting {
return;
}
Task.Run(async () => {
var status = await this.Plugin.Ui.Report.ReportMessageAsync(message);
switch (status) {
case ReportStatus.Successful: {
var msg = Language.ReportToastSuccess;
this.Plugin.Interface.Framework.Gui.Toast.ShowNormal(string.Format(msg, listing.Name));
break;
}
case ReportStatus.Failure: {
var msg = Language.ReportToastFailure;
this.Plugin.Interface.Framework.Gui.Toast.ShowError(string.Format(msg, listing.Name));
break;
}
}
});
this.Plugin.Ui.Report.ToShowModal = message;
}
}
}

View File

@ -4,7 +4,6 @@
OutputPath="$(OutputPath)"
AssemblyName="$(AssemblyName)"
VersionComponents="3"
MakeZip="true"
Include="NoSoliciting.json;NoSoliciting.dll;NoSoliciting.pdb"/>
MakeZip="true"/>
</Target>
</Project>

View File

@ -1,9 +1,8 @@
using Dalamud.Plugin;
using System;
using Dalamud.Game.Internal.Gui;
using Dalamud.Game.Internal.Gui.Structs;
using System;
using Dalamud.Game.Gui.PartyFinder.Types;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using NoSoliciting.Interface;
using NoSoliciting.Ml;
namespace NoSoliciting {
@ -47,8 +46,8 @@ namespace NoSoliciting {
public Filter(Plugin plugin) {
this.Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "Plugin cannot be null");
this.Plugin.Interface.Framework.Gui.Chat.OnCheckMessageHandled += this.OnChat;
this.Plugin.Interface.Framework.Gui.PartyFinder.ReceiveListing += this.OnListing;
this.Plugin.ChatGui.CheckMessageHandled += this.OnChat;
this.Plugin.PartyFinderGui.ReceiveListing += this.OnListing;
}
private void Dispose(bool disposing) {
@ -57,8 +56,8 @@ namespace NoSoliciting {
}
if (disposing) {
this.Plugin.Interface.Framework.Gui.Chat.OnCheckMessageHandled -= this.OnChat;
this.Plugin.Interface.Framework.Gui.PartyFinder.ReceiveListing -= this.OnListing;
this.Plugin.ChatGui.CheckMessageHandled -= this.OnChat;
this.Plugin.PartyFinderGui.ReceiveListing -= this.OnListing;
}
this._disposedValue = true;
@ -83,33 +82,31 @@ namespace NoSoliciting {
this.LastBatch = args.BatchNumber;
var version = this.Plugin.MlFilter?.Version;
var reason = this.MlListingFilterReason(listing);
if (version == null) {
return;
}
var (category, reason) = this.MlListingFilterReason(listing);
this.Plugin.AddPartyFinderHistory(new Message(
version.Value,
version,
ChatType.None,
listing.ContentIdLower,
listing.Name,
listing.Description,
true,
reason
category,
reason == "custom",
reason == "ilvl",
this.Plugin.Config.CreateFiltersClone()
));
if (reason == null) {
if (category == null && reason == null) {
return;
}
args.Visible = false;
if (this.Plugin.Config.LogFilteredPfs) {
PluginLog.Log($"Filtered PF listing from {listing.Name.TextValue} ({reason}): {listing.Description.TextValue}");
Plugin.Log.Info($"Filtered PF listing from {listing.Name.TextValue} ({reason}): {listing.Description.TextValue}");
}
} catch (Exception ex) {
PluginLog.LogError($"Error in PF listing event: {ex}");
Plugin.Log.Error($"Error in PF listing event: {ex}");
}
}
@ -122,10 +119,6 @@ namespace NoSoliciting {
}
private bool MlFilterMessage(XivChatType type, uint senderId, SeString sender, SeString message) {
if (this.Plugin.MlFilter == null) {
return false;
}
var chatType = ChatTypeExt.FromDalamud(type);
// NOTE: don't filter on user-controlled chat types here because custom filters are supposed to check all
@ -136,80 +129,86 @@ namespace NoSoliciting {
var text = message.TextValue;
string? reason = null;
var custom = false;
MessageCategory? classification = null;
// step 1. check for custom filters if enabled
var filter = this.Plugin.Config.CustomChatFilter
&& Chat.MatchesCustomFilters(text, this.Plugin.Config)
&& SetReason(out reason, "custom");
var filter = false;
if (this.Plugin.Config.CustomChatFilter && Chat.MatchesCustomFilters(text, this.Plugin.Config)) {
filter = true;
custom = true;
}
// only look at ml if message >= min words
if (!filter && text.Trim().Split(' ').Length >= MinWords) {
if (!filter && this.Plugin.MlFilter != null && CountWords(text) >= MinWords) {
// step 2. classify the message using the model
var category = this.Plugin.MlFilter.ClassifyMessage((ushort) chatType, text);
// step 2a. only filter if configured to act on this channel
filter = category != MessageCategory.Normal
&& this.Plugin.Config.MlEnabledOn(category, chatType)
&& SetReason(out reason, category.Name());
if (category != MessageCategory.Normal && this.Plugin.Config.MlEnabledOn(category, chatType)) {
filter = true;
classification = category;
}
}
this.Plugin.AddMessageHistory(new Message(
this.Plugin.MlFilter.Version,
var history = new Message(
this.Plugin.MlFilter?.Version,
ChatTypeExt.FromDalamud(type),
senderId,
sender,
message,
true,
reason
));
classification,
custom,
false,
this.Plugin.Config.CreateFiltersClone()
);
this.Plugin.AddMessageHistory(history);
if (filter && this.Plugin.Config.LogFilteredChat) {
PluginLog.Log($"Filtered chat message ({reason}): {text}");
Plugin.Log.Info($"Filtered chat message ({history.FilterReason ?? "unknown"}): {text}");
}
return filter;
}
private string? MlListingFilterReason(PartyFinderListing listing) {
private (MessageCategory?, string?) MlListingFilterReason(PartyFinderListing listing) {
if (this.Plugin.MlFilter == null) {
return null;
return (null, null);
}
// ignore private listings if configured
if (!this.Plugin.Config.ConsiderPrivatePfs && listing[SearchAreaFlags.Private]) {
return null;
return (null, null);
}
// step 1. check if pf has an item level that's too high
if (this.Plugin.Config.FilterHugeItemLevelPFs && listing.MinimumItemLevel > FilterUtil.MaxItemLevelAttainable(this.Plugin.DataManager)) {
return (null, "ilvl");
}
var desc = listing.Description.TextValue;
// step 1. check if pf has an item level that's too high
if (this.Plugin.Config.FilterHugeItemLevelPFs && listing.MinimumItemLevel > FilterUtil.MaxItemLevelAttainable(this.Plugin.Interface.Data)) {
return "ilvl";
}
// step 2. check custom filters
if (this.Plugin.Config.CustomPFFilter && PartyFinder.MatchesCustomFilters(desc, this.Plugin.Config)) {
return "custom";
return (null, "custom");
}
// only look at ml for pfs >= min words
if (desc.Trim().Split(' ').Length < MinWords) {
return null;
if (CountWords(desc) < MinWords) {
return (null, null);
}
var category = this.Plugin.MlFilter.ClassifyMessage((ushort) ChatType.None, desc);
if (category != MessageCategory.Normal && this.Plugin.Config.MlEnabledOn(category, ChatType.None)) {
return category.Name();
return (category, Enum.GetName(category));
}
return null;
return (null, null);
}
private static bool SetReason(out string reason, string value) {
reason = value;
return true;
private static int CountWords(string text) {
return text.Spacify().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Length;
}
}
}

View File

@ -1,10 +1,10 @@
using Dalamud.Data;
using Lumina.Excel.GeneratedSheets;
using Lumina.Excel.GeneratedSheets;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using Dalamud.Plugin.Services;
using NoSoliciting.Ml;
namespace NoSoliciting {
public static class FilterUtil {
@ -87,7 +87,7 @@ namespace NoSoliciting {
return null;
}
public static int MaxItemLevelAttainable(DataManager data) {
public static int MaxItemLevelAttainable(IDataManager data) {
if (MaxItemLevel > 0) {
return MaxItemLevel;
}
@ -98,7 +98,7 @@ namespace NoSoliciting {
var ilvls = new Dictionary<Slot, int>();
foreach (var item in data.GetExcelSheet<Item>()) {
foreach (var item in data.GetExcelSheet<Item>()!) {
var slot = SlotFromItem(item);
if (slot == null) {
continue;
@ -123,9 +123,13 @@ namespace NoSoliciting {
}
}
public static class RmtExtensions {
public static class Extensions {
public static bool ContainsIgnoreCase(this string haystack, string needle) {
return CultureInfo.InvariantCulture.CompareInfo.IndexOf(haystack, needle, CompareOptions.IgnoreCase) >= 0;
}
public static bool WasEnabled(this IEnumerable<MessageCategory> enabled, MessageCategory category) {
return category == MessageCategory.Normal || enabled.Contains(category);
}
}
}

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Resourcer/>
<ResourcesMerge/>
</Weavers>

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="ILRepacker" AfterTargets="Build">
<ItemGroup>
<InputAssemblies Include="$(OutputPath)\NoSoliciting.dll"/>
<InputAssemblies Include="$(OutputPath)\NoSoliciting.Interface.dll"/>
<InputAssemblies Include="$(OutputPath)\J*.dll"/>
<InputAssemblies Include="$(OutputPath)\M*.dll"/>
<InputAssemblies Include="$(OutputPath)\S*.dll"/>
<InputAssemblies Include="$(OutputPath)\Y*.dll"/>
<InputAssemblies Include="$(OutputPath)\XivCommon.dll"/>
</ItemGroup>
<ILRepack
Parallel="true"
Internalize="true"
InputAssemblies="@(InputAssemblies)"
LibraryPath="$(OutputPath);$(AppData)\XIVLauncher\addon\Hooks\dev"
TargetKind="Dll"
OutputFile="$(OutputPath)\$(AssemblyName).dll"
/>
</Target>
</Project>

View File

@ -13,11 +13,11 @@ namespace NoSoliciting.Interface {
this.Settings = new Settings(plugin, this);
this.Report = new Report(plugin);
this.Plugin.Interface.UiBuilder.OnBuildUi += this.Draw;
this.Plugin.Interface.UiBuilder.Draw += this.Draw;
}
public void Dispose() {
this.Plugin.Interface.UiBuilder.OnBuildUi -= this.Draw;
this.Plugin.Interface.UiBuilder.Draw -= this.Draw;
this.Settings.Dispose();
}

View File

@ -3,14 +3,17 @@ using System.Globalization;
using System.Linq;
using System.Net;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Plugin;
using ImGuiNET;
using NoSoliciting.Ml;
using NoSoliciting.Resources;
#if DEBUG
using System.Text;
#endif
namespace NoSoliciting.Interface {
public class Report {
private const ImGuiTableFlags TableFlags = ImGuiTableFlags.Borders
@ -34,6 +37,8 @@ namespace NoSoliciting.Interface {
set => this._showReporting = value;
}
internal Message? ToShowModal { get; set; }
public Report(Plugin plugin) {
this.Plugin = plugin;
}
@ -47,14 +52,22 @@ namespace NoSoliciting.Interface {
}
public void Draw() {
var toShow = this.ToShowModal;
if (toShow != null) {
if (!this.SetUpReportModal(toShow)) {
ImGui.OpenPopup($"###modal-message-{toShow.Id}");
}
}
if (!this.ShowReporting) {
return;
}
ImGui.SetNextWindowSize(new Vector2(1_000, 350), ImGuiCond.FirstUseEver);
var windowTitle = string.Format(Language.Reporting, this.Plugin.Name);
var windowTitle = string.Format(Language.Reporting, Plugin.Name);
if (!ImGui.Begin($"{windowTitle}###NoSoliciting reporting", ref this._showReporting)) {
ImGui.End();
return;
}
@ -102,7 +115,7 @@ namespace NoSoliciting.Interface {
foreach (var message in this.Plugin.MessageHistory) {
ImGui.TableNextRow();
if (message.FilterReason != null) {
if (message.Filtered) {
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(238f / 255f, 71f / 255f, 71f / 255f, 1f));
}
@ -112,11 +125,11 @@ namespace NoSoliciting.Interface {
.Select(payload => payload.Text)
.FirstOrDefault() ?? "";
if (AddRow(message.Timestamp.ToString(CultureInfo.CurrentCulture), message.ChatType.Name(this.Plugin.Interface.Data), message.FilterReason ?? "", sender, message.Content.TextValue)) {
if (AddRow(message.Timestamp.ToString(CultureInfo.CurrentCulture), message.ChatType.Name(this.Plugin.DataManager), message.FilterReason ?? string.Empty, sender, message.Content.TextValue)) {
ImGui.OpenPopup($"###modal-message-{message.Id}");
}
if (message.FilterReason != null) {
if (message.Filtered) {
ImGui.PopStyleColor();
}
@ -142,7 +155,7 @@ namespace NoSoliciting.Interface {
var builder = new StringBuilder();
foreach (var message in this.Plugin.PartyFinderHistory) {
if (message.FilterReason == null) {
if (message.Classification == null) {
continue;
}
@ -165,7 +178,7 @@ namespace NoSoliciting.Interface {
foreach (var message in this.Plugin.PartyFinderHistory) {
ImGui.TableNextRow();
if (message.FilterReason != null) {
if (message.Filtered) {
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(238f / 255f, 71f / 255f, 71f / 255f, 1f));
}
@ -175,11 +188,11 @@ namespace NoSoliciting.Interface {
.Select(payload => payload.Text)
.FirstOrDefault() ?? "";
if (AddRow(message.Timestamp.ToString(CultureInfo.CurrentCulture), message.FilterReason ?? "", sender, message.Content.TextValue)) {
if (AddRow(message.Timestamp.ToString(CultureInfo.CurrentCulture), message.FilterReason ?? string.Empty, sender, message.Content.TextValue)) {
ImGui.OpenPopup($"###modal-message-{message.Id}");
}
if (message.FilterReason != null) {
if (message.Filtered) {
ImGui.PopStyleColor();
}
@ -197,50 +210,139 @@ namespace NoSoliciting.Interface {
#region Modal
private void SetUpReportModal(Message message) {
private MessageCategory? _reportCategory;
private bool _other;
/// <summary>
///
/// </summary>
/// <param name="message"></param>
/// <returns>true if modal is closing</returns>
private bool SetUpReportModal(Message message) {
var closing = false;
ImGui.SetNextWindowSize(new Vector2(350, -1));
var modalTitle = string.Format(Language.ReportModalTitle, this.Plugin.Name);
var modalTitle = string.Format(Language.ReportModalTitle, Plugin.Name);
if (!ImGui.BeginPopupModal($"{modalTitle}###modal-message-{message.Id}")) {
return;
return false;
}
if (this._reportCategory == null && !this._other) {
if (message.Classification != null) {
this._reportCategory = message.Classification;
} else if (message.Classification == null && !message.Custom && !message.ItemLevel) {
this._reportCategory = MessageCategory.Normal;
}
}
ImGui.PushTextWrapPos();
ImGui.TextUnformatted(Language.ReportModalHelp1);
ImGui.TextUnformatted(message.FilterReason != null
? Language.ReportModalWasFiltered
: Language.ReportModalWasNotFiltered);
ImGui.Separator();
ImGui.TextUnformatted(message.Content.TextValue);
ImGui.Separator();
ImGui.TextUnformatted(string.Format(Language.ReportModalOriginalClassification, message.FilterReason ?? MessageCategory.Normal.Name()));
ImGui.TextUnformatted(Language.ReportModalSuggestedClassification);
ImGui.SetNextItemWidth(-1);
var preview = this._reportCategory?.Name() ?? (this._other ? Language.ReportModalClassificationOther : string.Empty);
if (ImGui.BeginCombo($"##modal-classification-{message.Id}", preview)) {
foreach (var category in (MessageCategory[]) Enum.GetValues(typeof(MessageCategory))) {
if (ImGui.Selectable($"{category.Name()}##modal-option-{message.Id}", this._reportCategory == category)) {
this._reportCategory = category;
this._other = false;
}
WrappedTooltip(category.Description());
}
if (ImGui.Selectable(Language.ReportModalClassificationOther)) {
this._reportCategory = null;
this._other = true;
}
WrappedTooltip(Language.ReportModalClassificationOtherDescription);
ImGui.EndCombo();
}
ImGui.Separator();
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0f, 0f, 1f));
ImGui.TextUnformatted(Language.ReportModalHelp2);
ImGui.PopStyleColor();
ImGui.Separator();
if (message.FilterReason == "custom") {
string? errorText = null;
if (message.Custom) {
errorText = Language.ReportModalDisabledCustom;
} else if (message.ItemLevel) {
errorText = Language.ReportModalDisabledItemLevel;
} else if (message.ModelVersion == null) {
errorText = Language.ReportModalDisabledBadModel;
} else if (!message.EnabledSnapshot.WasEnabled(this._reportCategory ?? MessageCategory.Normal)) {
errorText = Language.ReportModalDisabledFilterNotEnabled;
} else if (this._reportCategory == (message.Classification ?? MessageCategory.Normal)) {
errorText = Language.ReportModalDisabledSameClassification;
}
if (this._other) {
if (ImGui.Button($"{Language.ReportModalGoToCustomButton}##report-goto-custom-{message.Id}")) {
ImGui.CloseCurrentPopup();
closing = true;
if (message == this.ToShowModal) {
this.ToShowModal = null;
}
this.Plugin.Ui.Settings.ShowOtherFilters = true;
this.Plugin.Ui.Settings.Show();
}
ImGui.SameLine();
} else if (errorText != null) {
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0f, 0f, 1f));
ImGui.TextUnformatted(Language.ReportModalCustom);
ImGui.TextUnformatted(errorText);
ImGui.PopStyleColor();
} else {
var buttonTitle =Language.ReportModalReport;
if (ImGui.Button($"{buttonTitle}##report-submit-{message.Id}")) {
this.ReportMessage(message);
if (ImGui.Button($"{Language.ReportModalReport}##report-submit-{message.Id}")) {
var suggested = this._reportCategory?.ToModelName() ?? "none (this is a bug)";
this._reportCategory = null;
if (message == this.ToShowModal) {
Task.Run(async () => {
var status = await this.Plugin.Ui.Report.ReportMessageAsync(message, suggested);
switch (status) {
case ReportStatus.Successful: {
var msg = Language.ReportToastSuccess;
this.Plugin.ToastGui.ShowNormal(string.Format(msg, message.Sender));
break;
}
case ReportStatus.Failure: {
var msg = Language.ReportToastFailure;
this.Plugin.ToastGui.ShowError(string.Format(msg, message.Sender));
break;
}
}
});
this.ToShowModal = null;
} else {
this.ReportMessage(message, suggested);
}
ImGui.CloseCurrentPopup();
closing = true;
}
ImGui.SameLine();
}
var copyButton = Language.ReportModalCopy;
if (ImGui.Button($"{copyButton}##report-copy-{message.Id}")) {
if (ImGui.Button($"{Language.ReportModalCopy}##report-copy-{message.Id}")) {
ImGui.SetClipboardText(message.Content.TextValue);
}
@ -255,12 +357,24 @@ namespace NoSoliciting.Interface {
var cancelButton = Language.ReportModalCancel;
if (ImGui.Button($"{cancelButton}##report-cancel-{message.Id}")) {
this._reportCategory = null;
if (message == this.ToShowModal) {
this.ToShowModal = null;
}
ImGui.CloseCurrentPopup();
closing = true;
}
ImGui.PopTextWrapPos();
ImGui.EndPopup();
if (closing) {
this._other = false;
}
return closing;
}
#endregion
@ -292,18 +406,33 @@ namespace NoSoliciting.Interface {
return clicked;
}
internal void ReportMessage(Message message) {
Task.Run(async () => await this.ReportMessageAsync(message));
private static void WrappedTooltip(string text) {
if (!ImGui.IsItemHovered()) {
return;
}
ImGui.BeginTooltip();
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 24);
ImGui.TextUnformatted(text);
ImGui.PopTextWrapPos();
ImGui.EndTooltip();
}
internal async Task<ReportStatus> ReportMessageAsync(Message message) {
private void ReportMessage(Message message, string suggested) {
Task.Run(async () => await this.ReportMessageAsync(message, suggested));
}
private async Task<ReportStatus> ReportMessageAsync(Message message, string suggested) {
string? resp = null;
try {
using var client = new WebClient();
this.LastReportStatus = ReportStatus.InProgress;
var reportUrl = this.Plugin.MlFilter?.ReportUrl;
if (reportUrl != null) {
resp = await client.UploadStringTaskAsync(reportUrl, message.ToJson()).ConfigureAwait(true);
var json = message.ToJson(suggested);
if (json != null) {
resp = await client.UploadStringTaskAsync(reportUrl, json).ConfigureAwait(true);
}
}
} catch (Exception) {
// ignored
@ -311,11 +440,11 @@ namespace NoSoliciting.Interface {
var status = resp == "{\"message\":\"ok\"}" ? ReportStatus.Successful : ReportStatus.Failure;
if (status == ReportStatus.Failure) {
PluginLog.LogWarning($"Failed to report message:\n{resp}");
Plugin.Log.Warning($"Failed to report message:\n{resp}");
}
this.LastReportStatus = status;
PluginLog.Log(resp == null
Plugin.Log.Info(resp == null
? "Report not sent. ML model not set."
: $"Report sent. Response: {resp}");

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text;
using System.Text.RegularExpressions;
using Dalamud.Interface;
using ImGuiNET;
@ -23,14 +24,14 @@ namespace NoSoliciting.Interface {
this.Plugin = plugin;
this.Ui = ui;
this.Plugin.Interface.UiBuilder.OnOpenConfigUi += this.Open;
this.Plugin.Interface.UiBuilder.OpenConfigUi += this.Open;
}
public void Dispose() {
this.Plugin.Interface.UiBuilder.OnOpenConfigUi -= this.Open;
this.Plugin.Interface.UiBuilder.OpenConfigUi -= this.Open;
}
private void Open(object? sender, EventArgs? e) {
private void Open() {
this.ShowSettings = true;
}
@ -38,8 +39,12 @@ namespace NoSoliciting.Interface {
this.ShowSettings = !this.ShowSettings;
}
public void Show() {
this.ShowSettings = true;
}
public void Draw() {
var windowTitle = string.Format(Language.Settings, this.Plugin.Name);
var windowTitle = string.Format(Language.Settings, Plugin.Name);
if (!this.ShowSettings || !ImGui.Begin($"{windowTitle}###NoSoliciting settings", ref this._showSettings)) {
return;
}
@ -159,7 +164,7 @@ namespace NoSoliciting.Interface {
var types = this.Plugin.Config.MlFilters[category];
void DrawTypes(ChatType type, string id) {
var name = type.Name(this.Plugin.Interface.Data);
var name = type.Name(this.Plugin.DataManager);
var check = types.Contains(type);
if (!ImGui.Checkbox($"{name}##{id}", ref check)) {
@ -189,8 +194,26 @@ namespace NoSoliciting.Interface {
#region Other config
internal bool ShowOtherFilters;
private static unsafe bool BeginTabItem(string label, ImGuiTabItemFlags flags) {
var unterminatedLabelBytes = Encoding.UTF8.GetBytes(label);
var labelBytes = stackalloc byte[unterminatedLabelBytes.Length + 1];
fixed (byte* unterminatedPtr = unterminatedLabelBytes) {
Buffer.MemoryCopy(unterminatedPtr, labelBytes, unterminatedLabelBytes.Length + 1, unterminatedLabelBytes.Length);
}
labelBytes[unterminatedLabelBytes.Length] = 0;
var num2 = (int) ImGuiNative.igBeginTabItem(labelBytes, null, flags);
return (uint) num2 > 0U;
}
private void DrawOtherFilters() {
if (!ImGui.BeginTabItem($"{Language.OtherFiltersTab}###other-filters-tab")) {
var flags = this.ShowOtherFilters ? ImGuiTabItemFlags.SetSelected : ImGuiTabItemFlags.None;
this.ShowOtherFilters = false;
if (!BeginTabItem($"{Language.OtherFiltersTab}###other-filters-tab", flags)) {
return;
}

View File

@ -3,69 +3,93 @@ using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Data;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Plugin.Services;
using Lumina.Excel.GeneratedSheets;
using NoSoliciting.Ml;
#if DEBUG
using System.Text;
using NoSoliciting.Ml;
#endif
namespace NoSoliciting {
[Serializable]
public class Message {
public Guid Id { get; }
[JsonIgnore]
public uint ActorId { get; }
public uint DefinitionsVersion { get; }
public uint? ModelVersion { get; }
public DateTime Timestamp { get; }
public ChatType ChatType { get; }
public SeString Sender { get; }
public SeString Content { get; }
public bool Ml { get; }
public string? FilterReason { get; }
public Message(uint defsVersion, ChatType type, uint actorId, SeString sender, SeString content, bool ml, string? reason) {
public IEnumerable<MessageCategory> EnabledSnapshot { get; }
public MessageCategory? Classification { get; }
public bool Custom { get; }
public bool ItemLevel { get; }
public bool Filtered => this.Custom || this.ItemLevel || this.Classification != null;
public string? FilterReason => this.Custom
? "custom"
: this.ItemLevel
? "ilvl"
: this.Classification?.Name();
internal Message(uint? defsVersion, ChatType type, uint actorId, SeString sender, SeString content, MessageCategory? classification, bool custom, bool ilvl, IEnumerable<MessageCategory> enabledSnapshot) {
this.Id = Guid.NewGuid();
this.DefinitionsVersion = defsVersion;
this.ModelVersion = defsVersion;
this.Timestamp = DateTime.Now;
this.ChatType = type;
this.ActorId = actorId;
this.Sender = sender;
this.Content = content;
this.Ml = ml;
this.FilterReason = reason;
this.Classification = classification;
this.Custom = custom;
this.ItemLevel = ilvl;
this.EnabledSnapshot = enabledSnapshot;
}
[Serializable]
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
private class JsonMessage {
public Guid Id { get; set; }
public uint DefinitionsVersion { get; set; }
public uint ReportVersion { get; } = 2;
public uint ModelVersion { get; set; }
public DateTime Timestamp { get; set; }
public ushort Type { get; set; }
// note: cannot use byte[] because Newtonsoft thinks it's a good idea to always base64 byte[]
// and I don't want to write a custom converter to overwrite their stupiditiy
// and I don't want to write a custom converter to overwrite their stupidity
public List<byte> Sender { get; set; }
public List<byte> Content { get; set; }
public bool Ml { get; set; }
public string? Reason { get; set; }
public string? SuggestedClassification { get; set; }
}
public string ToJson() {
public string? ToJson(string suggested) {
if (this.ModelVersion == null) {
return null;
}
var msg = new JsonMessage {
Id = this.Id,
DefinitionsVersion = this.DefinitionsVersion,
ModelVersion = this.ModelVersion.Value,
Timestamp = this.Timestamp,
Type = (ushort) this.ChatType,
Sender = this.Sender.Encode().ToList(),
Content = this.Content.Encode().ToList(),
Ml = this.Ml,
Reason = this.FilterReason,
Reason = this.Custom
? "custom"
: this.ItemLevel
? "ilvl"
: (this.Classification ?? MessageCategory.Normal).ToModelName(),
SuggestedClassification = suggested,
};
return JsonConvert.SerializeObject(msg, new JsonSerializerSettings {
@ -77,9 +101,7 @@ namespace NoSoliciting {
public StringBuilder ToCsv(StringBuilder? builder = null) {
builder ??= new StringBuilder();
var category = MessageCategoryExt.FromName(this.FilterReason) ?? MessageCategory.Normal;
builder.Append(category.ToModelName());
builder.Append(this.Classification?.ToModelName());
builder.Append(',');
builder.Append((int) this.ChatType);
builder.Append(",\"");
@ -186,7 +208,7 @@ namespace NoSoliciting {
_ => (byte) type,
};
public static string Name(this ChatType type, DataManager data) {
public static string Name(this ChatType type, IDataManager data) {
switch (type) {
case ChatType.None:
return "Party Finder";
@ -198,7 +220,7 @@ namespace NoSoliciting {
return "Party (Cross-world)";
}
var lf = data.GetExcelSheet<LogFilter>().FirstOrDefault(lf => lf.LogKind == type.LogKind());
var lf = data.GetExcelSheet<LogFilter>()!.FirstOrDefault(lf => lf.LogKind == type.LogKind());
return lf?.Name?.ToString() ?? type.ToString();
}

35
NoSoliciting/Ml/Classifier.cs Executable file
View File

@ -0,0 +1,35 @@
using System.IO;
using Microsoft.ML;
using NoSoliciting.Interface;
namespace NoSoliciting.Ml {
internal class Classifier : IClassifier {
private MLContext Context { get; set; } = null!;
private ITransformer Model { get; set; } = null!;
private DataViewSchema Schema { get; set; } = null!;
private PredictionEngine<Data, Prediction>? PredictionEngine { get; set; }
public void Initialise(byte[] data) {
if (this.PredictionEngine != null) {
this.PredictionEngine.Dispose();
this.PredictionEngine = null;
}
this.Context = new MLContext();
this.Context.ComponentCatalog.RegisterAssembly(typeof(Data).Assembly);
using var stream = new MemoryStream(data);
var model = this.Context.Model.Load(stream, out var schema);
this.Model = model;
this.Schema = schema;
this.PredictionEngine = this.Context.Model.CreatePredictionEngine<Data, Prediction>(this.Model, this.Schema);
}
public string Classify(ushort channel, string message) {
return this.PredictionEngine?.Predict(new Data(channel, message))?.Category ?? "UNKNOWN";
}
public void Dispose() {
this.PredictionEngine?.Dispose();
}
}
}

View File

@ -1,17 +1,12 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Dalamud.Plugin;
using JKang.IpcServiceFramework.Client;
using Microsoft.Extensions.DependencyInjection;
using NoSoliciting.Interface;
using NoSoliciting.Resources;
using Resourcer;
using YamlDotNet.Core;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
@ -31,25 +26,23 @@ namespace NoSoliciting.Ml {
public uint Version { get; }
public Uri ReportUrl { get; }
private Process Process { get; }
private IIpcClient<IClassifier> Classifier { get; }
private IClassifier Classifier { get; }
private MlFilter(uint version, Uri reportUrl, Process process, IIpcClient<IClassifier> classifier) {
this.Process = process;
private MlFilter(uint version, Uri reportUrl, IClassifier classifier) {
this.Classifier = classifier;
this.Version = version;
this.ReportUrl = reportUrl;
}
public MessageCategory ClassifyMessage(ushort channel, string message) {
var prediction = this.Classifier.InvokeAsync(classifier => classifier.Classify(channel, message)).Result;
var prediction = this.Classifier.Classify(channel, message);
var category = MessageCategoryExt.FromString(prediction);
if (category != null) {
return (MessageCategory) category;
}
PluginLog.LogWarning($"Unknown message category: {prediction}");
Plugin.Log.Warning($"Unknown message category: {prediction}");
return MessageCategory.Normal;
}
@ -59,7 +52,7 @@ namespace NoSoliciting.Ml {
// download and parse the remote manifest
var manifest = await DownloadManifest();
if (manifest == null) {
PluginLog.LogWarning("Could not download manifest. Will attempt to fall back on cached version.");
Plugin.Log.Warning("Could not download manifest. Will attempt to fall back on cached version.");
}
// model zip file data
@ -71,7 +64,7 @@ namespace NoSoliciting.Ml {
if (localManifest != null && (manifest?.Item1 == null || localManifest.Version == manifest.Value.manifest.Version)) {
try {
// try to reach the cached model
data = File.ReadAllBytes(CachedFilePath(plugin, ModelName));
data = await File.ReadAllBytesAsync(CachedFilePath(plugin, ModelName));
// set the manifest to our local one and an empty string for the source
manifest ??= (localManifest, string.Empty);
} catch (IOException) {
@ -102,7 +95,7 @@ namespace NoSoliciting.Ml {
var hash = sha.ComputeHash(data);
while (!hash.SequenceEqual(correctHash) && retries < maxRetries) {
PluginLog.Warning($"Model checksum did not match. Redownloading (attempt {retries + 1}/{maxRetries})");
Plugin.Log.Warning($"Model checksum did not match. Redownloading (attempt {retries + 1}/{maxRetries})");
retries += 1;
data = await DownloadModel(manifest!.Value.manifest!.ModelUrl);
@ -128,72 +121,24 @@ namespace NoSoliciting.Ml {
}
// initialise the classifier
var pluginFolder = plugin.Interface.ConfigDirectory.ToString();
var exePath = await ExtractClassifier(pluginFolder);
var pipeId = Guid.NewGuid();
var process = StartClassifier(exePath, pipeId, showWindow);
var client = await CreateClassifierClient(pipeId, data);
var classifier = new Classifier();
classifier.Initialise(data);
return new MlFilter(
manifest.Value.manifest!.Version,
manifest.Value.manifest!.ReportUrl,
process!,
client
classifier
);
}
private static async Task<IIpcClient<IClassifier>> CreateClassifierClient(Guid pipeId, byte[] data) {
var serviceProvider = new ServiceCollection()
.AddNamedPipeIpcClient<IClassifier>("client", (_, options) => {
options.PipeName = $"NoSoliciting.MessageClassifier-{pipeId}";
options.Serializer = new BetterIpcSerialiser();
})
.BuildServiceProvider();
var clientFactory = serviceProvider.GetRequiredService<IIpcClientFactory<IClassifier>>();
var client = clientFactory.CreateClient("client");
await client.InvokeAsync(classifier => classifier.Initialise(data));
return client;
}
private static Process StartClassifier(string exePath, Guid pipeId, bool showWindow) {
var game = Process.GetCurrentProcess();
var startInfo = new ProcessStartInfo(exePath) {
CreateNoWindow = !showWindow,
UseShellExecute = false,
Arguments = $"\"{game.Id}\" \"{game.ProcessName}\" \"{pipeId}\"",
};
return Process.Start(startInfo)!;
}
private static async Task<string> ExtractClassifier(string pluginFolder) {
using var exe = Resource.AsStream("NoSoliciting.NoSoliciting.MessageClassifier.exe");
Directory.CreateDirectory(pluginFolder);
var exePath = Path.Combine(pluginFolder, "NoSoliciting.MessageClassifier.exe");
try {
using var exeFile = File.Create(exePath);
await exe.CopyToAsync(exeFile);
} catch (IOException ex) {
PluginLog.LogWarning($"Could not update classifier. Continuing as normal.\n{ex}");
}
return exePath;
}
private static async Task<byte[]?> DownloadModel(Uri url) {
try {
using var client = new WebClient();
var data = await client.DownloadDataTaskAsync(url);
return data;
} catch (WebException e) {
PluginLog.LogError("Could not download newest model.");
PluginLog.LogError(e.ToString());
Plugin.Log.Error("Could not download newest model.");
Plugin.Log.Error(e.ToString());
LastError = e.Message;
return null;
}
@ -211,7 +156,7 @@ namespace NoSoliciting.Ml {
var file = File.Create(cachePath);
await file.WriteAsync(data, 0, data.Length);
await file.FlushAsync();
file.Dispose();
await file.DisposeAsync();
}
private static async Task<(Manifest manifest, string source)?> DownloadManifest() {
@ -221,8 +166,8 @@ namespace NoSoliciting.Ml {
LastError = null;
return (LoadYaml<Manifest>(data), data);
} catch (Exception e) when (e is WebException or YamlException) {
PluginLog.LogError("Could not download newest model manifest.");
PluginLog.LogError(e.ToString());
Plugin.Log.Error("Could not download newest model manifest.");
Plugin.Log.Error(e.ToString());
LastError = e.Message;
return null;
}
@ -257,12 +202,7 @@ namespace NoSoliciting.Ml {
}
public void Dispose() {
try {
this.Process.Kill();
this.Process.Dispose();
} catch (Exception) {
// ignored
}
this.Classifier.Dispose();
}
}

View File

@ -13,6 +13,7 @@ namespace NoSoliciting.Ml {
Static,
Community,
StaticSub,
Fluff,
}
public static class MessageCategoryExt {
@ -26,6 +27,7 @@ namespace NoSoliciting.Ml {
MessageCategory.Static,
MessageCategory.StaticSub,
MessageCategory.Community,
MessageCategory.Fluff,
};
public static MessageCategory? FromString(string? category) => category switch {
@ -39,24 +41,11 @@ namespace NoSoliciting.Ml {
"STATIC" => MessageCategory.Static,
"COMMUNITY" => MessageCategory.Community,
"STATIC_SUB" => MessageCategory.StaticSub,
"FLUFF" => MessageCategory.Fluff,
_ => null,
};
#if DEBUG
public static string ToModelName(this MessageCategory category) => category switch {
MessageCategory.Trade => "TRADE",
MessageCategory.FreeCompany => "FC",
MessageCategory.Normal => "NORMAL",
MessageCategory.Phishing => "PHISH",
MessageCategory.RmtContent => "RMT_C",
MessageCategory.RmtGil => "RMT_G",
MessageCategory.Roleplaying => "RP",
MessageCategory.Static => "STATIC",
MessageCategory.Community => "COMMUNITY",
MessageCategory.StaticSub => "STATIC_SUB",
_ => throw new ArgumentException("Invalid category", nameof(category)),
};
public static MessageCategory? FromName(string? category) => category switch {
"Trade ads" => MessageCategory.Trade,
"Free Company ads" => MessageCategory.FreeCompany,
@ -68,11 +57,26 @@ namespace NoSoliciting.Ml {
"Static recruitment" => MessageCategory.Static,
"Community ads" => MessageCategory.Community,
"Static substitutes" => MessageCategory.StaticSub,
"Fluff" => MessageCategory.Fluff,
_ => null,
};
#endif
public static string ToModelName(this MessageCategory category) => category switch {
MessageCategory.Trade => "TRADE",
MessageCategory.FreeCompany => "FC",
MessageCategory.Normal => "NORMAL",
MessageCategory.Phishing => "PHISH",
MessageCategory.RmtContent => "RMT_C",
MessageCategory.RmtGil => "RMT_G",
MessageCategory.Roleplaying => "RP",
MessageCategory.Static => "STATIC",
MessageCategory.Community => "COMMUNITY",
MessageCategory.StaticSub => "STATIC_SUB",
MessageCategory.Fluff => "FLUFF",
_ => throw new ArgumentException("Invalid category", nameof(category)),
};
public static string Name(this MessageCategory category) => category switch {
MessageCategory.Trade => Language.TradeCategory,
MessageCategory.FreeCompany => Language.FreeCompanyCategory,
@ -84,6 +88,7 @@ namespace NoSoliciting.Ml {
MessageCategory.Static => Language.StaticCategory,
MessageCategory.Community => Language.CommunityCategory,
MessageCategory.StaticSub => Language.StaticSubCategory,
MessageCategory.Fluff => Language.FluffCategory,
_ => throw new ArgumentException("Invalid category", nameof(category)),
};
@ -98,6 +103,7 @@ namespace NoSoliciting.Ml {
MessageCategory.Static => Language.StaticDescription,
MessageCategory.Community => Language.CommunityDescription,
MessageCategory.StaticSub => Language.StaticSubDescription,
MessageCategory.Fluff => Language.FluffDescription,
_ => throw new ArgumentException("Invalid category", nameof(category)),
};
}

View File

@ -3,50 +3,59 @@
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<Version>2.0.3</Version>
<TargetFramework>net48</TargetFramework>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<Version>3.0.8</Version>
<TargetFramework>net7-windows</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PlatformTarget>x64</PlatformTarget>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
</PropertyGroup>
<PropertyGroup>
<DalamudLibPath>$(AppData)\XIVLauncher\addon\Hooks\dev</DalamudLibPath>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))'">
<DalamudLibPath>$(DALAMUD_HOME)</DalamudLibPath>
</PropertyGroup>
<PropertyGroup Condition="'$(IsCI)' == 'true'">
<DalamudLibPath>$(HOME)/dalamud</DalamudLibPath>
</PropertyGroup>
<ItemGroup>
<Reference Include="Dalamud">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
<HintPath>$(DalamudLibPath)\Dalamud.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ImGui.NET">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\ImGui.NET.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ImGuiScene">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\ImGuiScene.dll</HintPath>
<HintPath>$(DalamudLibPath)\ImGui.NET.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Lumina">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.dll</HintPath>
<HintPath>$(DalamudLibPath)\Lumina.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Lumina.Excel">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
<HintPath>$(DalamudLibPath)\Lumina.Excel.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Newtonsoft.Json.dll</HintPath>
<HintPath>$(DalamudLibPath)\Newtonsoft.Json.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="DalamudPackager" Version="1.2.1"/>
<PackageReference Include="Fody" Version="6.5.1" PrivateAssets="all"/>
<PackageReference Include="ILRepack.Lib.MSBuild.Task" Version="2.0.18.2"/>
<PackageReference Include="JKang.IpcServiceFramework.Client.NamedPipe" Version="3.1.0"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1"/>
<PackageReference Include="Resourcer.Fody" Version="1.8.0" PrivateAssets="all"/>
<PackageReference Include="ResourcesMerge.Fody" Version="1.0.1"/>
<PackageReference Include="XivCommon" Version="1.5.0"/>
<PackageReference Include="YamlDotNet" Version="11.1.1"/>
<PackageReference Include="Dalamud.ContextMenu" Version="1.3.1"/>
<PackageReference Include="DalamudPackager" Version="2.1.12"/>
<PackageReference Include="Fody" Version="6.8.0" PrivateAssets="all"/>
<PackageReference Include="Microsoft.ML" Version="2.0.1"/>
<PackageReference Include="Resourcer.Fody" Version="1.8.1" PrivateAssets="all"/>
<PackageReference Include="XivCommon" Version="9.0.0"/>
<PackageReference Include="YamlDotNet" Version="13.4.0"/>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\NoSoliciting.MessageClassifier\bin\Release\net48\NoSoliciting.MessageClassifier.exe"/>
<EmbeddedResource Update="Resources\Language.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Language.Designer.cs</LastGenOutput>

View File

@ -1,5 +1,6 @@
author: ascclemens
author: Anna
name: NoSoliciting
punchline: Adblock for FFXIV.
description: |-
Customisable chat and Party Finder filtering. In addition to letting
you filter anything from chat and PF, it comes with built-in filters
@ -13,6 +14,6 @@ description: |-
- Trade ads
- Community ads
- Any PF with an item level over the max
repo_url: https://git.sr.ht/~jkcclemens/NoSoliciting
repo_url: https://git.anna.lgbt/anna/NoSoliciting
# Use higher priority to filter messages before other plugins get them.
load_priority: 100

View File

@ -6,52 +6,79 @@ using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Dalamud;
using Dalamud.ContextMenu;
using Dalamud.IoC;
using Dalamud.Plugin.Services;
using NoSoliciting.Interface;
using NoSoliciting.Ml;
using NoSoliciting.Resources;
using XivCommon;
namespace NoSoliciting {
// ReSharper disable once ClassNeverInstantiated.Global
public class Plugin : IDalamudPlugin {
private bool _disposedValue;
public string Name => "NoSoliciting";
internal static string Name => "NoSoliciting";
private Filter Filter { get; set; } = null!;
private Filter Filter { get; }
public DalamudPluginInterface Interface { get; private set; } = null!;
public PluginConfiguration Config { get; private set; } = null!;
public XivCommonBase Common { get; private set; } = null!;
public PluginUi Ui { get; private set; } = null!;
public Commands Commands { get; private set; } = null!;
private ContextMenu ContextMenu { get; set; } = null!;
public MlFilterStatus MlStatus { get; set; } = MlFilterStatus.Uninitialised;
public MlFilter? MlFilter { get; set; }
[PluginService]
internal static IPluginLog Log { get; private set; } = null!;
[PluginService]
internal DalamudPluginInterface Interface { get; init; } = null!;
[PluginService]
private IClientState ClientState { get; init; } = null!;
[PluginService]
internal IChatGui ChatGui { get; init; } = null!;
[PluginService]
internal IPartyFinderGui PartyFinderGui { get; init; } = null!;
[PluginService]
internal IDataManager DataManager { get; init; } = null!;
[PluginService]
internal ICommandManager CommandManager { get; init; } = null!;
[PluginService]
internal IToastGui ToastGui { get; init; } = null!;
internal PluginConfiguration Config { get; }
internal XivCommonBase Common { get; }
internal DalamudContextMenu DalamudContextMenu { get; }
internal PluginUi Ui { get; }
private Commands Commands { get; }
private ContextMenu ContextMenu { get; }
internal MlFilterStatus MlStatus { get; set; } = MlFilterStatus.Uninitialised;
internal MlFilter? MlFilter { get; set; }
private readonly List<Message> _messageHistory = new();
public IEnumerable<Message> MessageHistory => this._messageHistory;
internal IEnumerable<Message> MessageHistory => this._messageHistory;
private readonly List<Message> _partyFinderHistory = new();
public IEnumerable<Message> PartyFinderHistory => this._partyFinderHistory;
internal IEnumerable<Message> PartyFinderHistory => this._partyFinderHistory;
// ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Local
public string AssemblyLocation { get; private set; } = Assembly.GetExecutingAssembly().Location;
public void Initialize(DalamudPluginInterface pluginInterface) {
public Plugin() {
string path = Environment.GetEnvironmentVariable("PATH")!;
string newPath = Path.GetDirectoryName(this.AssemblyLocation)!;
Environment.SetEnvironmentVariable("PATH", $"{path};{newPath}");
this.Interface = pluginInterface;
this.Config = this.Interface.GetPluginConfig() as PluginConfiguration ?? new PluginConfiguration();
this.Config.Initialise(this.Interface);
this.ConfigureLanguage();
this.Interface.OnLanguageChanged += this.OnLanguageUpdate;
this.Interface.LanguageChanged += this.OnLanguageUpdate;
this.Common = new XivCommonBase(this.Interface, Hooks.PartyFinder | Hooks.ContextMenu);
this.Common = new XivCommonBase(this.Interface, Hooks.PartyFinderListings);
this.DalamudContextMenu = new DalamudContextMenu(this.Interface);
this.Ui = new PluginUi(this);
this.Commands = new Commands(this);
@ -63,9 +90,9 @@ namespace NoSoliciting {
// pre-compute the max ilvl to prevent stutter
try {
FilterUtil.MaxItemLevelAttainable(this.Interface.Data);
FilterUtil.MaxItemLevelAttainable(this.DataManager);
} catch (Exception ex) {
PluginLog.LogError(ex, "Exception while computing max item level");
Plugin.Log.Error(ex, "Exception while computing max item level");
}
}
@ -80,8 +107,9 @@ namespace NoSoliciting {
this.ContextMenu.Dispose();
this.Commands.Dispose();
this.Ui.Dispose();
this.DalamudContextMenu.Dispose();
this.Common.Dispose();
this.Interface.OnLanguageChanged -= this.OnLanguageUpdate;
this.Interface.LanguageChanged -= this.OnLanguageUpdate;
}
this._disposedValue = true;
@ -93,17 +121,19 @@ namespace NoSoliciting {
internal void ConfigureLanguage(string? langCode = null) {
if (this.Config.FollowGameLanguage) {
langCode = this.Interface.ClientState.ClientLanguage switch {
langCode = this.ClientState.ClientLanguage switch {
ClientLanguage.Japanese => "ja",
ClientLanguage.English => "en",
ClientLanguage.German => "de",
ClientLanguage.French => "fr",
_ => throw new ArgumentOutOfRangeException(),
_ => throw new ArgumentOutOfRangeException(nameof(this.ClientState.ClientLanguage), "Unknown ClientLanguage"),
};
}
langCode ??= this.Interface.UiLanguage;
Resources.Language.Culture = new CultureInfo(langCode ?? "en");
// I don't fucking trust this. Not since last time.
// ReSharper disable once ConstantNullCoalescingCondition
Language.Culture = new CultureInfo(langCode ?? "en");
}
internal void InitialiseMachineLearning(bool showWindow) {
@ -119,7 +149,7 @@ namespace NoSoliciting {
}
this.MlStatus = MlFilterStatus.Initialised;
PluginLog.Log("Machine learning model loaded");
Log.Info("Machine learning model loaded");
});
}

View File

@ -44,12 +44,17 @@ namespace NoSoliciting {
public Dictionary<MessageCategory, HashSet<ChatType>> MlFilters { get; set; } = new() {
[MessageCategory.RmtGil] = new HashSet<ChatType> {
ChatType.None,
ChatType.Say,
ChatType.Shout,
},
[MessageCategory.RmtContent] = new HashSet<ChatType> {
ChatType.None,
ChatType.Say,
ChatType.Shout,
},
[MessageCategory.Phishing] = new HashSet<ChatType> {
ChatType.None,
ChatType.TellIncoming,
},
[MessageCategory.Roleplaying] = new HashSet<ChatType> {
@ -71,9 +76,18 @@ namespace NoSoliciting {
},
[MessageCategory.Trade] = new HashSet<ChatType> {
ChatType.None,
ChatType.Shout,
ChatType.Yell,
},
[MessageCategory.Community] = new HashSet<ChatType> {
ChatType.None,
ChatType.Shout,
ChatType.Yell,
},
[MessageCategory.Fluff] = new HashSet<ChatType> {
ChatType.None,
ChatType.Shout,
ChatType.Yell,
},
};
@ -106,7 +120,7 @@ namespace NoSoliciting {
}
internal bool MlEnabledOn(MessageCategory category, ChatType chatType) {
HashSet<ChatType> filtered;
HashSet<ChatType>? filtered;
if (this.AdvancedMode) {
if (!this.MlFilters.TryGetValue(category, out filtered)) {
@ -126,5 +140,23 @@ namespace NoSoliciting {
return filtered.Contains(chatType);
}
internal IEnumerable<MessageCategory> CreateFiltersClone() {
var filters = new HashSet<MessageCategory>();
foreach (var category in (MessageCategory[]) Enum.GetValues(typeof(MessageCategory))) {
if (this.AdvancedMode) {
if (this.MlFilters.TryGetValue(category, out var filtered) && filtered.Count > 0) {
filters.Add(category);
}
} else {
if (this.BasicMlFilters.Contains(category)) {
filters.Add(category);
}
}
}
return filters;
}
}
}

View File

@ -159,6 +159,24 @@ namespace NoSoliciting.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Fluff.
/// </summary>
internal static string FluffCategory {
get {
return ResourceManager.GetString("FluffCategory", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Messages that don&apos;t fall into other categories and are not for content (Party Finder).
/// </summary>
internal static string FluffDescription {
get {
return ResourceManager.GetString("FluffDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Free Company ads.
/// </summary>
@ -465,6 +483,24 @@ namespace NoSoliciting.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Other.
/// </summary>
internal static string ReportModalClassificationOther {
get {
return ResourceManager.GetString("ReportModalClassificationOther", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Messages that don&apos;t fall under any of the other categories..
/// </summary>
internal static string ReportModalClassificationOtherDescription {
get {
return ResourceManager.GetString("ReportModalClassificationOtherDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copy to clipboard.
/// </summary>
@ -474,12 +510,57 @@ namespace NoSoliciting.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Reporting is disabled because your ML model was not functioning when you saw this message..
/// </summary>
internal static string ReportModalDisabledBadModel {
get {
return ResourceManager.GetString("ReportModalDisabledBadModel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You cannot report messages filtered because of a custom filter..
/// </summary>
internal static string ReportModalCustom {
internal static string ReportModalDisabledCustom {
get {
return ResourceManager.GetString("ReportModalCustom", resourceCulture);
return ResourceManager.GetString("ReportModalDisabledCustom", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reporting is disabled because you weren&apos;t filtering for this kind of message at the time you saw it..
/// </summary>
internal static string ReportModalDisabledFilterNotEnabled {
get {
return ResourceManager.GetString("ReportModalDisabledFilterNotEnabled", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You cannot report messages filtered because of item level..
/// </summary>
internal static string ReportModalDisabledItemLevel {
get {
return ResourceManager.GetString("ReportModalDisabledItemLevel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reporting is disabled because you must choose a different classification than the original..
/// </summary>
internal static string ReportModalDisabledSameClassification {
get {
return ResourceManager.GetString("ReportModalDisabledSameClassification", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Go to custom filters.
/// </summary>
internal static string ReportModalGoToCustomButton {
get {
return ResourceManager.GetString("ReportModalGoToCustomButton", resourceCulture);
}
}
@ -501,6 +582,15 @@ namespace NoSoliciting.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to This message&apos;s original classification: {0}.
/// </summary>
internal static string ReportModalOriginalClassification {
get {
return ResourceManager.GetString("ReportModalOriginalClassification", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Report.
/// </summary>
@ -510,6 +600,15 @@ namespace NoSoliciting.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to How do you think this message should have been classified?.
/// </summary>
internal static string ReportModalSuggestedClassification {
get {
return ResourceManager.GetString("ReportModalSuggestedClassification", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Report to {0}.
/// </summary>
@ -519,24 +618,6 @@ namespace NoSoliciting.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Specifically, this message WAS filtered but shouldn&apos;t have been..
/// </summary>
internal static string ReportModalWasFiltered {
get {
return ResourceManager.GetString("ReportModalWasFiltered", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Specifically, this message WAS NOT filtered but should have been..
/// </summary>
internal static string ReportModalWasNotFiltered {
get {
return ResourceManager.GetString("ReportModalWasNotFiltered", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Party Finder.
/// </summary>

View File

@ -132,7 +132,7 @@
<data name="ReportModalReport" xml:space="preserve">
<value>Melden</value>
</data>
<data name="ReportModalCustom" xml:space="preserve">
<data name="ReportModalDisabledCustom" xml:space="preserve">
<value>Du kannst Nachrichten, die aufgrund von benutzerdefinierten Filtern gefiltert wurden, nicht melden.</value>
</data>
<data name="PhishingCategory" xml:space="preserve">
@ -141,9 +141,6 @@
<data name="NormalCategory" xml:space="preserve">
<value>Normale Nachrichten</value>
</data>
<data name="ReportModalWasNotFiltered" xml:space="preserve">
<value>Diese Nachricht WURDE NICHT gefiltert, hätte aber gefiltert werden sollen.</value>
</data>
<data name="ReportModalHelp2" xml:space="preserve">
<value>NoSoliciting funktioniert nur für englische Nachrichten, bitte ausschließlich englische Nachrichten melden.</value>
</data>
@ -204,9 +201,6 @@
<data name="ModelTabStatus" xml:space="preserve">
<value>Status des Modells: {0}</value>
</data>
<data name="ReportModalWasFiltered" xml:space="preserve">
<value>Diese Nachricht WURDE gefiltert, hätte aber nicht gefiltert werden sollen.</value>
</data>
<data name="LogFilteredMessages" xml:space="preserve">
<value>Gefilterte Nachrichten protokollieren</value>
</data>
@ -231,4 +225,40 @@
<data name="TradeCategory" xml:space="preserve">
<value>Handelswerbung</value>
</data>
<data name="ModelStatusUninitialised" xml:space="preserve">
<value>nicht initialisiert</value>
</data>
<data name="ModelStatusInitialised" xml:space="preserve">
<value>initialisiert</value>
</data>
<data name="ModelStatusDownloadingModel" xml:space="preserve">
<value>lade Modell herunter</value>
</data>
<data name="ModelStatusPreparing" xml:space="preserve">
<value>bereite Aktualisierung des Modells vor</value>
</data>
<data name="ModelStatusDownloadingManifest" xml:space="preserve">
<value>lade Machine Learning Manifest herunter</value>
</data>
<data name="ModelStatusInitialising" xml:space="preserve">
<value>initialisiere Modell und Klassifikator</value>
</data>
<data name="ReportModalDisabledBadModel" xml:space="preserve">
<value>Du kannst diese Nachricht nicht melden, weil das Machine Learning Modell nicht funktionierte, als du sie gesehen hast.</value>
</data>
<data name="ReportModalDisabledItemLevel" xml:space="preserve">
<value>Du kannst Nachrichten, die wegen zu hoher Gegenstandsstufe gefiltert wurden, nicht melden.</value>
</data>
<data name="ReportModalDisabledSameClassification" xml:space="preserve">
<value>Wähle eine andere Kategorie, bevor du diese Nachricht meldest.</value>
</data>
<data name="ReportModalOriginalClassification" xml:space="preserve">
<value>Ursprüngliche Kategorie dieser Nachricht: {0}</value>
</data>
<data name="ReportModalSuggestedClassification" xml:space="preserve">
<value>Wie denkst du, hätte diese Nachricht kategorisiert werden sollen?</value>
</data>
<data name="ReportModalDisabledFilterNotEnabled" xml:space="preserve">
<value>Du kannst diese Nachricht nicht melden, weil du diese Art von Nachrichten nicht gefiltert hast, als du sie gesehen hast.</value>
</data>
</root>

View File

@ -166,13 +166,10 @@
<data name="ReportModalHelp1" xml:space="preserve">
<value>Al informar de este mensaje le harás saber a la desarrolladora que crees que este mensaje ha sido clasificado de manera errónea.</value>
</data>
<data name="ReportModalWasFiltered" xml:space="preserve">
<value>Específicamente, este mensaje HA SIDO filtrado pero no debería haberlo sido.</value>
</data>
<data name="ReportModalHelp2" xml:space="preserve">
<value>NoSoliciting sólo funciona con mensajes en inglés. No informes de mensajes en otros idiomas.</value>
</data>
<data name="ReportModalCustom" xml:space="preserve">
<data name="ReportModalDisabledCustom" xml:space="preserve">
<value>No puedes informar de mensajes que han sido filtrados debido a un filtro personalizado.</value>
</data>
<data name="ReportModalReport" xml:space="preserve">
@ -259,9 +256,6 @@
<data name="ReportStatusMessage" xml:space="preserve">
<value>Estado del último reporte: {0}</value>
</data>
<data name="ReportModalWasNotFiltered" xml:space="preserve">
<value>Específicamente, este mensaje NO HA SIDO filtrado pero debería haberlo sido.</value>
</data>
<data name="NormalCategory" xml:space="preserve">
<value>Mensajes normales</value>
</data>
@ -277,4 +271,49 @@
<data name="StaticSubDescription" xml:space="preserve">
<value>Statics buscando sustitutos temporales para clears</value>
</data>
<data name="ModelStatusUninitialised" xml:space="preserve">
<value>Sin inicializar</value>
</data>
<data name="ModelStatusPreparing" xml:space="preserve">
<value>Preparando actualización del modelo</value>
</data>
<data name="ModelStatusDownloadingManifest" xml:space="preserve">
<value>Descargando manifiesto del modelo</value>
</data>
<data name="ModelStatusDownloadingModel" xml:space="preserve">
<value>Descargando modelo</value>
</data>
<data name="ModelStatusInitialising" xml:space="preserve">
<value>Inicializando modelo y clasificador</value>
</data>
<data name="ModelStatusInitialised" xml:space="preserve">
<value>Inicializado</value>
</data>
<data name="ReportModalSuggestedClassification" xml:space="preserve">
<value>¿Cómo crees que se debería haber clasificado este mensaje?</value>
</data>
<data name="ReportModalDisabledBadModel" xml:space="preserve">
<value>No puedes reportar este mensaje porque tu modelo ML no estaba funcionando correctamente cuando viste este mensaje.</value>
</data>
<data name="ReportModalDisabledSameClassification" xml:space="preserve">
<value>No puedes reportar este mensaje porque debes elegir una clasificación distinta a la original.</value>
</data>
<data name="ReportModalDisabledItemLevel" xml:space="preserve">
<value>No puedes reportar mensajes filtrados por item level.</value>
</data>
<data name="ReportModalOriginalClassification" xml:space="preserve">
<value>La clasificación original de este mensaje: {0}</value>
</data>
<data name="ReportModalDisabledFilterNotEnabled" xml:space="preserve">
<value>No puedes reportar este mensaje porque no estabas filtrando este tipo de mensaje cuando lo viste.</value>
</data>
<data name="ReportModalClassificationOther" xml:space="preserve">
<value>Otro</value>
</data>
<data name="ReportModalGoToCustomButton" xml:space="preserve">
<value>Ir a filtros personalizados</value>
</data>
<data name="ReportModalClassificationOtherDescription" xml:space="preserve">
<value>Mensajes que no encajan en ninguna otra categoría.</value>
</data>
</root>

View File

@ -52,7 +52,7 @@
<value>Date et heure</value>
</data>
<data name="ReportColumnChannel" xml:space="preserve">
<value>Channel</value>
<value>Canal</value>
</data>
<data name="ReportColumnReason" xml:space="preserve">
<value>Raison</value>
@ -210,16 +210,10 @@
<data name="ReportModalHelp1" xml:space="preserve">
<value>Signaler ce message avertira la développeuse que vous pensez qu'il a été incorrectement classifié.</value>
</data>
<data name="ReportModalWasFiltered" xml:space="preserve">
<value>Plus précisément, cela veut dire que ce message A ÉTÉ filtré alors qu'il n'aurait pas dû l'être.</value>
</data>
<data name="ReportModalWasNotFiltered" xml:space="preserve">
<value>Plus précisément, cela veut dire que ce message N'A PAS ÉTÉ filtré alors qu'il aurait dû l'être.</value>
</data>
<data name="ReportModalHelp2" xml:space="preserve">
<value>NoSoliciting n'est fonctionnel que pour les messages en anglais. Veuillez ne pas signaler des messages qui ne sont pas en anglais.</value>
</data>
<data name="ReportModalCustom" xml:space="preserve">
<data name="ReportModalDisabledCustom" xml:space="preserve">
<value>Vous ne pouvez pas signaler de messages filtrés à cause d'un filtre personnalisé.</value>
</data>
<data name="ReportModalReport" xml:space="preserve">
@ -249,4 +243,31 @@
<data name="ModelStatusInitialising" xml:space="preserve">
<value>Initialisation du modèle et du classificateur</value>
</data>
<data name="ReportModalDisabledBadModel" xml:space="preserve">
<value>Le signalement est désactivé car votre modèle de ML n'est pas fonctionnel au moment où ce message est affiché.</value>
</data>
<data name="ReportModalDisabledSameClassification" xml:space="preserve">
<value>Le signalement est désactivé car vous devez choisir une catégorisation différente de celle d'origine.</value>
</data>
<data name="ReportModalDisabledItemLevel" xml:space="preserve">
<value>Vous ne pouvez pas signaler ce message à cause du niveau d'objet requis.</value>
</data>
<data name="ReportModalOriginalClassification" xml:space="preserve">
<value>La catégorisation originale du message : {0}</value>
</data>
<data name="ReportModalSuggestedClassification" xml:space="preserve">
<value>De quelle manière pensez-vous que ce message aurait dû être catégorisé ?</value>
</data>
<data name="ReportModalDisabledFilterNotEnabled" xml:space="preserve">
<value>Le signalement est désactivé parce que vous ne filtriez pas cette catégorie de messages au moment où vous l'avez vu.</value>
</data>
<data name="ReportModalClassificationOther" xml:space="preserve">
<value>Autres</value>
</data>
<data name="ReportModalGoToCustomButton" xml:space="preserve">
<value>Ouvrir les filtres de discussion personnalisés</value>
</data>
<data name="ReportModalClassificationOtherDescription" xml:space="preserve">
<value>Messages qui ne correspondent à aucune des autres catégories.</value>
</data>
</root>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
@ -58,4 +58,235 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
<data name="OtherTab" xml:space="preserve">
<value>その他</value>
</data>
<data name="OtherGameLanguage" xml:space="preserve">
<value>Dalamudの言語設定ではなく、ゲームの言語設定を使用する</value>
</data>
<data name="ShowReportingWindow" xml:space="preserve">
<value>報告ウィンドウを開く</value>
</data>
<data name="Settings" xml:space="preserve">
<value>{0} 設定</value>
</data>
<data name="ModelTab" xml:space="preserve">
<value>モデル</value>
</data>
<data name="ModelTabVersion" xml:space="preserve">
<value>バージョン: {0}</value>
</data>
<data name="ModelTabStatus" xml:space="preserve">
<value>モデルの状態: {0}</value>
</data>
<data name="ModelTabError" xml:space="preserve">
<value>最後に発生したエラー</value>
</data>
<data name="UpdateModel" xml:space="preserve">
<value>モデルの更新</value>
</data>
<data name="FiltersTab" xml:space="preserve">
<value>フィルタ</value>
</data>
<data name="AdvancedWarning2" xml:space="preserve">
<value>機械学習のモデルは特定のチャンネルの会話を想定して訓練されています。</value>
</data>
<data name="OtherFiltersTab" xml:space="preserve">
<value>その他のフィルタ</value>
</data>
<data name="ChatFilters" xml:space="preserve">
<value>チャットフィルタ</value>
</data>
<data name="PartyFinderFilters" xml:space="preserve">
<value>パーティ募集フィルタ</value>
</data>
<data name="FilterIlvlPfs" xml:space="preserve">
<value>アイテムレベルが最大値を超える募集をフィルタリング</value>
</data>
<data name="EnableCustomPartyFinderFilters" xml:space="preserve">
<value>カスタムパーティ募集フィルタを有効化</value>
</data>
<data name="SubstringsToFilter" xml:space="preserve">
<value>フィルタするキーワード</value>
</data>
<data name="RegularExpressionsToFilter" xml:space="preserve">
<value>フィルタする正規表現</value>
</data>
<data name="SaveFilters" xml:space="preserve">
<value>フィルタの保存</value>
</data>
<data name="Reporting" xml:space="preserve">
<value>{0} 誤りの報告</value>
</data>
<data name="ReportStatusFailure" xml:space="preserve">
<value>送信失敗</value>
</data>
<data name="ReportStatusSuccessful" xml:space="preserve">
<value>送信成功</value>
</data>
<data name="LogFilteredMessages" xml:space="preserve">
<value>テキストメッセージのフィルタリングログを有効化</value>
</data>
<data name="LogFilteredPfs" xml:space="preserve">
<value>パーティ募集のフィルタリングログを有効化</value>
</data>
<data name="ReportStatusInProgress" xml:space="preserve">
<value>送信中</value>
</data>
<data name="ReportStatusUnknown" xml:space="preserve">
<value>不明</value>
</data>
<data name="ReportStatusMessage" xml:space="preserve">
<value>最後の報告の状態: {0}</value>
</data>
<data name="ReportChatTab" xml:space="preserve">
<value>チャット</value>
</data>
<data name="ReportColumnChannel" xml:space="preserve">
<value>チャンネル</value>
</data>
<data name="ReportColumnReason" xml:space="preserve">
<value>理由</value>
</data>
<data name="ReportColumnSender" xml:space="preserve">
<value>発言者</value>
</data>
<data name="ReportColumnMessage" xml:space="preserve">
<value>内容</value>
</data>
<data name="ReportPartyFinderTab" xml:space="preserve">
<value>パーティ募集</value>
</data>
<data name="ReportColumnHost" xml:space="preserve">
<value>募集者</value>
</data>
<data name="ReportColumnDescription" xml:space="preserve">
<value>説明</value>
</data>
<data name="ReportModalTitle" xml:space="preserve">
<value>{0}に報告</value>
</data>
<data name="ReportModalHelp2" xml:space="preserve">
<value>NoSolicitingは英語のメッセージにのみ有効です。 他の言語でメッセージを報告しないでください。</value>
</data>
<data name="ReportModalDisabledCustom" xml:space="preserve">
<value>カスタムフィルタでフィルタリングされたメッセージを報告することはできません。</value>
</data>
<data name="ReportModalReport" xml:space="preserve">
<value>報告</value>
</data>
<data name="ReportModalCopy" xml:space="preserve">
<value>クリップボードにコピー</value>
</data>
<data name="ReportModalCancel" xml:space="preserve">
<value>キャンセル</value>
</data>
<data name="TradeCategory" xml:space="preserve">
<value>トレード広告</value>
</data>
<data name="NormalCategory" xml:space="preserve">
<value>通常メッセージ</value>
</data>
<data name="PhishingCategory" xml:space="preserve">
<value>フィッシングメッセージ</value>
</data>
<data name="RmtContentCategory" xml:space="preserve">
<value>RMT (コンテンツ)</value>
</data>
<data name="RmtGilCategory" xml:space="preserve">
<value>RMT (ギル)</value>
</data>
<data name="StaticCategory" xml:space="preserve">
<value>固定パーティ募集</value>
</data>
<data name="CommunityCategory" xml:space="preserve">
<value>コミュニティ広告</value>
</data>
<data name="StaticSubCategory" xml:space="preserve">
<value>固定パーティ補充の募集</value>
</data>
<data name="FreeCompanyDescription" xml:space="preserve">
<value>フリーカンパニーの募集広告</value>
</data>
<data name="NormalDescription" xml:space="preserve">
<value>フィルタリングされるべきでない通常のメッセージ</value>
</data>
<data name="PhishingDescription" xml:space="preserve">
<value>アカウントを盗むために、人を騙してアカウント情報を明かそうとするメッセージ</value>
</data>
<data name="RmtContentDescription" xml:space="preserve">
<value>リアルマネーを使ったコンテンツの取引</value>
</data>
<data name="RoleplayingDescription" xml:space="preserve">
<value>個人的なRP、RPコミュニティ、会場、その他ロールプレイに関連するものの広告</value>
</data>
<data name="StaticDescription" xml:space="preserve">
<value>固定パーティのメンバー募集や固定パーティを探すプレイヤー</value>
</data>
<data name="StaticSubDescription" xml:space="preserve">
<value>固定パーティのメンバー補充募集</value>
</data>
<data name="ReportToNoSoliciting" xml:space="preserve">
<value>NoSolicitingに報告</value>
</data>
<data name="ReportToastSuccess" xml:space="preserve">
<value>{0}のパーティ募集メッセージが正常に報告されました。</value>
</data>
<data name="AdvancedMode" xml:space="preserve">
<value>高度な設定</value>
</data>
<data name="AdvancedWarning1" xml:space="preserve">
<value>高度な設定の変更は、自分が何をしているのかを理解していない限り行わないでください。</value>
</data>
<data name="EnableCustomChatFilters" xml:space="preserve">
<value>カスタムチャットフィルタを有効化</value>
</data>
<data name="FilterPrivatePfs" xml:space="preserve">
<value>プライベート募集にフィルタを適用</value>
</data>
<data name="ReportHelp" xml:space="preserve">
<value>以下のエントリをクリックすることで、誤分類を開発者に報告することができます。</value>
</data>
<data name="ReportColumnTimestamp" xml:space="preserve">
<value>タイムスタンプ</value>
</data>
<data name="ReportModalHelp1" xml:space="preserve">
<value>このメッセージを報告することで、開発者はこのメッセージが誤って分類されたことを知ることができます。</value>
</data>
<data name="FreeCompanyCategory" xml:space="preserve">
<value>FC広告</value>
</data>
<data name="RoleplayingCategory" xml:space="preserve">
<value>ロールプレイ広告</value>
</data>
<data name="TradeDescription" xml:space="preserve">
<value>ギルを対価にクラフターの製作を行う募集やレアアイテムの取引など、アイテムやサービスをギルと交換することを宣伝するメッセージ</value>
</data>
<data name="RmtGilDescription" xml:space="preserve">
<value>リアルマネーを使ったアイテムやギルの取引</value>
</data>
<data name="CommunityDescription" xml:space="preserve">
<value>一般的なDiscordサーバなどの汎用コミュニティの広告</value>
</data>
<data name="ReportToastFailure" xml:space="preserve">
<value>{0}のパーティ募集メッセージの報告に失敗しました。</value>
</data>
<data name="ModelStatusUninitialised" xml:space="preserve">
<value>未初期化</value>
</data>
<data name="ModelStatusPreparing" xml:space="preserve">
<value>モデルの更新準備中</value>
</data>
<data name="ModelStatusDownloadingManifest" xml:space="preserve">
<value>モデルマニフェストのダウンロード中</value>
</data>
<data name="ModelStatusDownloadingModel" xml:space="preserve">
<value>モデルのダウンロード中</value>
</data>
<data name="ModelStatusInitialising" xml:space="preserve">
<value>モデルと分類器の初期化中</value>
</data>
<data name="ModelStatusInitialised" xml:space="preserve">
<value>準備完了</value>
</data>
</root>

View File

@ -0,0 +1,319 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="OtherTab" xml:space="preserve">
<value>Outros</value>
</data>
<data name="LogFilteredPfs" xml:space="preserve">
<value>Registrar PFs filtrados</value>
</data>
<data name="LogFilteredMessages" xml:space="preserve">
<value>Registrar mensagens filtradas</value>
</data>
<data name="Settings" xml:space="preserve">
<value>Configurações de {0}</value>
</data>
<data name="AdvancedMode" xml:space="preserve">
<value>Modo avançado</value>
</data>
<data name="ModelTab" xml:space="preserve">
<value>Modelo</value>
</data>
<data name="ModelTabVersion" xml:space="preserve">
<value>Versão: {0}</value>
</data>
<data name="ModelTabStatus" xml:space="preserve">
<value>Status do modelo: {0}</value>
</data>
<data name="ModelTabError" xml:space="preserve">
<value>Último erro: {0}</value>
</data>
<data name="UpdateModel" xml:space="preserve">
<value>Atualizar modelo</value>
</data>
<data name="FiltersTab" xml:space="preserve">
<value>Filtros</value>
</data>
<data name="AdvancedWarning1" xml:space="preserve">
<value>Não altere as configurações avançadas a não ser que você saiba o que está fazendo.</value>
</data>
<data name="AdvancedWarning2" xml:space="preserve">
<value>O modelo de machine learning foi treinado com certos canais em mente.</value>
</data>
<data name="OtherFiltersTab" xml:space="preserve">
<value>Outros filtros</value>
</data>
<data name="ChatFilters" xml:space="preserve">
<value>Filtros de Chat</value>
</data>
<data name="EnableCustomChatFilters" xml:space="preserve">
<value>Ativar filtros de chat personalizados</value>
</data>
<data name="PartyFinderFilters" xml:space="preserve">
<value>Filtros do Party Finder</value>
</data>
<data name="FilterIlvlPfs" xml:space="preserve">
<value>Filtrar PFs com item level acima do máximo</value>
</data>
<data name="ShowReportingWindow" xml:space="preserve">
<value>Mostrar janela de relato</value>
</data>
<data name="FilterPrivatePfs" xml:space="preserve">
<value>Aplicar filtros a anúncios de Party Finder privado</value>
</data>
<data name="EnableCustomPartyFinderFilters" xml:space="preserve">
<value>Ativar filtros de Party Finder personalizados</value>
</data>
<data name="SubstringsToFilter" xml:space="preserve">
<value>Subtextos a filtrar</value>
</data>
<data name="SaveFilters" xml:space="preserve">
<value>Salvar filtros</value>
</data>
<data name="Reporting" xml:space="preserve">
<value>Relatar {0}</value>
</data>
<data name="ReportHelp" xml:space="preserve">
<value>Clique em uma das entradas abaixo para reportá-la ao desenvolvedor como categorizada de forma errônea.</value>
</data>
<data name="ReportStatusFailure" xml:space="preserve">
<value>Falha no envio</value>
</data>
<data name="ReportStatusSuccessful" xml:space="preserve">
<value>enviado com sucesso</value>
</data>
<data name="ReportStatusInProgress" xml:space="preserve">
<value>enviando</value>
</data>
<data name="ReportStatusUnknown" xml:space="preserve">
<value>desconhecido</value>
</data>
<data name="ReportChatTab" xml:space="preserve">
<value>Chat</value>
</data>
<data name="ReportColumnTimestamp" xml:space="preserve">
<value>Carimbo de data / hora</value>
</data>
<data name="ReportColumnChannel" xml:space="preserve">
<value>Canal</value>
</data>
<data name="ReportColumnReason" xml:space="preserve">
<value>Motivo</value>
</data>
<data name="ReportColumnSender" xml:space="preserve">
<value>Remetente</value>
</data>
<data name="ReportColumnMessage" xml:space="preserve">
<value>Mensagem</value>
</data>
<data name="ReportPartyFinderTab" xml:space="preserve">
<value>Party Finder</value>
</data>
<data name="ReportColumnHost" xml:space="preserve">
<value>Anfitrião</value>
</data>
<data name="ReportColumnDescription" xml:space="preserve">
<value>Descrição</value>
</data>
<data name="ReportModalTitle" xml:space="preserve">
<value>Relatar a(ao) {0}</value>
</data>
<data name="ReportModalDisabledCustom" xml:space="preserve">
<value>Voçê não pode relatar as mensagens filtradas por causa de um filtro personalizado.</value>
</data>
<data name="ReportModalReport" xml:space="preserve">
<value>Relatar</value>
</data>
<data name="ReportModalCopy" xml:space="preserve">
<value>Copiar para a área de transferência</value>
</data>
<data name="ReportModalCancel" xml:space="preserve">
<value>Cancelar</value>
</data>
<data name="TradeCategory" xml:space="preserve">
<value>Anúncios propondo trocas</value>
</data>
<data name="FreeCompanyCategory" xml:space="preserve">
<value>Anúncios de Free Company</value>
</data>
<data name="NormalCategory" xml:space="preserve">
<value>Mensagens normais</value>
</data>
<data name="PhishingCategory" xml:space="preserve">
<value>Mensagens de Phishing</value>
</data>
<data name="RmtContentCategory" xml:space="preserve">
<value>RMT (conteúdo)</value>
</data>
<data name="RmtGilCategory" xml:space="preserve">
<value>RMT (gil)</value>
</data>
<data name="StaticCategory" xml:space="preserve">
<value>Recrutamentos para Statics</value>
</data>
<data name="CommunityCategory" xml:space="preserve">
<value>Anúncios de Comunidades</value>
</data>
<data name="StaticSubCategory" xml:space="preserve">
<value>Substitutos para Statics</value>
</data>
<data name="FreeCompanyDescription" xml:space="preserve">
<value>Propagandas de Free Companies</value>
</data>
<data name="NormalDescription" xml:space="preserve">
<value>Mensagens normais que não devem ser filtradas</value>
</data>
<data name="ModelStatusDownloadingManifest" xml:space="preserve">
<value>Baixando manifest de modelos</value>
</data>
<data name="ModelStatusDownloadingModel" xml:space="preserve">
<value>Baixando modelo</value>
</data>
<data name="ModelStatusInitialising" xml:space="preserve">
<value>Inicializando modelo e classificador</value>
</data>
<data name="ModelStatusInitialised" xml:space="preserve">
<value>Inicializado</value>
</data>
<data name="PhishingDescription" xml:space="preserve">
<value>Mensagens tentando enganar você para que você revele dados de sua conta para poder roubá-la</value>
</data>
<data name="RmtContentDescription" xml:space="preserve">
<value>Conteúdo que envolva troca por dinheiro real (prática conhecida como venda de conteúdo)</value>
</data>
<data name="RmtGilDescription" xml:space="preserve">
<value>Troca por dinheiro real que envolva gil ou itens (também conhecidos como bots de RMT)</value>
</data>
<data name="RoleplayingDescription" xml:space="preserve">
<value>Propaganda de RP pessoal, comunidades ou locais de RP ou qualquer coisa que envolva roleplaying</value>
</data>
<data name="StaticDescription" xml:space="preserve">
<value>Statics procurando membros ou jogadores procurando por um Static</value>
</data>
<data name="CommunityDescription" xml:space="preserve">
<value>Propaganda de comunidades, em geral servidores no Discord</value>
</data>
<data name="StaticSubDescription" xml:space="preserve">
<value>Statics procurando tapa-buracos para membros</value>
</data>
<data name="ReportToNoSoliciting" xml:space="preserve">
<value>Relatar ao NoSoliciting</value>
</data>
<data name="ReportToastSuccess" xml:space="preserve">
<value>Anúncio de Party Finder criado pelo jogador {0} relatado com sucesso.</value>
</data>
<data name="RegularExpressionsToFilter" xml:space="preserve">
<value>Expressões regulares a filtrar</value>
</data>
<data name="ReportStatusMessage" xml:space="preserve">
<value>Status do último relato: {0}</value>
</data>
<data name="ReportModalHelp1" xml:space="preserve">
<value>Relatar essa mensagem vai permtir que o desenvolvedor saiba por que você pensa que essa mensagem foi classificada erroneamente.</value>
</data>
<data name="ReportModalHelp2" xml:space="preserve">
<value>NoSoliciting só funciona para mensagens em Inglês. Não relate mensagens em outras línguas.</value>
</data>
<data name="RoleplayingCategory" xml:space="preserve">
<value>Anúncios de Roleplaying</value>
</data>
<data name="TradeDescription" xml:space="preserve">
<value>Mensagens de propaganda de troca de itens ou serviços por gil, tais como omnicrafters oferecendo serviços ou pessoas vendendo itens raros fora do market board</value>
</data>
<data name="ReportToastFailure" xml:space="preserve">
<value>Falha ao relatar anúncio de Party Finder do anfitrião {0}.</value>
</data>
<data name="OtherGameLanguage" xml:space="preserve">
<value>Usar a configuração de idioma do jogo em vez da do Dalamud</value>
</data>
<data name="ModelStatusUninitialised" xml:space="preserve">
<value>Não inicializado</value>
</data>
<data name="ModelStatusPreparing" xml:space="preserve">
<value>Preparando para atualizar modelo</value>
</data>
<data name="ReportModalClassificationOtherDescription" xml:space="preserve">
<value>Mensagens que não caiam em outras categorias.</value>
</data>
<data name="ReportModalClassificationOther" xml:space="preserve">
<value>Outros</value>
</data>
<data name="ReportModalGoToCustomButton" xml:space="preserve">
<value>Ir para filtros personalizados</value>
</data>
<data name="ReportModalDisabledBadModel" xml:space="preserve">
<value>Função relatar está desabilitada por que seu modelo de ML não estava funcionando quando você viu esta mensagem.</value>
</data>
<data name="ReportModalDisabledSameClassification" xml:space="preserve">
<value>Função relatar desabilitada por que você precisa escolher uma classificação diferente da original.</value>
</data>
<data name="ReportModalDisabledItemLevel" xml:space="preserve">
<value>Você não pode relatar mensagens filtradas por causa de item level.</value>
</data>
<data name="ReportModalOriginalClassification" xml:space="preserve">
<value>Classificação original desta mensagem: {0}</value>
</data>
<data name="ReportModalSuggestedClassification" xml:space="preserve">
<value>Como você acha que esta mensagem deveria ter sido classificada?</value>
</data>
<data name="ReportModalDisabledFilterNotEnabled" xml:space="preserve">
<value>Função relatar desabilitada por que você não estava filtrando este tipo de mensagem quando a viu.</value>
</data>
</root>

View File

@ -3,7 +3,7 @@
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
@ -144,16 +144,10 @@
<data name="ReportModalHelp1" xml:space="preserve">
<value>Reporting this message will let the developer know that you think this message was incorrectly classified.</value>
</data>
<data name="ReportModalWasFiltered" xml:space="preserve">
<value>Specifically, this message WAS filtered but shouldn't have been.</value>
</data>
<data name="ReportModalWasNotFiltered" xml:space="preserve">
<value>Specifically, this message WAS NOT filtered but should have been.</value>
</data>
<data name="ReportModalHelp2" xml:space="preserve">
<value>NoSoliciting only works for English messages. Do not report non-English messages.</value>
</data>
<data name="ReportModalCustom" xml:space="preserve">
<data name="ReportModalDisabledCustom" xml:space="preserve">
<value>You cannot report messages filtered because of a custom filter.</value>
</data>
<data name="ReportModalReport" xml:space="preserve">
@ -255,4 +249,37 @@
<data name="ModelStatusInitialised" xml:space="preserve">
<value>Initialised</value>
</data>
</root>
<data name="ReportModalDisabledBadModel" xml:space="preserve">
<value>Reporting is disabled because your ML model was not functioning when you saw this message.</value>
</data>
<data name="ReportModalDisabledSameClassification" xml:space="preserve">
<value>Reporting is disabled because you must choose a different classification than the original.</value>
</data>
<data name="ReportModalDisabledItemLevel" xml:space="preserve">
<value>You cannot report messages filtered because of item level.</value>
</data>
<data name="ReportModalOriginalClassification" xml:space="preserve">
<value>This message's original classification: {0}</value>
</data>
<data name="ReportModalSuggestedClassification" xml:space="preserve">
<value>How do you think this message should have been classified?</value>
</data>
<data name="ReportModalDisabledFilterNotEnabled" xml:space="preserve">
<value>Reporting is disabled because you weren't filtering for this kind of message at the time you saw it.</value>
</data>
<data name="ReportModalClassificationOther" xml:space="preserve">
<value>Other</value>
</data>
<data name="ReportModalGoToCustomButton" xml:space="preserve">
<value>Go to custom filters</value>
</data>
<data name="ReportModalClassificationOtherDescription" xml:space="preserve">
<value>Messages that don't fall under any of the other categories.</value>
</data>
<data name="FluffCategory" xml:space="preserve">
<value>Fluff</value>
</data>
<data name="FluffDescription" xml:space="preserve">
<value>Messages that don't fall into other categories and are not for content (Party Finder)</value>
</data>
</root>

View File

@ -169,13 +169,7 @@
<data name="ReportModalTitle" xml:space="preserve">
<value>报告给 {0}</value>
</data>
<data name="ReportModalWasFiltered" xml:space="preserve">
<value>具体来说,这是一条正常消息,但它被错误地过滤了。</value>
</data>
<data name="ReportModalWasNotFiltered" xml:space="preserve">
<value>具体来说,这是一条应该被过滤的消息,但它被放过了。</value>
</data>
<data name="ReportModalCustom" xml:space="preserve">
<data name="ReportModalDisabledCustom" xml:space="preserve">
<value>你不能报告被自定义过滤规则过滤的消息。</value>
</data>
<data name="ReportModalReport" xml:space="preserve">
@ -295,4 +289,31 @@
<data name="ModelStatusInitialised" xml:space="preserve">
<value>初始化完成</value>
</data>
<data name="ReportModalDisabledFilterNotEnabled" xml:space="preserve">
<value>您暂时无法报告此消息。因为您在当时并没有过滤此类消息。</value>
</data>
<data name="ReportModalDisabledBadModel" xml:space="preserve">
<value>您暂时无法报告此消息。因为机器学习模型在当时并未启用。</value>
</data>
<data name="ReportModalDisabledSameClassification" xml:space="preserve">
<value>您暂时无法报告此消息。因为您必须选择与原始分类不同的分类。</value>
</data>
<data name="ReportModalDisabledItemLevel" xml:space="preserve">
<value>您暂时无法报告此消息。因为它的平均品级实在是太高了!</value>
</data>
<data name="ReportModalOriginalClassification" xml:space="preserve">
<value>此消息的原始分类:{0}</value>
</data>
<data name="ReportModalSuggestedClassification" xml:space="preserve">
<value>您觉得此消息应当被归为何种分类?</value>
</data>
<data name="ReportModalGoToCustomButton" xml:space="preserve">
<value>前往自定义过滤规则</value>
</data>
<data name="ReportModalClassificationOther" xml:space="preserve">
<value>其他</value>
</data>
<data name="ReportModalClassificationOtherDescription" xml:space="preserve">
<value>不属于任何类别的消息。</value>
</data>
</root>

View File

@ -64,7 +64,7 @@
<data name="ReportModalHelp1" xml:space="preserve">
<value>報告此消息將使開發人員知道此消息被錯誤歸類。</value>
</data>
<data name="ReportModalCustom" xml:space="preserve">
<data name="ReportModalDisabledCustom" xml:space="preserve">
<value>你不能報告被客製化過濾規則過濾的消息。</value>
</data>
<data name="OtherTab" xml:space="preserve">
@ -133,12 +133,6 @@
<data name="ReportModalTitle" xml:space="preserve">
<value>報告給 {0}</value>
</data>
<data name="ReportModalWasFiltered" xml:space="preserve">
<value>具體來說,這是一條正常消息,但它被錯誤地過濾了。</value>
</data>
<data name="ReportModalWasNotFiltered" xml:space="preserve">
<value>具體來說,這是一條應該被過濾的消息,但它被略過了。</value>
</data>
<data name="ReportModalHelp2" xml:space="preserve">
<value>NoSoliciting 只對英文消息有效。請不要報告其他語言的消息。</value>
</data>
@ -295,4 +289,31 @@
<data name="ModelStatusInitialised" xml:space="preserve">
<value>初始化完成</value>
</data>
<data name="ReportModalDisabledBadModel" xml:space="preserve">
<value>您暫時無法報告此消息。因為機器學習模型在當時並未啟用。</value>
</data>
<data name="ReportModalDisabledSameClassification" xml:space="preserve">
<value>您暫時無法報告此消息。因為您必須選擇與原始分類不同的分類。</value>
</data>
<data name="ReportModalDisabledItemLevel" xml:space="preserve">
<value>您暫時無法報告此消息。因為它的平均品級太高了。</value>
</data>
<data name="ReportModalOriginalClassification" xml:space="preserve">
<value>此消息的原始分類:{0}</value>
</data>
<data name="ReportModalSuggestedClassification" xml:space="preserve">
<value>您覺得此消息應當被歸為何種分類?</value>
</data>
<data name="ReportModalDisabledFilterNotEnabled" xml:space="preserve">
<value>您暫時無法報告此消息。因為您在當時並沒有過濾此類消息。</value>
</data>
<data name="ReportModalClassificationOther" xml:space="preserve">
<value>其他</value>
</data>
<data name="ReportModalGoToCustomButton" xml:space="preserve">
<value>前往客製化過濾規則</value>
</data>
<data name="ReportModalClassificationOtherDescription" xml:space="preserve">
<value>不屬於任何類別的消息。</value>
</data>
</root>

View File

@ -0,0 +1,201 @@
{
"version": 1,
"dependencies": {
"net7.0-windows7.0": {
"Dalamud.ContextMenu": {
"type": "Direct",
"requested": "[1.3.1, )",
"resolved": "1.3.1",
"contentHash": "ptAxut5PiLnzZ4G/KQdHJVcyklC/BF3otHJ7zYVUPiKBjsOCoF0n/6h2jK7e+8ev2Y1yAY3Wtx2GuXLFQgt9Uw=="
},
"DalamudPackager": {
"type": "Direct",
"requested": "[2.1.12, )",
"resolved": "2.1.12",
"contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg=="
},
"Fody": {
"type": "Direct",
"requested": "[6.8.0, )",
"resolved": "6.8.0",
"contentHash": "hfZ/f8Mezt8aTkgv9nsvFdYoQ809/AqwsJlOGOPYIfBcG2aAIG3v3ex9d8ZqQuFYyMoucjRg4eKy3VleeGodKQ=="
},
"Microsoft.ML": {
"type": "Direct",
"requested": "[2.0.1, )",
"resolved": "2.0.1",
"contentHash": "yD5kWZfisykwiGG66wJToSicY9MP847Grl8kLALQyqyiyQjr408ChdGW1JG7D0wHN7Zbs1KkCZvidi6pMFYR/A==",
"dependencies": {
"Microsoft.ML.CpuMath": "2.0.1",
"Microsoft.ML.DataView": "2.0.1",
"Newtonsoft.Json": "13.0.1",
"System.CodeDom": "4.5.0",
"System.Collections.Immutable": "1.5.0",
"System.Memory": "4.5.3",
"System.Reflection.Emit.Lightweight": "4.3.0",
"System.Threading.Channels": "4.7.1"
}
},
"Resourcer.Fody": {
"type": "Direct",
"requested": "[1.8.1, )",
"resolved": "1.8.1",
"contentHash": "FPeK4jKyyX5+mIjTnHNReGZk2/2xDhmu44UsBI5w9WEhbr4oTMmht3rnBr46A+GCGepC4+2N41K4vExDYiGNVQ==",
"dependencies": {
"Fody": "6.6.4"
}
},
"XivCommon": {
"type": "Direct",
"requested": "[9.0.0, )",
"resolved": "9.0.0",
"contentHash": "avaBp3FmSCi/PiQhntCeBDYOHejdyTWmFtz4pRBVQQ8vHkmRx+YTk1la9dkYBMlXxRXKckEdH1iI1Fu61JlE7w=="
},
"YamlDotNet": {
"type": "Direct",
"requested": "[13.4.0, )",
"resolved": "13.4.0",
"contentHash": "XkqTRgpt/tkqX9sOKcdPAZLVMktR5sBFwt7aK/PP061k9yoUntr/Qc8o9KshjSte34WYTIuCrpBs1+LwR7dFdA=="
},
"Microsoft.ML.CpuMath": {
"type": "Transitive",
"resolved": "2.0.1",
"contentHash": "UjxC9Ju6QPqyvTkj4ggsvnsqKYnbGO1TEypDzKDiIPBGlU/t7Pvx+oV9DDVhZzDKvb34zpLhAm/g79oBuJS3aw=="
},
"Microsoft.ML.DataView": {
"type": "Transitive",
"resolved": "2.0.1",
"contentHash": "w+GkAXlxaut65Lm+Fbp34YTfp0/9jGRn9uiVlL7Lls0/v+4IJM7SMTHfhvegPU48cyI6K2kzaK9j2Va/labhTA==",
"dependencies": {
"System.Collections.Immutable": "1.5.0",
"System.Memory": "4.5.3"
}
},
"Microsoft.NETCore.Platforms": {
"type": "Transitive",
"resolved": "1.1.0",
"contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
},
"Microsoft.NETCore.Targets": {
"type": "Transitive",
"resolved": "1.1.0",
"contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg=="
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.1",
"contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A=="
},
"System.CodeDom": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "gqpR1EeXOuzNQWL7rOzmtdIz3CaXVjSQCiaGOs2ivjPwynKSJYm39X81fdlp7WuojZs/Z5t1k5ni7HtKQurhjw=="
},
"System.Collections.Immutable": {
"type": "Transitive",
"resolved": "1.5.0",
"contentHash": "EXKiDFsChZW0RjrZ4FYHu9aW6+P4MCgEDCklsVseRfhoO0F+dXeMSsMRAlVXIo06kGJ/zv+2w1a2uc2+kxxSaQ=="
},
"System.IO": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"System.Text.Encoding": "4.3.0",
"System.Threading.Tasks": "4.3.0"
}
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.3",
"contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA=="
},
"System.Reflection": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.IO": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Reflection.Emit.ILGeneration": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==",
"dependencies": {
"System.Reflection": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Reflection.Emit.Lightweight": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==",
"dependencies": {
"System.Reflection": "4.3.0",
"System.Reflection.Emit.ILGeneration": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Reflection.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Runtime": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0"
}
},
"System.Text.Encoding": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Threading.Channels": {
"type": "Transitive",
"resolved": "4.7.1",
"contentHash": "6akRtHK/wab3246t4p5v3HQrtQk8LboOt5T4dtpNgsp3zvDeM4/Gx8V12t0h+c/W9/enUrilk8n6EQqdQorZAA=="
},
"System.Threading.Tasks": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"nosoliciting.interface": {
"type": "Project",
"dependencies": {
"Microsoft.ML": "[2.0.1, )"
}
}
}
}
}

View File

@ -5,11 +5,12 @@
## Summary
NoSoliciting filters chat messages and Party Finder listings based on
various built-in and custom filters. The built-in filters are updated
in the `definitions.yaml` file and can be updated without a plugin
update. Custom filters are user-defined and are either
case-insensitive substrings or regular expressions.
various built-in and custom filters. The built-in filters are generated
using data from `NoSoliciting.Trainer/data.csv` to create a machine
learning model. They can be updated without a plugin update. Custom
filters are user-defined and are either case-insensitive substrings or
regular expressions.
All messages and listings filtered are logged in case of false
All messages and listings filtered can be logged in case of false
positives, and there is a reporting mechanism in which users can
report if NoSoliciting had a false positive or a false negative.

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

83
icon.svg Executable file
View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="458.08871"
height="458.08871"
viewBox="0 0 121.20264 121.20264"
version="1.1"
id="svg5"
inkscape:export-filename="C:\Users\Anna\Desktop\nosol.png"
inkscape:export-xdpi="107.298"
inkscape:export-ydpi="107.298"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
sodipodi:docname="nosol.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:document-units="px"
showgrid="false"
inkscape:zoom="1.7039681"
inkscape:cx="175.7662"
inkscape:cy="230.34468"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1592"
inkscape:window-y="32"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
units="px"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-53.447652,-85.066737)">
<path
sodipodi:type="star"
style="fill:#af3310;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path33"
inkscape:flatsided="true"
sodipodi:sides="8"
sodipodi:cx="-396.65961"
sodipodi:cy="345.39066"
sodipodi:r1="243.58626"
sodipodi:r2="225.04436"
sodipodi:arg1="0"
sodipodi:arg2="0.39269908"
inkscape:rounded="0"
inkscape:randomized="0"
d="m -153.07335,345.39066 -71.34476,172.24149 -172.2415,71.34476 -172.24149,-71.34476 -71.34476,-172.24149 71.34476,-172.2415 172.24149,-71.34476 172.2415,71.34476 z"
transform="matrix(0.24444312,0.10125166,-0.10125166,0.24444312,245.98106,101.40213)"
inkscape:transform-center-x="-1.8847157e-06"
inkscape:transform-center-y="-3.1424378e-06"
inkscape:export-filename="C:\Users\Anna\Desktop\path33.png"
inkscape:export-xdpi="61.923672"
inkscape:export-ydpi="61.923672" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:38.4628px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.265;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
x="57.772713"
y="159.98831"
id="text3416"><tspan
sodipodi:role="line"
id="tspan3414"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.265;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
x="57.772713"
y="159.98831">NoSol</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB