Compare commits

...

No commits in common. "v1.8.1" and "master" have entirely different histories.

57 changed files with 7967 additions and 1646 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="26.1.0"/>
<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,20 +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",
@ -33,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) {
@ -41,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) {
@ -123,7 +159,7 @@ namespace NoSoliciting.Trainer {
var pipeline = ctx.Transforms.Conversion.MapValueToKey("Label", nameof(Data.Category))
.Append(ctx.Transforms.CustomMapping(compute.GetMapping(), "Compute"))
.Append(ctx.Transforms.CustomMapping(normalise.GetMapping(), "Normalise"))
.Append(ctx.Transforms.Text.NormalizeText("MsgNormal", nameof(Data.Normalise.Normalised.NormalisedMessage), keepPunctuations: false))
.Append(ctx.Transforms.Text.NormalizeText("MsgNormal", nameof(Data.Normalise.Normalised.NormalisedMessage), keepPunctuations: false, keepNumbers: false))
.Append(ctx.Transforms.Text.TokenizeIntoWords("MsgTokens", "MsgNormal"))
.Append(ctx.Transforms.Text.RemoveDefaultStopWords("MsgNoDefStop", "MsgTokens"))
.Append(ctx.Transforms.Text.RemoveStopWords("MsgNoStop", "MsgNoDefStop", StopWords))
@ -138,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 {
@ -216,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") {

54
NoSoliciting/ContextMenu.cs Executable file
View File

@ -0,0 +1,54 @@
using System;
using System.Linq;
using Dalamud.ContextMenu;
using NoSoliciting.Resources;
namespace NoSoliciting {
public class ContextMenu : IDisposable {
private Plugin Plugin { get; }
internal ContextMenu(Plugin plugin) {
this.Plugin = plugin;
this.Plugin.DalamudContextMenu.OnOpenGameObjectContextMenu += this.OnOpenContextMenu;
}
public void Dispose() {
this.Plugin.DalamudContextMenu.OnOpenGameObjectContextMenu -= this.OnOpenContextMenu;
}
private void OnOpenContextMenu(GameObjectContextMenuOpenArgs args) {
if (args.ParentAddonName != "LookingForGroup") {
return;
}
if (args.ContentIdLower == 0) {
return;
}
var label = Language.ReportToNoSoliciting;
args.AddCustomItem(new GameObjectContextMenuItem(label, this.Report));
}
private void Report(GameObjectContextMenuItemSelectedArgs args) {
if (args.ContentIdLower == 0) {
return;
}
var listing = this.Plugin.Common.Functions.PartyFinder.CurrentListings
.Values
.FirstOrDefault(listing => listing.ContentIdLower == args.ContentIdLower);
if (listing == null) {
return;
}
var message = this.Plugin.PartyFinderHistory.FirstOrDefault(message => message.ActorId == listing.ContentIdLower);
if (message == null) {
return;
}
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,275 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Dalamud.Game.Text;
using Dalamud.Plugin;
using NoSoliciting.Interface;
using NoSoliciting.Properties;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace NoSoliciting {
public class Definitions {
public static string? LastError { get; private set; }
public static DateTime? LastUpdate { get; set; }
private const string Url = "https://git.sr.ht/~jkcclemens/NoSoliciting/blob/master/NoSoliciting/definitions.yaml";
public uint Version { get; private set; }
public Uri ReportUrl { get; private set; }
public Dictionary<string, Definition> Chat { get; private set; }
public Dictionary<string, Definition> PartyFinder { get; private set; }
public static async Task<Definitions> UpdateAndCache(Plugin plugin) {
#if DEBUG
return LoadDefaults();
#endif
Definitions? defs = null;
var download = await Download().ConfigureAwait(true);
if (download != null) {
defs = download.Item1;
try {
UpdateCache(plugin, download.Item2);
} catch (IOException e) {
PluginLog.Log("Could not update cache.");
PluginLog.Log(e.ToString());
}
}
return defs ?? await CacheOrDefault(plugin).ConfigureAwait(true);
}
public static Definitions Load(string text) {
var de = new DeserializerBuilder()
.WithNamingConvention(UnderscoredNamingConvention.Instance)
.WithTypeConverter(new MatcherConverter())
.IgnoreUnmatchedProperties()
.Build();
return de.Deserialize<Definitions>(text);
}
private static async Task<Definitions> CacheOrDefault(Plugin plugin) {
if (plugin == null) {
throw new ArgumentNullException(nameof(plugin), "Plugin cannot be null");
}
var pluginFolder = Util.PluginFolder(plugin);
var cachedPath = Path.Combine(pluginFolder, "definitions.yaml");
if (!File.Exists(cachedPath)) {
goto LoadDefaults;
}
string text;
using (var file = File.OpenText(cachedPath)) {
text = await file.ReadToEndAsync().ConfigureAwait(true);
}
try {
return Load(text);
} catch (YamlException e) {
PluginLog.Log($"Could not load cached definitions: {e}. Loading defaults.");
}
LoadDefaults:
return LoadDefaults();
}
private static Definitions LoadDefaults() {
return Load(Resources.DefaultDefinitions);
}
private static async Task<Tuple<Definitions, string>?> Download() {
try {
using var client = new WebClient();
var text = await client.DownloadStringTaskAsync(Url).ConfigureAwait(true);
LastError = null;
return Tuple.Create(Load(text), text);
} catch (Exception e) when (e is WebException || e is YamlException) {
PluginLog.Log("Could not download newest definitions.");
PluginLog.Log(e.ToString());
LastError = e.Message;
return null;
}
}
private static async void UpdateCache(IDalamudPlugin plugin, string defs) {
var pluginFolder = Util.PluginFolder(plugin);
Directory.CreateDirectory(pluginFolder);
var cachePath = Path.Combine(pluginFolder, "definitions.yaml");
var b = Encoding.UTF8.GetBytes(defs);
using var file = File.OpenWrite(cachePath);
await file.WriteAsync(b, 0, b.Length).ConfigureAwait(true);
}
internal void Initialise(Plugin plugin) {
var defs = this.Chat.Select(e => new KeyValuePair<string, Definition>($"chat.{e.Key}", e.Value))
.Concat(this.PartyFinder.Select(e => new KeyValuePair<string, Definition>($"party_finder.{e.Key}", e.Value)));
foreach (var entry in defs) {
entry.Value.Initialise(entry.Key);
if (!plugin.Config.FilterStatus.TryGetValue(entry.Key, out _)) {
plugin.Config.FilterStatus[entry.Key] = entry.Value.Default;
}
}
plugin.Config.Save();
}
}
public class Definition {
private bool _initialised;
[YamlIgnore]
public string Id { get; private set; }
public List<List<Matcher>> RequiredMatchers { get; private set; } = new();
public List<List<Matcher>> LikelyMatchers { get; private set; } = new();
public int LikelihoodThreshold { get; private set; }
public bool IgnoreCase { get; private set; }
public bool Normalise { get; private set; } = true;
public List<XivChatType> Channels { get; private set; } = new();
public OptionNames Option { get; private set; }
public bool Default { get; private set; }
public void Initialise(string id) {
if (this._initialised) {
return;
}
this._initialised = true;
this.Id = id ?? throw new ArgumentNullException(nameof(id), "string cannot be null");
if (!this.IgnoreCase) {
return;
}
var allMatchers = this.LikelyMatchers
.Concat(this.RequiredMatchers)
.SelectMany(matchers => matchers);
foreach (var matcher in allMatchers) {
matcher.MakeIgnoreCase();
}
}
public bool Matches(XivChatType type, string text) {
if (text == null) {
throw new ArgumentNullException(nameof(text), "string cannot be null");
}
if (this.Channels.Count != 0 && !this.Channels.Contains(type)) {
return false;
}
if (this.Normalise) {
text = NoSolUtil.Normalise(text);
}
if (this.IgnoreCase) {
text = text.ToLowerInvariant();
}
// ensure all required matchers match
var allRequired = this.RequiredMatchers.All(matchers => matchers.Any(matcher => matcher.Matches(text)));
if (!allRequired) {
return false;
}
// calculate likelihood
var likelihood = this.LikelyMatchers.Count(matchers => matchers.Any(matcher => matcher.Matches(text)));
// matches only if likelihood is greater than or equal the threshold
return likelihood >= this.LikelihoodThreshold;
}
}
public class Matcher {
private string? substring;
private Regex? regex;
public Matcher(string substring) {
this.substring = substring ?? throw new ArgumentNullException(nameof(substring), "string cannot be null");
}
public Matcher(Regex regex) {
this.regex = regex ?? throw new ArgumentNullException(nameof(regex), "Regex cannot be null");
}
internal void MakeIgnoreCase() {
if (this.substring != null) {
this.substring = this.substring.ToLowerInvariant();
}
if (this.regex != null) {
this.regex = new Regex(this.regex.ToString(), this.regex.Options | RegexOptions.IgnoreCase);
}
}
public bool Matches(string text) {
if (text == null) {
throw new ArgumentNullException(nameof(text), "string cannot be null");
}
if (this.substring != null) {
return text.Contains(this.substring);
}
if (this.regex != null) {
return this.regex.IsMatch(text);
}
throw new ApplicationException("Matcher created without substring or regex");
}
}
public class OptionNames {
public string Basic { get; private set; }
public string Advanced { get; private set; }
}
internal sealed class MatcherConverter : IYamlTypeConverter {
public bool Accepts(Type type) {
return type == typeof(Matcher);
}
public object ReadYaml(IParser parser, Type type) {
Matcher matcher;
if (parser.TryConsume(out Scalar scalar)) {
matcher = new Matcher(scalar.Value);
} else if (parser.TryConsume(out MappingStart _)) {
if (parser.Consume<Scalar>().Value != "regex") {
throw new ArgumentException("matcher was an object but did not specify regex key");
}
var regex = new Regex(parser.Consume<Scalar>().Value, RegexOptions.Compiled);
matcher = new Matcher(regex);
parser.Consume<MappingEnd>();
} else {
throw new ArgumentException("invalid matcher");
}
return matcher;
}
public void WriteYaml(IEmitter emitter, object value, Type type) {
throw new NotImplementedException();
}
}
}

View File

@ -16,7 +16,7 @@ namespace NoSoliciting {
msg = NoSolUtil.Normalise(msg);
return config.ChatSubstrings.Any(needle => msg.ContainsIgnoreCase(needle))
return config.ValidChatSubstrings.Any(needle => msg.ContainsIgnoreCase(needle))
|| config.CompiledChatRegexes.Any(needle => needle.IsMatch(msg));
}
}

View File

@ -16,8 +16,8 @@ namespace NoSoliciting {
msg = NoSolUtil.Normalise(msg);
return config.PFSubstrings.Any(needle => msg.ContainsIgnoreCase(needle))
|| config.CompiledPFRegexes.Any(needle => needle.IsMatch(msg));
return config.ValidPfSubstrings.Any(needle => msg.ContainsIgnoreCase(needle))
|| config.CompiledPfRegexes.Any(needle => needle.IsMatch(msg));
}
}
}

View File

@ -1,10 +1,8 @@
using Dalamud.Hooking;
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 {
@ -41,24 +39,15 @@ namespace NoSoliciting {
};
private Plugin Plugin { get; }
private bool _clearOnNext;
private delegate IntPtr HandlePfSummaryDelegate(IntPtr param1, IntPtr param2, byte param3);
private readonly Hook<HandlePfSummaryDelegate>? _handleSummaryHook;
private int LastBatch { get; set; } = -1;
private bool _disposedValue;
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;
var summaryPtr = this.Plugin.Interface.TargetModuleScanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B FA 48 8B F1 45 84 C0 74 ?? 0F B7 0A");
this._handleSummaryHook = new Hook<HandlePfSummaryDelegate>(summaryPtr, new HandlePfSummaryDelegate(this.HandleSummary));
this._handleSummaryHook.Enable();
this.Plugin.ChatGui.CheckMessageHandled += this.OnChat;
this.Plugin.PartyFinderGui.ReceiveListing += this.OnListing;
}
private void Dispose(bool disposing) {
@ -67,9 +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._handleSummaryHook?.Dispose();
this.Plugin.ChatGui.CheckMessageHandled -= this.OnChat;
this.Plugin.PartyFinderGui.ReceiveListing -= this.OnListing;
}
this._disposedValue = true;
@ -82,70 +70,55 @@ namespace NoSoliciting {
}
private void OnChat(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled) {
isHandled = isHandled || this.FilterMessage(type, sender, message);
isHandled = isHandled || this.FilterMessage(type, senderId, sender, message);
}
private void OnListing(PartyFinderListing listing, PartyFinderListingEventArgs args) {
try {
if (this._clearOnNext) {
if (this.LastBatch != args.BatchNumber) {
this.Plugin.ClearPartyFinderHistory();
this._clearOnNext = false;
}
string? reason = null;
uint? version = null;
if (this.Plugin.MlReady) {
version = this.Plugin.MlFilter!.Version;
reason = this.MlListingFilterReason(listing);
} else if (this.Plugin.DefsReady) {
version = this.Plugin.Definitions!.Version;
reason = this.DefsListingFilterReason(listing);
}
this.LastBatch = args.BatchNumber;
if (version == null) {
return;
}
var version = this.Plugin.MlFilter?.Version;
var (category, reason) = this.MlListingFilterReason(listing);
this.Plugin.AddPartyFinderHistory(new Message(
version.Value,
version,
ChatType.None,
listing.ContentIdLower,
listing.Name,
listing.Description,
this.Plugin.MlReady,
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}");
}
}
private bool FilterMessage(XivChatType type, SeString sender, SeString message) {
private bool FilterMessage(XivChatType type, uint senderId, SeString sender, SeString message) {
if (message == null) {
throw new ArgumentNullException(nameof(message), "SeString cannot be null");
}
if (this.Plugin.MlReady) {
return this.MlFilterMessage(type, sender, message);
}
return this.Plugin.DefsReady && this.DefsFilterMessage(type, sender, message);
return this.MlFilterMessage(type, senderId, sender, message);
}
private bool MlFilterMessage(XivChatType type, SeString sender, SeString message) {
if (this.Plugin.MlFilter == null) {
return false;
}
private bool MlFilterMessage(XivChatType type, uint senderId, SeString sender, SeString message) {
var chatType = ChatTypeExt.FromDalamud(type);
// NOTE: don't filter on user-controlled chat types here because custom filters are supposed to check all
@ -156,148 +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,
ChatTypeExt.FromDalamud(type),
sender,
message,
true,
reason
));
if (filter && this.Plugin.Config.LogFilteredChat) {
PluginLog.Log($"Filtered chat message ({reason}): {text}");
}
return filter;
}
private bool DefsFilterMessage(XivChatType type, SeString sender, SeString message) {
if (this.Plugin.Definitions == null || ChatTypeExt.FromDalamud(type).IsBattle()) {
return false;
}
var text = message.TextValue;
string? reason = null;
var filter = false;
foreach (var def in this.Plugin.Definitions.Chat.Values) {
filter = filter || this.Plugin.Config.FilterStatus.TryGetValue(def.Id, out var enabled)
&& enabled
&& def.Matches(type, text)
&& SetReason(out reason, def.Id);
}
// check for custom filters if enabled
filter = filter || this.Plugin.Config.CustomChatFilter
&& Chat.MatchesCustomFilters(text, this.Plugin.Config)
&& SetReason(out reason, "custom");
this.Plugin.AddMessageHistory(new Message(
this.Plugin.Definitions.Version,
var history = new Message(
this.Plugin.MlFilter?.Version,
ChatTypeExt.FromDalamud(type),
senderId,
sender,
message,
classification,
custom,
false,
reason
));
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 string? DefsListingFilterReason(PartyFinderListing listing) {
if (this.Plugin.Definitions == null) {
return null;
}
var desc = listing.Description.TextValue;
if (this.Plugin.Config.FilterHugeItemLevelPFs && listing.MinimumItemLevel > FilterUtil.MaxItemLevelAttainable(this.Plugin.Interface.Data)) {
return "ilvl";
}
foreach (var def in this.Plugin.Definitions.PartyFinder.Values) {
if (this.Plugin.Config.FilterStatus.TryGetValue(def.Id, out var enabled) && enabled && def.Matches(XivChatType.None, desc)) {
return def.Id;
}
}
// check for custom filters if enabled
if (this.Plugin.Config.CustomPFFilter && PartyFinder.MatchesCustomFilters(desc, this.Plugin.Config)) {
return "custom";
}
return null;
}
private IntPtr HandleSummary(IntPtr param1, IntPtr param2, byte param3) {
this._clearOnNext = true;
return this._handleSummaryHook!.Original(param1, param2, param3);
}
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,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Resourcer />
</Weavers>
<Resourcer/>
</Weavers>

View File

@ -1,23 +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"/>
</ItemGroup>
<ILRepack
Parallel="true"
Internalize="true"
InputAssemblies="@(InputAssemblies)"
LibraryPath="$(OutputPath)"
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

@ -1,18 +1,31 @@
using System;
using System.Collections.Generic;
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
& ~ImGuiTableFlags.BordersOuterV
| ImGuiTableFlags.PadOuterX
| ImGuiTableFlags.RowBg
| ImGuiTableFlags.SizingFixedFit
| ImGuiTableFlags.ScrollY
| ImGuiTableFlags.Hideable
| ImGuiTableFlags.Reorderable
| ImGuiTableFlags.Resizable;
private Plugin Plugin { get; }
private ReportStatus LastReportStatus { get; set; } = ReportStatus.None;
@ -24,6 +37,8 @@ namespace NoSoliciting.Interface {
set => this._showReporting = value;
}
internal Message? ToShowModal { get; set; }
public Report(Plugin plugin) {
this.Plugin = plugin;
}
@ -37,26 +52,36 @@ 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);
if (!ImGui.Begin("NoSoliciting reporting", ref this._showReporting)) {
var windowTitle = string.Format(Language.Reporting, Plugin.Name);
if (!ImGui.Begin($"{windowTitle}###NoSoliciting reporting", ref this._showReporting)) {
ImGui.End();
return;
}
ImGui.TextUnformatted("Click on one of the entries below to report it to the developer as miscategorised.");
ImGui.TextUnformatted(Language.ReportHelp);
if (this.LastReportStatus != ReportStatus.None) {
var status = this.LastReportStatus switch {
ReportStatus.Failure => "failed to send",
ReportStatus.Successful => "sent successfully",
ReportStatus.InProgress => "sending",
_ => "unknown",
ReportStatus.Failure => Language.ReportStatusFailure,
ReportStatus.Successful => Language.ReportStatusSuccessful,
ReportStatus.InProgress => Language.ReportStatusInProgress,
_ => Language.ReportStatusUnknown,
};
ImGui.TextUnformatted($"Last report status: {status}");
var reportStatus = Language.ReportStatusMessage;
ImGui.TextUnformatted(string.Format(reportStatus, status));
}
ImGui.Separator();
@ -73,46 +98,47 @@ namespace NoSoliciting.Interface {
}
private void ChatTab() {
if (!ImGui.BeginTabItem("Chat##chat-report")) {
if (!ImGui.BeginTabItem($"{Language.ReportChatTab}###chat-report")) {
return;
}
float[] maxSizes = {0f, 0f, 0f, 0f};
if (ImGui.BeginChild("##chat-messages", new Vector2(-1, -1))) {
ImGui.Columns(5);
if (ImGui.BeginTable("nosol-chat-report-table", 5, TableFlags)) {
ImGui.TableSetupColumn(Language.ReportColumnTimestamp);
ImGui.TableSetupColumn(Language.ReportColumnChannel);
ImGui.TableSetupColumn(Language.ReportColumnReason);
ImGui.TableSetupColumn(Language.ReportColumnSender);
ImGui.TableSetupColumn(Language.ReportColumnMessage, ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableHeadersRow();
AddRow(maxSizes, "Timestamp", "Channel", "Reason", "Sender", "Message");
ImGui.Separator();
foreach (var message in this.Plugin.MessageHistory) {
ImGui.TableNextRow();
foreach (var message in this.Plugin.MessageHistory) {
if (message.FilterReason != null) {
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(238f / 255f, 71f / 255f, 71f / 255f, 1f));
if (message.Filtered) {
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(238f / 255f, 71f / 255f, 71f / 255f, 1f));
}
var sender = message.Sender.Payloads
.Where(payload => payload.Type == PayloadType.RawText)
.Cast<TextPayload>()
.Select(payload => payload.Text)
.FirstOrDefault() ?? "";
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.Filtered) {
ImGui.PopStyleColor();
}
this.SetUpReportModal(message);
}
var sender = message.Sender.Payloads
.Where(payload => payload.Type == PayloadType.RawText)
.Cast<TextPayload>()
.Select(payload => payload.Text)
.FirstOrDefault() ?? "";
if (AddRow(maxSizes, message.Timestamp.ToString(CultureInfo.CurrentCulture), message.ChatType.Name(this.Plugin.Interface.Data), message.FilterReason ?? "", sender, message.Content.TextValue)) {
ImGui.OpenPopup($"###modal-message-{message.Id}");
}
if (message.FilterReason != null) {
ImGui.PopStyleColor();
}
this.SetUpReportModal(message);
ImGui.EndTable();
}
for (var idx = 0; idx < maxSizes.Length; idx++) {
ImGui.SetColumnWidth(idx, maxSizes[idx] + ImGui.GetStyle().ItemSpacing.X * 2);
}
ImGui.Columns(1);
ImGui.EndChild();
}
@ -120,7 +146,7 @@ namespace NoSoliciting.Interface {
}
private void PartyFinderTab() {
if (!ImGui.BeginTabItem("Party Finder##pf-report")) {
if (!ImGui.BeginTabItem($"{Language.ReportPartyFinderTab}###pf-report")) {
return;
}
@ -129,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;
}
@ -140,42 +166,42 @@ namespace NoSoliciting.Interface {
}
#endif
float[] maxSizes = {0f, 0f, 0f};
if (ImGui.BeginChild("##pf-messages", new Vector2(-1, -1))) {
ImGui.Columns(4);
if (ImGui.BeginTable("nosol-pf-report-table", 4, TableFlags)) {
ImGui.TableSetupColumn(Language.ReportColumnTimestamp);
ImGui.TableSetupColumn(Language.ReportColumnReason);
ImGui.TableSetupColumn(Language.ReportColumnHost);
ImGui.TableSetupColumn(Language.ReportColumnDescription, ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableHeadersRow();
AddRow(maxSizes, "Timestamp", "Reason", "Host", "Description");
ImGui.Separator();
foreach (var message in this.Plugin.PartyFinderHistory) {
ImGui.TableNextRow();
foreach (var message in this.Plugin.PartyFinderHistory) {
if (message.FilterReason != null) {
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(238f / 255f, 71f / 255f, 71f / 255f, 1f));
if (message.Filtered) {
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(238f / 255f, 71f / 255f, 71f / 255f, 1f));
}
var sender = message.Sender.Payloads
.Where(payload => payload.Type == PayloadType.RawText)
.Cast<TextPayload>()
.Select(payload => payload.Text)
.FirstOrDefault() ?? "";
if (AddRow(message.Timestamp.ToString(CultureInfo.CurrentCulture), message.FilterReason ?? string.Empty, sender, message.Content.TextValue)) {
ImGui.OpenPopup($"###modal-message-{message.Id}");
}
if (message.Filtered) {
ImGui.PopStyleColor();
}
this.SetUpReportModal(message);
}
var sender = message.Sender.Payloads
.Where(payload => payload.Type == PayloadType.RawText)
.Cast<TextPayload>()
.Select(payload => payload.Text)
.FirstOrDefault() ?? "";
if (AddRow(maxSizes, message.Timestamp.ToString(CultureInfo.CurrentCulture), message.FilterReason ?? "", sender, message.Content.TextValue)) {
ImGui.OpenPopup($"###modal-message-{message.Id}");
}
if (message.FilterReason != null) {
ImGui.PopStyleColor();
}
this.SetUpReportModal(message);
ImGui.EndTable();
}
for (var idx = 0; idx < maxSizes.Length; idx++) {
ImGui.SetColumnWidth(idx, maxSizes[idx] + ImGui.GetStyle().ItemSpacing.X * 2);
}
ImGui.Columns(1);
ImGui.EndChild();
}
@ -184,25 +210,35 @@ 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));
if (!ImGui.BeginPopupModal($"Report to NoSoliciting###modal-message-{message.Id}")) {
return;
var modalTitle = string.Format(Language.ReportModalTitle, Plugin.Name);
if (!ImGui.BeginPopupModal($"{modalTitle}###modal-message-{message.Id}")) {
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();
if (!message.Ml) {
ImGui.TextUnformatted("You cannot report messages filtered by definitions. Please switch to machine learning mode.");
goto EndPopup;
}
ImGui.TextUnformatted("Reporting this message will let the developer know that you think this message was incorrectly classified.");
ImGui.TextUnformatted(message.FilterReason != null
? "Specifically, this message WAS filtered but shouldn't have been."
: "Specifically, this message WAS NOT filtered but should have been.");
ImGui.TextUnformatted(Language.ReportModalHelp1);
ImGui.Separator();
@ -210,43 +246,103 @@ namespace NoSoliciting.Interface {
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("NoSoliciting only works for English messages. Do not report non-English messages.");
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("You cannot report messages filtered because of a custom filter.");
ImGui.TextUnformatted(errorText);
ImGui.PopStyleColor();
} else {
if (ImGui.Button("Report")) {
Task.Run(async () => {
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);
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;
}
}
} catch (Exception) {
// ignored
}
});
this.ToShowModal = null;
} else {
this.ReportMessage(message, suggested);
}
this.LastReportStatus = resp == "{\"message\":\"ok\"}" ? ReportStatus.Successful : ReportStatus.Failure;
PluginLog.Log(resp == null
? "Report not sent. ML model not set."
: $"Report sent. Response: {resp}");
});
ImGui.CloseCurrentPopup();
closing = true;
}
ImGui.SameLine();
}
if (ImGui.Button("Copy to clipboard")) {
if (ImGui.Button($"{Language.ReportModalCopy}##report-copy-{message.Id}")) {
ImGui.SetClipboardText(message.Content.TextValue);
}
@ -259,24 +355,38 @@ namespace NoSoliciting.Interface {
ImGui.SameLine();
EndPopup:
if (ImGui.Button("Cancel")) {
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
#region Utility
private static bool AddRow(IList<float> maxSizes, params string[] args) {
private static bool AddRow(params string[] args) {
var clicked = false;
for (var i = 0; i < args.Length; i++) {
ImGui.TableNextColumn();
var arg = args[i];
var last = i == args.Length - 1;
@ -285,21 +395,62 @@ namespace NoSoliciting.Interface {
}
ImGui.TextUnformatted(arg);
if (last) {
ImGui.PopTextWrapPos();
}
clicked = clicked || ImGui.IsItemClicked();
if (!last) {
maxSizes[i] = Math.Max(maxSizes[i], ImGui.CalcTextSize(arg).X);
}
ImGui.NextColumn();
}
return clicked;
}
private static void WrappedTooltip(string text) {
if (!ImGui.IsItemHovered()) {
return;
}
ImGui.BeginTooltip();
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 24);
ImGui.TextUnformatted(text);
ImGui.PopTextWrapPos();
ImGui.EndTooltip();
}
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) {
var json = message.ToJson(suggested);
if (json != null) {
resp = await client.UploadStringTaskAsync(reportUrl, json).ConfigureAwait(true);
}
}
} catch (Exception) {
// ignored
}
var status = resp == "{\"message\":\"ok\"}" ? ReportStatus.Successful : ReportStatus.Failure;
if (status == ReportStatus.Failure) {
Plugin.Log.Warning($"Failed to report message:\n{resp}");
}
this.LastReportStatus = status;
Plugin.Log.Info(resp == null
? "Report not sent. ML model not set."
: $"Report sent. Response: {resp}");
return status;
}
#endregion
}
}

View File

@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text;
using System.Text.RegularExpressions;
using Dalamud.Interface;
using ImGuiNET;
using NoSoliciting.Ml;
using NoSoliciting.Resources;
namespace NoSoliciting.Interface {
public class Settings : IDisposable {
@ -22,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;
}
@ -37,27 +39,18 @@ namespace NoSoliciting.Interface {
this.ShowSettings = !this.ShowSettings;
}
public void Show() {
this.ShowSettings = true;
}
public void Draw() {
if (!this.ShowSettings || !ImGui.Begin($"{this.Plugin.Name} settings", ref this._showSettings)) {
var windowTitle = string.Format(Language.Settings, Plugin.Name);
if (!this.ShowSettings || !ImGui.Begin($"{windowTitle}###NoSoliciting settings", ref this._showSettings)) {
return;
}
var modes = new[] {
"Machine learning (default)",
"Definition matchers (obsolete)",
};
var modeIndex = this.Plugin.Config.UseMachineLearning ? 0 : 1;
if (ImGui.Combo("Filter mode", ref modeIndex, modes, modes.Length)) {
this.Plugin.Config.UseMachineLearning = modeIndex == 0;
this.Plugin.Config.Save();
if (this.Plugin.Config.UseMachineLearning) {
this.Plugin.InitialiseMachineLearning(false);
}
}
var advanced = this.Plugin.Config.AdvancedMode;
if (ImGui.Checkbox("Advanced mode", ref advanced)) {
if (ImGui.Checkbox(Language.AdvancedMode, ref advanced)) {
this.Plugin.Config.AdvancedMode = advanced;
this.Plugin.Config.Save();
}
@ -68,35 +61,17 @@ namespace NoSoliciting.Interface {
return;
}
if (this.Plugin.Config.UseMachineLearning) {
this.DrawMachineLearningConfig();
} else {
this.DrawDefinitionsConfig();
}
this.DrawMachineLearningConfig();
this.DrawOtherFilters();
if (ImGui.BeginTabItem("Other")) {
var logFilteredPfs = this.Plugin.Config.LogFilteredPfs;
if (ImGui.Checkbox("Log filtered PFs", ref logFilteredPfs)) {
this.Plugin.Config.LogFilteredPfs = logFilteredPfs;
this.Plugin.Config.Save();
}
var logFilteredMessages = this.Plugin.Config.LogFilteredChat;
if (ImGui.Checkbox("Log filtered messages", ref logFilteredMessages)) {
this.Plugin.Config.LogFilteredChat = logFilteredMessages;
this.Plugin.Config.Save();
}
ImGui.EndTabItem();
}
this.DrawOtherTab();
ImGui.EndTabBar();
ImGui.Separator();
if (ImGui.Button("Show reporting window")) {
if (ImGui.Button(Language.ShowReportingWindow)) {
this.Ui.Report.Open();
}
@ -112,20 +87,20 @@ namespace NoSoliciting.Interface {
this.DrawBasicMachineLearningConfig();
}
if (!ImGui.BeginTabItem("Model")) {
if (!ImGui.BeginTabItem($"{Language.ModelTab}###model-tab")) {
return;
}
ImGui.TextUnformatted($"Version: {this.Plugin.MlFilter?.Version}");
ImGui.TextUnformatted($"Model status: {this.Plugin.MlStatus.Description()}");
ImGui.TextUnformatted(string.Format(Language.ModelTabVersion, this.Plugin.MlFilter?.Version));
ImGui.TextUnformatted(string.Format(Language.ModelTabStatus, this.Plugin.MlStatus.Description()));
var lastError = MlFilter.LastError;
if (lastError != null) {
ImGui.TextUnformatted($"Last error: {lastError}");
ImGui.TextUnformatted(string.Format(Language.ModelTabError, lastError));
}
if (ImGui.Button("Update model")) {
if (ImGui.Button(Language.UpdateModel)) {
// prevent issues when people spam the button
if (ImGui.GetIO().KeyCtrl || this.Plugin.MlStatus == MlFilterStatus.Uninitialised || this.Plugin.MlStatus == MlFilterStatus.Initialised) {
if (ImGui.GetIO().KeyCtrl || this.Plugin.MlStatus is MlFilterStatus.Uninitialised or MlFilterStatus.Initialised) {
this.Plugin.MlFilter?.Dispose();
this.Plugin.MlFilter = null;
this.Plugin.MlStatus = MlFilterStatus.Uninitialised;
@ -137,7 +112,7 @@ namespace NoSoliciting.Interface {
}
private void DrawBasicMachineLearningConfig() {
if (!ImGui.BeginTabItem("Filters")) {
if (!ImGui.BeginTabItem($"{Language.FiltersTab}###filters-tab")) {
return;
}
@ -168,13 +143,13 @@ namespace NoSoliciting.Interface {
}
private void DrawAdvancedMachineLearningConfig() {
if (!ImGui.BeginTabItem("Filters")) {
if (!ImGui.BeginTabItem($"{Language.FiltersTab}###filters-tab")) {
return;
}
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(255f, 204f, 0f, 1f));
ImGui.TextUnformatted("Do not change advanced settings unless you know what you are doing.");
ImGui.TextUnformatted("The machine learning model was trained with certain channels in mind.");
ImGui.TextUnformatted(Language.AdvancedWarning1);
ImGui.TextUnformatted(Language.AdvancedWarning2);
ImGui.PopStyleColor();
foreach (var category in MessageCategoryExt.UiOrder) {
@ -189,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)) {
@ -217,91 +192,34 @@ namespace NoSoliciting.Interface {
#endregion
#region Definitions config
private void DrawDefinitionsConfig() {
if (this.Plugin.Config.AdvancedMode) {
this.DrawDefsAdvancedSettings();
} else {
this.DrawDefsBasicSettings();
}
this.DrawDefinitionsTab();
}
private void DrawDefinitionsTab() {
if (!ImGui.BeginTabItem("Definitions")) {
return;
}
if (this.Plugin.Definitions != null) {
ImGui.TextUnformatted($"Version: {this.Plugin.Definitions.Version}");
}
if (Definitions.LastUpdate != null) {
ImGui.TextUnformatted($"Last update: {Definitions.LastUpdate}");
}
var error = Definitions.LastError;
if (error != null) {
ImGui.TextUnformatted($"Last error: {error}");
}
if (ImGui.Button("Update definitions")) {
this.Plugin.UpdateDefinitions();
}
ImGui.EndTabItem();
}
private void DrawDefsBasicSettings() {
if (this.Plugin.Definitions == null) {
return;
}
if (!ImGui.BeginTabItem("Filters")) {
return;
}
this.DrawCheckboxes(this.Plugin.Definitions.Chat.Values, true, "chat");
ImGui.Separator();
this.DrawCheckboxes(this.Plugin.Definitions.PartyFinder.Values, true, "Party Finder");
ImGui.EndTabItem();
}
private void DrawDefsAdvancedSettings() {
if (this.Plugin.Definitions == null) {
return;
}
if (ImGui.BeginTabItem("Chat")) {
this.DrawCheckboxes(this.Plugin.Definitions.Chat.Values, false, "chat");
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Party Finder")) {
this.DrawCheckboxes(this.Plugin.Definitions.PartyFinder.Values, false, "Party Finder");
ImGui.EndTabItem();
}
}
#endregion
#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("Other filters")) {
var flags = this.ShowOtherFilters ? ImGuiTabItemFlags.SetSelected : ImGuiTabItemFlags.None;
this.ShowOtherFilters = false;
if (!BeginTabItem($"{Language.OtherFiltersTab}###other-filters-tab", flags)) {
return;
}
if (ImGui.CollapsingHeader("Chat filters")) {
if (ImGui.CollapsingHeader(Language.ChatFilters)) {
var customChat = this.Plugin.Config.CustomChatFilter;
if (ImGui.Checkbox("Enable custom chat filters", ref customChat)) {
if (ImGui.Checkbox(Language.EnableCustomChatFilters, ref customChat)) {
this.Plugin.Config.CustomChatFilter = customChat;
this.Plugin.Config.Save();
}
@ -313,22 +231,22 @@ namespace NoSoliciting.Interface {
}
}
if (ImGui.CollapsingHeader("Party Finder filters")) {
if (ImGui.CollapsingHeader(Language.PartyFinderFilters)) {
var filterHugeItemLevelPFs = this.Plugin.Config.FilterHugeItemLevelPFs;
// ReSharper disable once InvertIf
if (ImGui.Checkbox("Filter PFs with item level above maximum", ref filterHugeItemLevelPFs)) {
if (ImGui.Checkbox(Language.FilterIlvlPfs, ref filterHugeItemLevelPFs)) {
this.Plugin.Config.FilterHugeItemLevelPFs = filterHugeItemLevelPFs;
this.Plugin.Config.Save();
}
var considerPrivate = this.Plugin.Config.ConsiderPrivatePfs;
if (ImGui.Checkbox("Apply filters to private Party Finder listings", ref considerPrivate)) {
if (ImGui.Checkbox(Language.FilterPrivatePfs, ref considerPrivate)) {
this.Plugin.Config.ConsiderPrivatePfs = considerPrivate;
this.Plugin.Config.Save();
}
var customPf = this.Plugin.Config.CustomPFFilter;
if (ImGui.Checkbox("Enable custom Party Finder filters", ref customPf)) {
if (ImGui.Checkbox(Language.EnableCustomPartyFinderFilters, ref customPf)) {
this.Plugin.Config.CustomPFFilter = customPf;
this.Plugin.Config.Save();
}
@ -346,14 +264,12 @@ namespace NoSoliciting.Interface {
private void DrawCustom(string name, ref List<string> substrings, ref List<string> regexes) {
ImGui.Columns(2);
ImGui.TextUnformatted("Substrings to filter");
ImGui.TextUnformatted(Language.SubstringsToFilter);
if (ImGui.BeginChild($"##{name}-substrings", new Vector2(0, 175))) {
for (var i = 0; i < substrings.Count; i++) {
var input = substrings[i];
if (ImGui.InputText($"##{name}-substring-{i}", ref input, 1_000)) {
if (input.Length != 0) {
substrings[i] = input;
}
substrings[i] = input;
}
ImGui.SameLine();
@ -377,20 +293,17 @@ namespace NoSoliciting.Interface {
ImGui.NextColumn();
ImGui.TextUnformatted("Regular expressions to filter");
ImGui.TextUnformatted(Language.RegularExpressionsToFilter);
if (ImGui.BeginChild($"##{name}-regexes", new Vector2(0, 175))) {
for (var i = 0; i < regexes.Count; i++) {
var input = regexes[i];
if (ImGui.InputText($"##{name}-regex-{i}", ref input, 1_000)) {
var valid = true;
try {
_ = new Regex(input);
} catch (ArgumentException) {
valid = false;
}
if (valid && input.Length != 0) {
// update if valid
regexes[i] = input;
} catch (ArgumentException) {
// ignore
}
}
@ -416,7 +329,8 @@ namespace NoSoliciting.Interface {
ImGui.Columns(1);
// ReSharper disable once InvertIf
if (ImGui.Button($"Save filters##{name}-save")) {
var saveLoc = Language.SaveFilters;
if (ImGui.Button($"{saveLoc}##{name}-save")) {
this.Plugin.Config.Save();
this.Plugin.Config.CompileRegexes();
}
@ -424,21 +338,31 @@ namespace NoSoliciting.Interface {
#endregion
#region Utility
private void DrawCheckboxes(IEnumerable<Definition> defs, bool basic, string labelFillIn) {
foreach (var def in defs) {
this.Plugin.Config.FilterStatus.TryGetValue(def.Id, out var enabled);
var label = basic ? def.Option.Basic : def.Option.Advanced;
label = label.Replace("{}", labelFillIn);
// ReSharper disable once InvertIf
if (ImGui.Checkbox(label, ref enabled)) {
this.Plugin.Config.FilterStatus[def.Id] = enabled;
this.Plugin.Config.Save();
}
private void DrawOtherTab() {
if (!ImGui.BeginTabItem($"{Language.OtherTab}###other-tab")) {
return;
}
}
#endregion
var useGameLanguage = this.Plugin.Config.FollowGameLanguage;
if (ImGui.Checkbox(Language.OtherGameLanguage, ref useGameLanguage)) {
this.Plugin.Config.FollowGameLanguage = useGameLanguage;
this.Plugin.Config.Save();
this.Plugin.ConfigureLanguage();
}
var logFilteredPfs = this.Plugin.Config.LogFilteredPfs;
if (ImGui.Checkbox(Language.LogFilteredPfs, ref logFilteredPfs)) {
this.Plugin.Config.LogFilteredPfs = logFilteredPfs;
this.Plugin.Config.Save();
}
var logFilteredMessages = this.Plugin.Config.LogFilteredChat;
if (ImGui.Checkbox(Language.LogFilteredMessages, ref logFilteredMessages)) {
this.Plugin.Config.LogFilteredChat = logFilteredMessages;
this.Plugin.Config.Save();
}
ImGui.EndTabItem();
}
}
}

View File

@ -3,75 +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; }
public uint DefinitionsVersion { get; }
[JsonIgnore]
public uint ActorId { 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, 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;
}
public Message(uint defsVersion, ChatType type, string sender, string content, bool ml, string? reason) : this(
defsVersion,
type,
new SeString(new Payload[] {new TextPayload(sender)}),
new SeString(new Payload[] {new TextPayload(content)}),
ml,
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 {
@ -83,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(",\"");
@ -192,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";
@ -204,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,9 +1,13 @@
using System;
namespace NoSoliciting.Ml {
[Serializable]
public class Manifest {
public uint Version { get; set; }
public Uri ModelUrl { get; set; }
public string ModelHash { get; set; }
public Uri ReportUrl { get; set; }
public byte[] Hash() => Convert.FromBase64String(this.ModelHash);
}
}

View File

@ -1,14 +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 Resourcer;
using NoSoliciting.Resources;
using YamlDotNet.Core;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
@ -28,53 +26,86 @@ 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;
}
public static async Task<MlFilter?> Load(Plugin plugin, bool showWindow) {
plugin.MlStatus = MlFilterStatus.DownloadingManifest;
// 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
byte[]? data = null;
// load the cached manifest
var localManifest = LoadCachedManifest(plugin);
if (localManifest != null && (manifest?.Item1 == null || localManifest.Version == manifest.Item1.Version)) {
// if there is a cached manifest and we either couldn't download/parse the remote OR the cached version is the same as remote version
if (localManifest != null && (manifest?.Item1 == null || localManifest.Version == manifest.Value.manifest.Version)) {
try {
data = File.ReadAllBytes(CachedFilePath(plugin, ModelName));
manifest ??= Tuple.Create(localManifest, string.Empty);
// try to reach the cached model
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) {
// ignored
}
}
if (!string.IsNullOrEmpty(manifest?.Item2)) {
// if there is source for the manifest
if (!string.IsNullOrEmpty(manifest?.source)) {
plugin.MlStatus = MlFilterStatus.DownloadingModel;
data ??= await DownloadModel(manifest!.Item1!.ModelUrl);
// download the model if necessary
data ??= await DownloadModel(manifest!.Value.manifest!.ModelUrl);
}
// give up if we couldn't get any data at this point
if (data == null) {
plugin.MlStatus = MlFilterStatus.Uninitialised;
return null;
}
// validate checksum
var retries = 0;
const int maxRetries = 3;
var correctHash = manifest!.Value.manifest!.Hash();
using (var sha = SHA256.Create()) {
var hash = sha.ComputeHash(data);
while (!hash.SequenceEqual(correctHash) && retries < maxRetries) {
Plugin.Log.Warning($"Model checksum did not match. Redownloading (attempt {retries + 1}/{maxRetries})");
retries += 1;
data = await DownloadModel(manifest!.Value.manifest!.ModelUrl);
if (data != null) {
hash = sha.ComputeHash(data);
}
}
}
// give up if we couldn't get any data at this point
if (data == null) {
plugin.MlStatus = MlFilterStatus.Uninitialised;
return null;
@ -82,112 +113,67 @@ namespace NoSoliciting.Ml {
plugin.MlStatus = MlFilterStatus.Initialising;
if (!string.IsNullOrEmpty(manifest!.Item2)) {
// if there is source for the manifest
if (!string.IsNullOrEmpty(manifest!.Value.source)) {
// update the cached files
UpdateCachedFile(plugin, ModelName, data);
UpdateCachedFile(plugin, ManifestName, Encoding.UTF8.GetBytes(manifest.Item2));
UpdateCachedFile(plugin, ManifestName, Encoding.UTF8.GetBytes(manifest.Value.source));
}
var pluginFolder = Util.PluginFolder(plugin);
var exePath = await ExtractClassifier(pluginFolder);
var pipeId = Guid.NewGuid();
var process = StartClassifier(exePath, pipeId, showWindow);
var client = await CreateClassifierClient(pipeId, data);
// initialise the classifier
var classifier = new Classifier();
classifier.Initialise(data);
return new MlFilter(
manifest.Item1!.Version,
manifest.Item1!.ReportUrl,
process!,
client
manifest.Value.manifest!.Version,
manifest.Value.manifest!.ReportUrl,
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;
}
}
private static string CachedFilePath(IDalamudPlugin plugin, string name) {
var pluginFolder = Util.PluginFolder(plugin);
private static string CachedFilePath(Plugin plugin, string name) {
var pluginFolder = plugin.Interface.ConfigDirectory.ToString();
Directory.CreateDirectory(pluginFolder);
return Path.Combine(pluginFolder, name);
}
private static async void UpdateCachedFile(IDalamudPlugin plugin, string name, byte[] data) {
private static async void UpdateCachedFile(Plugin plugin, string name, byte[] data) {
var cachePath = CachedFilePath(plugin, name);
var file = File.OpenWrite(cachePath);
var file = File.Create(cachePath);
await file.WriteAsync(data, 0, data.Length);
await file.FlushAsync();
file.Dispose();
await file.DisposeAsync();
}
private static async Task<Tuple<Manifest, string>?> DownloadManifest() {
private static async Task<(Manifest manifest, string source)?> DownloadManifest() {
try {
using var client = new WebClient();
var data = await client.DownloadStringTaskAsync(Url);
LastError = null;
return Tuple.Create(LoadYaml<Manifest>(data), data);
} catch (Exception e) when (e is WebException || e is YamlException) {
PluginLog.LogError("Could not download newest model manifest.");
PluginLog.LogError(e.ToString());
return (LoadYaml<Manifest>(data), data);
} catch (Exception e) when (e is WebException or YamlException) {
Plugin.Log.Error("Could not download newest model manifest.");
Plugin.Log.Error(e.ToString());
LastError = e.Message;
return null;
}
}
private static Manifest? LoadCachedManifest(IDalamudPlugin plugin) {
private static Manifest? LoadCachedManifest(Plugin plugin) {
var manifestPath = CachedFilePath(plugin, ManifestName);
if (!File.Exists(manifestPath)) {
return null;
@ -216,12 +202,7 @@ namespace NoSoliciting.Ml {
}
public void Dispose() {
try {
this.Process.Kill();
this.Process.Dispose();
} catch (Exception) {
// ignored
}
this.Classifier.Dispose();
}
}
@ -237,12 +218,12 @@ namespace NoSoliciting.Ml {
public static class MlFilterStatusExt {
public static string Description(this MlFilterStatus status) {
return status switch {
MlFilterStatus.Uninitialised => "Uninitialised",
MlFilterStatus.Preparing => "Preparing to update model",
MlFilterStatus.DownloadingManifest => "Downloading model manifest",
MlFilterStatus.DownloadingModel => "Downloading model",
MlFilterStatus.Initialising => "Initialising model and classifier",
MlFilterStatus.Initialised => "Initialised",
MlFilterStatus.Uninitialised => Language.ModelStatusUninitialised,
MlFilterStatus.Preparing => Language.ModelStatusPreparing,
MlFilterStatus.DownloadingManifest => Language.ModelStatusDownloadingManifest,
MlFilterStatus.DownloadingModel => Language.ModelStatusDownloadingModel,
MlFilterStatus.Initialising => Language.ModelStatusInitialising,
MlFilterStatus.Initialised => Language.ModelStatusInitialised,
_ => status.ToString(),
};
}

View File

@ -1,4 +1,5 @@
using System;
using NoSoliciting.Resources;
namespace NoSoliciting.Ml {
public enum MessageCategory {
@ -12,6 +13,7 @@ namespace NoSoliciting.Ml {
Static,
Community,
StaticSub,
Fluff,
}
public static class MessageCategoryExt {
@ -25,6 +27,7 @@ namespace NoSoliciting.Ml {
MessageCategory.Static,
MessageCategory.StaticSub,
MessageCategory.Community,
MessageCategory.Fluff,
};
public static MessageCategory? FromString(string? category) => category switch {
@ -38,10 +41,26 @@ namespace NoSoliciting.Ml {
"STATIC" => MessageCategory.Static,
"COMMUNITY" => MessageCategory.Community,
"STATIC_SUB" => MessageCategory.StaticSub,
"FLUFF" => MessageCategory.Fluff,
_ => null,
};
#if DEBUG
public static MessageCategory? FromName(string? category) => category switch {
"Trade ads" => MessageCategory.Trade,
"Free Company ads" => MessageCategory.FreeCompany,
"Normal messages" => MessageCategory.Normal,
"Phishing messages" => MessageCategory.Phishing,
"RMT (content)" => MessageCategory.RmtContent,
"RMT (gil)" => MessageCategory.RmtGil,
"Roleplaying ads" => MessageCategory.Roleplaying,
"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",
@ -54,50 +73,37 @@ namespace NoSoliciting.Ml {
MessageCategory.Static => "STATIC",
MessageCategory.Community => "COMMUNITY",
MessageCategory.StaticSub => "STATIC_SUB",
MessageCategory.Fluff => "FLUFF",
_ => throw new ArgumentException("Invalid category", nameof(category)),
};
public static MessageCategory? FromName(string? category) => category switch {
"Trade ads" => MessageCategory.Trade,
"Free Company ads" => MessageCategory.FreeCompany,
"Normal messages" => MessageCategory.Normal,
"Phishing messages" => MessageCategory.Phishing,
"RMT (content)" => MessageCategory.RmtContent,
"RMT (gil)" => MessageCategory.RmtGil,
"Roleplaying ads" => MessageCategory.Roleplaying,
"Static recruitment" => MessageCategory.Static,
"Community ads" => MessageCategory.Community,
"Static substitutes" => MessageCategory.StaticSub,
_ => null,
};
#endif
public static string Name(this MessageCategory category) => category switch {
MessageCategory.Trade => "Trade ads",
MessageCategory.FreeCompany => "Free Company ads",
MessageCategory.Normal => "Normal messages",
MessageCategory.Phishing => "Phishing messages",
MessageCategory.RmtContent => "RMT (content)",
MessageCategory.RmtGil => "RMT (gil)",
MessageCategory.Roleplaying => "Roleplaying ads",
MessageCategory.Static => "Static recruitment",
MessageCategory.Community => "Community ads",
MessageCategory.StaticSub => "Static substitutes",
MessageCategory.Trade => Language.TradeCategory,
MessageCategory.FreeCompany => Language.FreeCompanyCategory,
MessageCategory.Normal => Language.NormalCategory,
MessageCategory.Phishing => Language.PhishingCategory,
MessageCategory.RmtContent => Language.RmtContentCategory,
MessageCategory.RmtGil => Language.RmtGilCategory,
MessageCategory.Roleplaying => Language.RoleplayingCategory,
MessageCategory.Static => Language.StaticCategory,
MessageCategory.Community => Language.CommunityCategory,
MessageCategory.StaticSub => Language.StaticSubCategory,
MessageCategory.Fluff => Language.FluffCategory,
_ => throw new ArgumentException("Invalid category", nameof(category)),
};
public static string Description(this MessageCategory category) => category switch {
MessageCategory.Trade => "Messages advertising trading items or services for gil, such as omnicrafters looking for work or people selling rare items off the market board",
MessageCategory.FreeCompany => "Advertisements for Free Companies",
MessageCategory.Normal => "Normal messages that should not be filtered",
MessageCategory.Phishing => "Messages trying to trick you into revealing your account details in order to steal your account",
MessageCategory.RmtContent => "Real-money trade involving content (also known as content sellers)",
MessageCategory.RmtGil => "Real-money trade involving gil or items (also known as RMT bots)",
MessageCategory.Roleplaying => "Advertisements for personal RP, RP communities, venues, or anything else related to roleplaying",
MessageCategory.Static => "Statics looking for members or players looking for a static",
MessageCategory.Community => "Advertisements for general-purpose communities, generally Discord servers",
MessageCategory.StaticSub => "Statics looking for fill-ins of missing members for clears",
MessageCategory.Trade => Language.TradeDescription,
MessageCategory.FreeCompany => Language.FreeCompanyDescription,
MessageCategory.Normal => Language.NormalDescription,
MessageCategory.Phishing => Language.PhishingDescription,
MessageCategory.RmtContent => Language.RmtContentDescription,
MessageCategory.RmtGil => Language.RmtGilDescription,
MessageCategory.Roleplaying => Language.RoleplayingDescription,
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,64 +3,72 @@
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<Version>1.8.1</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, Version=5.2.4.2, Culture=neutral, PublicKeyToken=null">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
<Reference Include="Dalamud">
<HintPath>$(DalamudLibPath)\Dalamud.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ImGui.NET, Version=1.72.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\ImGui.NET.dll</HintPath>
<Reference Include="ImGui.NET">
<HintPath>$(DalamudLibPath)\ImGui.NET.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ImGuiScene, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\ImGuiScene.dll</HintPath>
<Reference Include="Lumina">
<HintPath>$(DalamudLibPath)\Lumina.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Lumina, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.dll</HintPath>
<Reference Include="Lumina.Excel">
<HintPath>$(DalamudLibPath)\Lumina.Excel.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Lumina.Excel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Newtonsoft.Json.dll</HintPath>
<Reference Include="Newtonsoft.Json">
<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</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<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="YamlDotNet" Version="11.0.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>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<EmbeddedResource Update="Resources\Language.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<LastGenOutput>Language.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="..\NoSoliciting.MessageClassifier\bin\Release\net48\NoSoliciting.MessageClassifier.exe"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NoSoliciting.Interface\NoSoliciting.Interface.csproj"/>
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\Language.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>NoSoliciting.resx</DependentUpon>
</Compile>
</ItemGroup>
</Project>

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

@ -1,62 +1,99 @@
using Dalamud.Plugin;
using System;
using System.Collections.Generic;
using System.Globalization;
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 PluginUi Ui { get; private set; } = null!;
public Commands Commands { get; private set; } = null!;
public Definitions? Definitions { get; private set; }
public MlFilterStatus MlStatus { get; set; } = MlFilterStatus.Uninitialised;
public MlFilter? MlFilter { get; set; }
public bool MlReady => this.Config.UseMachineLearning && this.MlFilter != null;
public bool DefsReady => !this.Config.UseMachineLearning && this.Definitions != null;
[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 ?? throw new ArgumentNullException(nameof(pluginInterface), "DalamudPluginInterface cannot be null");
this.Ui = new PluginUi(this);
this.Commands = new Commands(this);
this.Config = this.Interface.GetPluginConfig() as PluginConfiguration ?? new PluginConfiguration();
this.Config.Initialise(this.Interface);
this.UpdateDefinitions();
this.ConfigureLanguage();
this.Interface.LanguageChanged += this.OnLanguageUpdate;
this.Common = new XivCommonBase(this.Interface, Hooks.PartyFinderListings);
this.DalamudContextMenu = new DalamudContextMenu(this.Interface);
this.Ui = new PluginUi(this);
this.Commands = new Commands(this);
this.ContextMenu = new ContextMenu(this);
this.Filter = new Filter(this);
if (this.Config.UseMachineLearning) {
this.InitialiseMachineLearning(false);
}
this.InitialiseMachineLearning(false);
// pre-compute the max ilvl to prevent stutter
FilterUtil.MaxItemLevelAttainable(this.Interface.Data);
try {
FilterUtil.MaxItemLevelAttainable(this.DataManager);
} catch (Exception ex) {
Plugin.Log.Error(ex, "Exception while computing max item level");
}
}
protected virtual void Dispose(bool disposing) {
@ -67,13 +104,38 @@ namespace NoSoliciting {
if (disposing) {
this.Filter.Dispose();
this.MlFilter?.Dispose();
this.ContextMenu.Dispose();
this.Commands.Dispose();
this.Ui.Dispose();
this.DalamudContextMenu.Dispose();
this.Common.Dispose();
this.Interface.LanguageChanged -= this.OnLanguageUpdate;
}
this._disposedValue = true;
}
private void OnLanguageUpdate(string langCode) {
this.ConfigureLanguage(langCode);
}
internal void ConfigureLanguage(string? langCode = null) {
if (this.Config.FollowGameLanguage) {
langCode = this.ClientState.ClientLanguage switch {
ClientLanguage.Japanese => "ja",
ClientLanguage.English => "en",
ClientLanguage.German => "de",
ClientLanguage.French => "fr",
_ => throw new ArgumentOutOfRangeException(nameof(this.ClientState.ClientLanguage), "Unknown ClientLanguage"),
};
}
langCode ??= this.Interface.UiLanguage;
// 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) {
if (this.MlFilter != null) {
return;
@ -87,22 +149,10 @@ namespace NoSoliciting {
}
this.MlStatus = MlFilterStatus.Initialised;
PluginLog.Log("Machine learning model loaded");
Log.Info("Machine learning model loaded");
});
}
internal void UpdateDefinitions() {
Task.Run(async () => {
var defs = await Definitions.UpdateAndCache(this).ConfigureAwait(true);
// this shouldn't be possible, but what do I know
if (defs != null) {
defs.Initialise(this);
this.Definitions = defs;
Definitions.LastUpdate = DateTime.Now;
}
});
}
public void AddMessageHistory(Message message) {
this._messageHistory.Insert(0, message);

View File

@ -14,24 +14,7 @@ namespace NoSoliciting {
private DalamudPluginInterface Interface { get; set; } = null!;
public int Version { get; set; } = 1;
[Obsolete("Use FilterStatus")]
public bool FilterChat { get; set; } = true;
[Obsolete("Use FilterStatus")]
public bool FilterFCRecruitments { get; set; } = false;
[Obsolete("Use FilterStatus")]
public bool FilterChatRPAds { get; set; } = false;
[Obsolete("Use FilterStatus")]
public bool FilterPartyFinder { get; set; } = true;
[Obsolete("Use FilterStatus")]
public bool FilterPartyFinderRPAds { get; set; } = false;
public Dictionary<string, bool> FilterStatus { get; private set; } = new();
public int Version { get; set; } = 2;
public bool AdvancedMode { get; set; }
@ -47,11 +30,11 @@ namespace NoSoliciting {
public List<string> PFRegexes { get; } = new();
[JsonIgnore]
public List<Regex> CompiledPFRegexes { get; private set; } = new();
public List<Regex> CompiledPfRegexes { get; private set; } = new();
public bool FilterHugeItemLevelPFs { get; set; }
public bool UseMachineLearning { get; set; } = true;
public bool FollowGameLanguage { get; set; }
public HashSet<MessageCategory> BasicMlFilters { get; set; } = new() {
MessageCategory.RmtGil,
@ -61,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> {
@ -88,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,
},
};
@ -99,6 +96,9 @@ namespace NoSoliciting {
public bool ConsiderPrivatePfs { get; set; }
public IEnumerable<string> ValidChatSubstrings => this.ChatSubstrings.Where(needle => !string.IsNullOrWhiteSpace(needle));
public IEnumerable<string> ValidPfSubstrings => this.PFSubstrings.Where(needle => !string.IsNullOrWhiteSpace(needle));
public void Initialise(DalamudPluginInterface pi) {
this.Interface = pi;
this.CompileRegexes();
@ -110,15 +110,17 @@ namespace NoSoliciting {
public void CompileRegexes() {
this.CompiledChatRegexes = this.ChatRegexes
.Where(reg => !string.IsNullOrWhiteSpace(reg))
.Select(reg => new Regex(reg, RegexOptions.Compiled))
.ToList();
this.CompiledPFRegexes = this.PFRegexes
this.CompiledPfRegexes = this.PFRegexes
.Where(reg => !string.IsNullOrWhiteSpace(reg))
.Select(reg => new Regex(reg, RegexOptions.Compiled))
.ToList();
}
internal bool MlEnabledOn(MessageCategory category, ChatType chatType) {
HashSet<ChatType> filtered;
HashSet<ChatType>? filtered;
if (this.AdvancedMode) {
if (!this.MlFilters.TryGetValue(category, out filtered)) {
@ -138,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

@ -1,84 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace NoSoliciting.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NoSoliciting.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to # This file defines the filters that NoSoliciting will use for
///# built-in filters.
///
///# The version should be incremented for each commit including changes
///# to this file.
///
///# There are three main sections: chat, party_finder, and global. The
///# chat and party_finder sections are for their respective areas (the
///# chat log and the Party Finder window), and the global section
///# applies to both.
///
///# Each subsection is a separate built-in filter that can be toggled on
///# and off. The option shown in the UI is defined [rest of string was truncated]&quot;;.
/// </summary>
internal static string DefaultDefinitions {
get {
return ResourceManager.GetString("DefaultDefinitions", resourceCulture);
}
}
}
}

View File

@ -1,124 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<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>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="DefaultDefinitions" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\definitions.yaml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
</data>
</root>

855
NoSoliciting/Resources/Language.Designer.cs generated Executable file
View File

@ -0,0 +1,855 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace NoSoliciting.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Language {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Language() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NoSoliciting.Resources.Language", typeof(Language).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Advanced mode.
/// </summary>
internal static string AdvancedMode {
get {
return ResourceManager.GetString("AdvancedMode", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Do not change advanced settings unless you know what you are doing..
/// </summary>
internal static string AdvancedWarning1 {
get {
return ResourceManager.GetString("AdvancedWarning1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The machine learning model was trained with certain channels in mind..
/// </summary>
internal static string AdvancedWarning2 {
get {
return ResourceManager.GetString("AdvancedWarning2", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Chat filters.
/// </summary>
internal static string ChatFilters {
get {
return ResourceManager.GetString("ChatFilters", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Community ads.
/// </summary>
internal static string CommunityCategory {
get {
return ResourceManager.GetString("CommunityCategory", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Advertisements for general-purpose communities, generally Discord servers.
/// </summary>
internal static string CommunityDescription {
get {
return ResourceManager.GetString("CommunityDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Enable custom chat filters.
/// </summary>
internal static string EnableCustomChatFilters {
get {
return ResourceManager.GetString("EnableCustomChatFilters", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Enable custom Party Finder filters.
/// </summary>
internal static string EnableCustomPartyFinderFilters {
get {
return ResourceManager.GetString("EnableCustomPartyFinderFilters", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Filter PFs with item level above maximum.
/// </summary>
internal static string FilterIlvlPfs {
get {
return ResourceManager.GetString("FilterIlvlPfs", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Apply filters to private Party Finder listings.
/// </summary>
internal static string FilterPrivatePfs {
get {
return ResourceManager.GetString("FilterPrivatePfs", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Filters.
/// </summary>
internal static string FiltersTab {
get {
return ResourceManager.GetString("FiltersTab", resourceCulture);
}
}
/// <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>
internal static string FreeCompanyCategory {
get {
return ResourceManager.GetString("FreeCompanyCategory", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Advertisements for Free Companies.
/// </summary>
internal static string FreeCompanyDescription {
get {
return ResourceManager.GetString("FreeCompanyDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Log filtered messages.
/// </summary>
internal static string LogFilteredMessages {
get {
return ResourceManager.GetString("LogFilteredMessages", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Log filtered PFs.
/// </summary>
internal static string LogFilteredPfs {
get {
return ResourceManager.GetString("LogFilteredPfs", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Downloading model manifest.
/// </summary>
internal static string ModelStatusDownloadingManifest {
get {
return ResourceManager.GetString("ModelStatusDownloadingManifest", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Downloading model.
/// </summary>
internal static string ModelStatusDownloadingModel {
get {
return ResourceManager.GetString("ModelStatusDownloadingModel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Initialised.
/// </summary>
internal static string ModelStatusInitialised {
get {
return ResourceManager.GetString("ModelStatusInitialised", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Initialising model and classifier.
/// </summary>
internal static string ModelStatusInitialising {
get {
return ResourceManager.GetString("ModelStatusInitialising", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Preparing to update model.
/// </summary>
internal static string ModelStatusPreparing {
get {
return ResourceManager.GetString("ModelStatusPreparing", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Uninitialised.
/// </summary>
internal static string ModelStatusUninitialised {
get {
return ResourceManager.GetString("ModelStatusUninitialised", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Model.
/// </summary>
internal static string ModelTab {
get {
return ResourceManager.GetString("ModelTab", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Last error: {0}.
/// </summary>
internal static string ModelTabError {
get {
return ResourceManager.GetString("ModelTabError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Model status: {0}.
/// </summary>
internal static string ModelTabStatus {
get {
return ResourceManager.GetString("ModelTabStatus", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Version: {0}.
/// </summary>
internal static string ModelTabVersion {
get {
return ResourceManager.GetString("ModelTabVersion", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Normal messages.
/// </summary>
internal static string NormalCategory {
get {
return ResourceManager.GetString("NormalCategory", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Normal messages that should not be filtered.
/// </summary>
internal static string NormalDescription {
get {
return ResourceManager.GetString("NormalDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Other filters.
/// </summary>
internal static string OtherFiltersTab {
get {
return ResourceManager.GetString("OtherFiltersTab", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use the game&apos;s language setting instead of Dalamud&apos;s.
/// </summary>
internal static string OtherGameLanguage {
get {
return ResourceManager.GetString("OtherGameLanguage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Other.
/// </summary>
internal static string OtherTab {
get {
return ResourceManager.GetString("OtherTab", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Party Finder filters.
/// </summary>
internal static string PartyFinderFilters {
get {
return ResourceManager.GetString("PartyFinderFilters", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Phishing messages.
/// </summary>
internal static string PhishingCategory {
get {
return ResourceManager.GetString("PhishingCategory", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Messages trying to trick you into revealing your account details in order to steal your account.
/// </summary>
internal static string PhishingDescription {
get {
return ResourceManager.GetString("PhishingDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Regular expressions to filter.
/// </summary>
internal static string RegularExpressionsToFilter {
get {
return ResourceManager.GetString("RegularExpressionsToFilter", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Chat.
/// </summary>
internal static string ReportChatTab {
get {
return ResourceManager.GetString("ReportChatTab", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Channel.
/// </summary>
internal static string ReportColumnChannel {
get {
return ResourceManager.GetString("ReportColumnChannel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Description.
/// </summary>
internal static string ReportColumnDescription {
get {
return ResourceManager.GetString("ReportColumnDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Host.
/// </summary>
internal static string ReportColumnHost {
get {
return ResourceManager.GetString("ReportColumnHost", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Message.
/// </summary>
internal static string ReportColumnMessage {
get {
return ResourceManager.GetString("ReportColumnMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reason.
/// </summary>
internal static string ReportColumnReason {
get {
return ResourceManager.GetString("ReportColumnReason", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Sender.
/// </summary>
internal static string ReportColumnSender {
get {
return ResourceManager.GetString("ReportColumnSender", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Timestamp.
/// </summary>
internal static string ReportColumnTimestamp {
get {
return ResourceManager.GetString("ReportColumnTimestamp", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Click on one of the entries below to report it to the developer as miscategorised..
/// </summary>
internal static string ReportHelp {
get {
return ResourceManager.GetString("ReportHelp", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} reporting.
/// </summary>
internal static string Reporting {
get {
return ResourceManager.GetString("Reporting", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cancel.
/// </summary>
internal static string ReportModalCancel {
get {
return ResourceManager.GetString("ReportModalCancel", resourceCulture);
}
}
/// <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>
internal static string ReportModalCopy {
get {
return ResourceManager.GetString("ReportModalCopy", resourceCulture);
}
}
/// <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 ReportModalDisabledCustom {
get {
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);
}
}
/// <summary>
/// Looks up a localized string similar to Reporting this message will let the developer know that you think this message was incorrectly classified..
/// </summary>
internal static string ReportModalHelp1 {
get {
return ResourceManager.GetString("ReportModalHelp1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to NoSoliciting only works for English messages. Do not report non-English messages..
/// </summary>
internal static string ReportModalHelp2 {
get {
return ResourceManager.GetString("ReportModalHelp2", resourceCulture);
}
}
/// <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>
internal static string ReportModalReport {
get {
return ResourceManager.GetString("ReportModalReport", resourceCulture);
}
}
/// <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>
internal static string ReportModalTitle {
get {
return ResourceManager.GetString("ReportModalTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Party Finder.
/// </summary>
internal static string ReportPartyFinderTab {
get {
return ResourceManager.GetString("ReportPartyFinderTab", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to failed to send.
/// </summary>
internal static string ReportStatusFailure {
get {
return ResourceManager.GetString("ReportStatusFailure", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to sending.
/// </summary>
internal static string ReportStatusInProgress {
get {
return ResourceManager.GetString("ReportStatusInProgress", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Last report status: {0}.
/// </summary>
internal static string ReportStatusMessage {
get {
return ResourceManager.GetString("ReportStatusMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to sent successfully.
/// </summary>
internal static string ReportStatusSuccessful {
get {
return ResourceManager.GetString("ReportStatusSuccessful", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to unknown.
/// </summary>
internal static string ReportStatusUnknown {
get {
return ResourceManager.GetString("ReportStatusUnknown", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Failed to report Party Finder listing hosted by {0}..
/// </summary>
internal static string ReportToastFailure {
get {
return ResourceManager.GetString("ReportToastFailure", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Party Finder listing hosted by {0} reported successfully..
/// </summary>
internal static string ReportToastSuccess {
get {
return ResourceManager.GetString("ReportToastSuccess", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Report to NoSoliciting.
/// </summary>
internal static string ReportToNoSoliciting {
get {
return ResourceManager.GetString("ReportToNoSoliciting", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to RMT (content).
/// </summary>
internal static string RmtContentCategory {
get {
return ResourceManager.GetString("RmtContentCategory", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Real-money trade involving content (also known as content sellers).
/// </summary>
internal static string RmtContentDescription {
get {
return ResourceManager.GetString("RmtContentDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to RMT (gil).
/// </summary>
internal static string RmtGilCategory {
get {
return ResourceManager.GetString("RmtGilCategory", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Real-money trade involving gil or items (also known as RMT bots).
/// </summary>
internal static string RmtGilDescription {
get {
return ResourceManager.GetString("RmtGilDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Roleplaying ads.
/// </summary>
internal static string RoleplayingCategory {
get {
return ResourceManager.GetString("RoleplayingCategory", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Advertisements for personal RP, RP communities, venues, or anything else related to roleplaying.
/// </summary>
internal static string RoleplayingDescription {
get {
return ResourceManager.GetString("RoleplayingDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Save filters.
/// </summary>
internal static string SaveFilters {
get {
return ResourceManager.GetString("SaveFilters", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} settings.
/// </summary>
internal static string Settings {
get {
return ResourceManager.GetString("Settings", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Show reporting window.
/// </summary>
internal static string ShowReportingWindow {
get {
return ResourceManager.GetString("ShowReportingWindow", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Static recruitment.
/// </summary>
internal static string StaticCategory {
get {
return ResourceManager.GetString("StaticCategory", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Statics looking for members or players looking for a static.
/// </summary>
internal static string StaticDescription {
get {
return ResourceManager.GetString("StaticDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Static substitutes.
/// </summary>
internal static string StaticSubCategory {
get {
return ResourceManager.GetString("StaticSubCategory", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Statics looking for fill-ins of missing members for clears.
/// </summary>
internal static string StaticSubDescription {
get {
return ResourceManager.GetString("StaticSubDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Substrings to filter.
/// </summary>
internal static string SubstringsToFilter {
get {
return ResourceManager.GetString("SubstringsToFilter", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Trade ads.
/// </summary>
internal static string TradeCategory {
get {
return ResourceManager.GetString("TradeCategory", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Messages advertising trading items or services for gil, such as omnicrafters looking for work or people selling rare items off the market board.
/// </summary>
internal static string TradeDescription {
get {
return ResourceManager.GetString("TradeDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Update model.
/// </summary>
internal static string UpdateModel {
get {
return ResourceManager.GetString("UpdateModel", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,264 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ReportToNoSoliciting" xml:space="preserve">
<value>An NoSoliciting melden</value>
</data>
<data name="OtherTab" xml:space="preserve">
<value>Anderes</value>
</data>
<data name="OtherFiltersTab" xml:space="preserve">
<value>Andere Filter</value>
</data>
<data name="ChatFilters" xml:space="preserve">
<value>Chat Filter</value>
</data>
<data name="PartyFinderFilters" xml:space="preserve">
<value>Gruppensuche Filter</value>
</data>
<data name="FilterIlvlPfs" xml:space="preserve">
<value>Filtere Gruppensuchen, dessen Gegenstandsstufe über dem Maximum liegt</value>
</data>
<data name="Settings" xml:space="preserve">
<value>Einstellungen</value>
</data>
<data name="EnableCustomChatFilters" xml:space="preserve">
<value>Benutzerdefinierte Chat Filter aktivieren</value>
</data>
<data name="AdvancedMode" xml:space="preserve">
<value>Erweiterte Einstellungen</value>
</data>
<data name="ModelTabError" xml:space="preserve">
<value>Letzter Fehler: {0}</value>
</data>
<data name="ModelTabVersion" xml:space="preserve">
<value>Version: {0}</value>
</data>
<data name="FiltersTab" xml:space="preserve">
<value>Filter</value>
</data>
<data name="FilterPrivatePfs" xml:space="preserve">
<value>Filter auf private Gruppensuchen anwenden</value>
</data>
<data name="EnableCustomPartyFinderFilters" xml:space="preserve">
<value>Benutzerdefinierte Gruppensuche Filter aktivieren</value>
</data>
<data name="SubstringsToFilter" xml:space="preserve">
<value>zu filternde Zeichenketten</value>
</data>
<data name="UpdateModel" xml:space="preserve">
<value>Modell aktualisieren</value>
</data>
<data name="ModelTab" xml:space="preserve">
<value>Modell</value>
</data>
<data name="AdvancedWarning2" xml:space="preserve">
<value>Das Machine Learning Modell wurde in Hinblick auf spezielle Kanäle trainiert.</value>
</data>
<data name="LogFilteredPfs" xml:space="preserve">
<value>Gefilterte Gruppensuchen protokollieren</value>
</data>
<data name="ReportStatusSuccessful" xml:space="preserve">
<value>erfolgreich gesendet</value>
</data>
<data name="ReportStatusInProgress" xml:space="preserve">
<value>sende</value>
</data>
<data name="ReportStatusUnknown" xml:space="preserve">
<value>unbekannt</value>
</data>
<data name="ReportChatTab" xml:space="preserve">
<value>Chat</value>
</data>
<data name="ReportColumnTimestamp" xml:space="preserve">
<value>Zeitstempel</value>
</data>
<data name="SaveFilters" xml:space="preserve">
<value>Filter speichern</value>
</data>
<data name="ReportColumnReason" xml:space="preserve">
<value>Grund</value>
</data>
<data name="ReportColumnMessage" xml:space="preserve">
<value>Nachricht</value>
</data>
<data name="ReportPartyFinderTab" xml:space="preserve">
<value>Gruppensuche</value>
</data>
<data name="ReportStatusMessage" xml:space="preserve">
<value>Status der letzten Meldung: {0}</value>
</data>
<data name="ReportColumnDescription" xml:space="preserve">
<value>Beschreibung</value>
</data>
<data name="ReportModalTitle" xml:space="preserve">
<value>An {0} melden</value>
</data>
<data name="ReportModalCopy" xml:space="preserve">
<value>In die Zwischenablage kopieren</value>
</data>
<data name="FreeCompanyCategory" xml:space="preserve">
<value>Werbung für Freie Gesellschaften</value>
</data>
<data name="ReportModalCancel" xml:space="preserve">
<value>Abbrechen</value>
</data>
<data name="RmtContentCategory" xml:space="preserve">
<value>RMT (Inhalte)</value>
</data>
<data name="RmtGilCategory" xml:space="preserve">
<value>RMT (Gil)</value>
</data>
<data name="ReportStatusFailure" xml:space="preserve">
<value>senden fehlgeschlagen</value>
</data>
<data name="ReportColumnChannel" xml:space="preserve">
<value>Kanal</value>
</data>
<data name="ReportColumnSender" xml:space="preserve">
<value>Absender</value>
</data>
<data name="ReportModalReport" xml:space="preserve">
<value>Melden</value>
</data>
<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">
<value>Phishing Nachrichten</value>
</data>
<data name="NormalCategory" xml:space="preserve">
<value>Normale Nachrichten</value>
</data>
<data name="ReportModalHelp2" xml:space="preserve">
<value>NoSoliciting funktioniert nur für englische Nachrichten, bitte ausschließlich englische Nachrichten melden.</value>
</data>
<data name="RoleplayingCategory" xml:space="preserve">
<value>Werbung für Rollenspiel</value>
</data>
<data name="FreeCompanyDescription" xml:space="preserve">
<value>Werbung für Freie Gesellschaften</value>
</data>
<data name="OtherGameLanguage" xml:space="preserve">
<value>Nutze die Spracheinstellung des Spiels, anstelle der von Dalamud</value>
</data>
<data name="ReportToastFailure" xml:space="preserve">
<value>Fehler beim Melden der Gruppensuche von {0}.</value>
</data>
<data name="ReportToastSuccess" xml:space="preserve">
<value>Gruppensuche von {0] erfolgreich gemeldet.</value>
</data>
<data name="CommunityDescription" xml:space="preserve">
<value>Werbung für alle möglichen Gemeinschaften, normalerweise Discord Server</value>
</data>
<data name="TradeDescription" xml:space="preserve">
<value>Werbung für Tauschgeschäfte oder Dienste für Gil, Omnicrafter, die nach Arbeit suchen oder Leute, die seltene Items abseits des Marktbretts verkaufen</value>
</data>
<data name="PhishingDescription" xml:space="preserve">
<value>Nachrichten, die dich dazu bringen sollen deine Account-Daten preiszugeben, um deinen Account zu stehlen</value>
</data>
<data name="StaticDescription" xml:space="preserve">
<value>Statics, die nach neuen Mitgliedern suchen oder Spieler, die nach einer Static suchen</value>
</data>
<data name="StaticCategory" xml:space="preserve">
<value>Static Rekrutierung</value>
</data>
<data name="StaticSubDescription" xml:space="preserve">
<value>Statics, die wegen abwesender Mitglieder nach Aushilfen suchen</value>
</data>
<data name="StaticSubCategory" xml:space="preserve">
<value>Static Aushilfen</value>
</data>
<data name="NormalDescription" xml:space="preserve">
<value>Normale Nachrichten, die nicht gefiltert werden sollten</value>
</data>
<data name="AdvancedWarning1" xml:space="preserve">
<value>Ändere diese Einstellungen nur, wenn du weißt, was du tust.</value>
</data>
<data name="RegularExpressionsToFilter" xml:space="preserve">
<value>zu filternde reguläre Ausdrücke</value>
</data>
<data name="ReportHelp" xml:space="preserve">
<value>Klicke auf einen der unten stehenden Einträge, um ihn dem Entwickler als falsch kategorisiert zu melden.</value>
</data>
<data name="ReportModalHelp1" xml:space="preserve">
<value>Diese Nachricht zu melden, lässt den Entwickler wissen, dass du denkst, das diese Nachricht falsch kategorisiert wurde.</value>
</data>
<data name="RoleplayingDescription" xml:space="preserve">
<value>Werbung für persönliches Rollenspiel, Rollenspiel Gemeinschaften, Veranstaltungen oder anderes in Bezug auf Rollenspiel</value>
</data>
<data name="ModelTabStatus" xml:space="preserve">
<value>Status des Modells: {0}</value>
</data>
<data name="LogFilteredMessages" xml:space="preserve">
<value>Gefilterte Nachrichten protokollieren</value>
</data>
<data name="ShowReportingWindow" xml:space="preserve">
<value>Berichtsfenster öffnen</value>
</data>
<data name="ReportColumnHost" xml:space="preserve">
<value>Suchender</value>
</data>
<data name="Reporting" xml:space="preserve">
<value>{0} Bericht</value>
</data>
<data name="RmtContentDescription" xml:space="preserve">
<value>Echtgeldhandel, der den Abschluss von Inhalten beinhaltet (auch bekannt als Content Seller)</value>
</data>
<data name="RmtGilDescription" xml:space="preserve">
<value>Echtgeldhandel, der Gil oder Gegenstände beinhaltet (auch bekannt als RMT Bots)</value>
</data>
<data name="CommunityCategory" xml:space="preserve">
<value>Gemeinschaftswerbung</value>
</data>
<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

@ -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="LogFilteredPfs" xml:space="preserve">
<value>Registrar PFs filtrados</value>
</data>
<data name="LogFilteredMessages" xml:space="preserve">
<value>Registrar mensajes filtrados</value>
</data>
<data name="Settings" xml:space="preserve">
<value>Ajustes de {0}</value>
</data>
<data name="AdvancedMode" xml:space="preserve">
<value>Modo avanzado</value>
</data>
<data name="ModelTab" xml:space="preserve">
<value>Modelo</value>
</data>
<data name="ModelTabVersion" xml:space="preserve">
<value>Versión: {0}</value>
</data>
<data name="ModelTabStatus" xml:space="preserve">
<value>Estado del modelo: {0}</value>
</data>
<data name="ModelTabError" xml:space="preserve">
<value>Último error: {0}</value>
</data>
<data name="FiltersTab" xml:space="preserve">
<value>Filtros</value>
</data>
<data name="AdvancedWarning1" xml:space="preserve">
<value>No modifiques los ajustes avanzados salvo que sepas lo que haces.</value>
</data>
<data name="OtherFiltersTab" xml:space="preserve">
<value>Otros filtros</value>
</data>
<data name="ChatFilters" xml:space="preserve">
<value>Filtros de chat</value>
</data>
<data name="EnableCustomChatFilters" xml:space="preserve">
<value>Habilitar filtros de chat personalizados</value>
</data>
<data name="PartyFinderFilters" xml:space="preserve">
<value>Filtros del Party Finder</value>
</data>
<data name="FilterIlvlPfs" xml:space="preserve">
<value>Filtrar PFs con el item level por encima del máximo</value>
</data>
<data name="FilterPrivatePfs" xml:space="preserve">
<value>Aplicar filtros a listados privados del Party Finder</value>
</data>
<data name="SubstringsToFilter" xml:space="preserve">
<value>Subcadenas a filtrar</value>
</data>
<data name="RegularExpressionsToFilter" xml:space="preserve">
<value>Expresiones regulares a filtrar</value>
</data>
<data name="SaveFilters" xml:space="preserve">
<value>Guardar filtros</value>
</data>
<data name="Reporting" xml:space="preserve">
<value>Reportes de {0}</value>
</data>
<data name="ReportHelp" xml:space="preserve">
<value>Haz click en una de las entradas de abajo para informar de que ha sido categorizada erróneamente.</value>
</data>
<data name="ReportStatusFailure" xml:space="preserve">
<value>No se ha podido enviar</value>
</data>
<data name="ReportStatusSuccessful" xml:space="preserve">
<value>Enviado con éxito</value>
</data>
<data name="ReportStatusInProgress" xml:space="preserve">
<value>enviando</value>
</data>
<data name="ReportStatusUnknown" xml:space="preserve">
<value>desconocido</value>
</data>
<data name="ReportChatTab" xml:space="preserve">
<value>Chat</value>
</data>
<data name="ReportColumnTimestamp" xml:space="preserve">
<value>Marca de tiempo</value>
</data>
<data name="ReportColumnChannel" xml:space="preserve">
<value>Canal</value>
</data>
<data name="ReportColumnReason" xml:space="preserve">
<value>Razón</value>
</data>
<data name="ReportColumnSender" xml:space="preserve">
<value>Remitente</value>
</data>
<data name="ReportColumnMessage" xml:space="preserve">
<value>Mensaje</value>
</data>
<data name="ReportPartyFinderTab" xml:space="preserve">
<value>Party Finder</value>
</data>
<data name="ReportColumnHost" xml:space="preserve">
<value>Anfitrión</value>
</data>
<data name="ReportColumnDescription" xml:space="preserve">
<value>Descripción</value>
</data>
<data name="ReportModalTitle" xml:space="preserve">
<value>Informar a {0}</value>
</data>
<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="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="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">
<value>Reportar</value>
</data>
<data name="ReportModalCopy" xml:space="preserve">
<value>Copiar al portapapeles</value>
</data>
<data name="ReportModalCancel" xml:space="preserve">
<value>Cancelar</value>
</data>
<data name="TradeCategory" xml:space="preserve">
<value>Anuncios de intercambio</value>
</data>
<data name="FreeCompanyCategory" xml:space="preserve">
<value>Anuncios de Free Company</value>
</data>
<data name="PhishingCategory" xml:space="preserve">
<value>Mensajes de phishing</value>
</data>
<data name="RmtContentCategory" xml:space="preserve">
<value>RMT (contenido)</value>
</data>
<data name="RmtGilCategory" xml:space="preserve">
<value>RMT (guil)</value>
</data>
<data name="RoleplayingCategory" xml:space="preserve">
<value>Anuncios de RP</value>
</data>
<data name="StaticCategory" xml:space="preserve">
<value>Reclutamiento de statics</value>
</data>
<data name="CommunityCategory" xml:space="preserve">
<value>Anuncios de comunidades</value>
</data>
<data name="FreeCompanyDescription" xml:space="preserve">
<value>Anuncios de Free Companies</value>
</data>
<data name="NormalDescription" xml:space="preserve">
<value>Mensajes normales que no deberían ser filtrados</value>
</data>
<data name="PhishingDescription" xml:space="preserve">
<value>Mensajes intentando engañarte para que reveles detalles de tu cuenta con el objetivo de robártela</value>
</data>
<data name="RmtContentDescription" xml:space="preserve">
<value>Intercambio con dinero real relacionado con contenido (también conocidos como vendedores de contenido)</value>
</data>
<data name="RoleplayingDescription" xml:space="preserve">
<value>Anuncios de RP personal, comunidades de rol, establecimientos donde rolear, o cualquier otra cosa relacionada con el RP</value>
</data>
<data name="StaticDescription" xml:space="preserve">
<value>Statics buscando miembros o jugadores buscando static</value>
</data>
<data name="CommunityDescription" xml:space="preserve">
<value>Anuncios de comunidades de uso general, normalmente servidores de Discord</value>
</data>
<data name="ReportToNoSoliciting" xml:space="preserve">
<value>Informar a NoSoliciting</value>
</data>
<data name="ReportToastSuccess" xml:space="preserve">
<value>El listado de Party Finder de {0} ha sido reportado con éxito.</value>
</data>
<data name="ReportToastFailure" xml:space="preserve">
<value>No se ha podido reportar el listado de Party Finder de {0}.</value>
</data>
<data name="OtherGameLanguage" xml:space="preserve">
<value>Usar el idioma del juego en lugar del idioma en el que está Dalamud</value>
</data>
<data name="OtherTab" xml:space="preserve">
<value>Otros</value>
</data>
<data name="ShowReportingWindow" xml:space="preserve">
<value>Mostrar ventana de reportes</value>
</data>
<data name="UpdateModel" xml:space="preserve">
<value>Actualizar modelo</value>
</data>
<data name="AdvancedWarning2" xml:space="preserve">
<value>El modelo de machine learning ha sido entrenado con ciertos canales en mente.</value>
</data>
<data name="EnableCustomPartyFinderFilters" xml:space="preserve">
<value>Habilitar filtros del Party Finder personalizados</value>
</data>
<data name="ReportStatusMessage" xml:space="preserve">
<value>Estado del último reporte: {0}</value>
</data>
<data name="NormalCategory" xml:space="preserve">
<value>Mensajes normales</value>
</data>
<data name="StaticSubCategory" xml:space="preserve">
<value>Sustitutos de statics</value>
</data>
<data name="TradeDescription" xml:space="preserve">
<value>Mensajes que anuncian el intercambio de objetos o servicios a cambio de guiles, como crafters buscando trabajo o gente vendiendo objetos poco comunes</value>
</data>
<data name="RmtGilDescription" xml:space="preserve">
<value>Intercambio de dinero real relacionado con guiles u objetos (también conocidos como bots de RMT o vendeoros)</value>
</data>
<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

@ -0,0 +1,273 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ReportToNoSoliciting" xml:space="preserve">
<value>Signaler à NoSoliciting</value>
</data>
<data name="Settings" xml:space="preserve">
<value>Paramètres de {0}</value>
</data>
<data name="AdvancedMode" xml:space="preserve">
<value>Paramètres avancés</value>
</data>
<data name="SaveFilters" xml:space="preserve">
<value>Sauvegarder les filtres</value>
</data>
<data name="Reporting" xml:space="preserve">
<value>Signalement à {0}</value>
</data>
<data name="ReportHelp" xml:space="preserve">
<value>Cliquez sur l'une des entrées pour la signaler à la développeuse comme étant mal catégorisée.</value>
</data>
<data name="ReportStatusFailure" xml:space="preserve">
<value>échec de l'envoi</value>
</data>
<data name="ReportStatusSuccessful" xml:space="preserve">
<value>effectué avec succès</value>
</data>
<data name="ReportStatusInProgress" xml:space="preserve">
<value>envoi en cours</value>
</data>
<data name="ReportStatusUnknown" xml:space="preserve">
<value>inconnu</value>
</data>
<data name="ReportStatusMessage" xml:space="preserve">
<value>État du dernier signalement : {0}</value>
</data>
<data name="ReportChatTab" xml:space="preserve">
<value>Discussion</value>
</data>
<data name="ReportColumnTimestamp" xml:space="preserve">
<value>Date et heure</value>
</data>
<data name="ReportColumnChannel" xml:space="preserve">
<value>Canal</value>
</data>
<data name="ReportColumnReason" xml:space="preserve">
<value>Raison</value>
</data>
<data name="OtherTab" xml:space="preserve">
<value>Autres</value>
</data>
<data name="ModelTab" xml:space="preserve">
<value>Modèle</value>
</data>
<data name="ModelTabVersion" xml:space="preserve">
<value>Version : {0}</value>
</data>
<data name="ModelTabStatus" xml:space="preserve">
<value>État du modèle : {0}</value>
</data>
<data name="ModelTabError" xml:space="preserve">
<value>Dernière erreur rencontrée : {0}</value>
</data>
<data name="UpdateModel" xml:space="preserve">
<value>Mettre à jour le modèle</value>
</data>
<data name="FiltersTab" xml:space="preserve">
<value>Filtres</value>
</data>
<data name="AdvancedWarning1" xml:space="preserve">
<value>Veuillez ne modifier les paramètres avancés que si vous savez exactement ce que vous faites.</value>
</data>
<data name="LogFilteredPfs" xml:space="preserve">
<value>Enregistrer les Recherches d'Équipe (PFs) filtrées</value>
</data>
<data name="LogFilteredMessages" xml:space="preserve">
<value>Enregistrer les messages filtrées</value>
</data>
<data name="ShowReportingWindow" xml:space="preserve">
<value>Ouvrir la fenêtre de signalement</value>
</data>
<data name="AdvancedWarning2" xml:space="preserve">
<value>Le modèle d'apprentissage automatique a été entraîné selon une configuration précise de channels.</value>
</data>
<data name="OtherFiltersTab" xml:space="preserve">
<value>Autres filtres</value>
</data>
<data name="ChatFilters" xml:space="preserve">
<value>Filtres de discussion</value>
</data>
<data name="EnableCustomChatFilters" xml:space="preserve">
<value>Activer les filtres de discussion personnalisés</value>
</data>
<data name="FilterPrivatePfs" xml:space="preserve">
<value>Appliquer les filtres aux Recherches d'Équipe (PFs) listées en privées</value>
</data>
<data name="EnableCustomPartyFinderFilters" xml:space="preserve">
<value>Activer les filtres de Recherche d'Équipe (PFs) personnalisés</value>
</data>
<data name="SubstringsToFilter" xml:space="preserve">
<value>Sous-chaînes (substrings) à filtrer</value>
</data>
<data name="RegularExpressionsToFilter" xml:space="preserve">
<value>Expressions régulières à filtrer</value>
</data>
<data name="PartyFinderFilters" xml:space="preserve">
<value>Filtres de Recherche d'Équipe (PFs)</value>
</data>
<data name="FilterIlvlPfs" xml:space="preserve">
<value>Filtrer les Recherche d'Équipe (PFs) avec un niveau d'objet requis supérieur au maximum</value>
</data>
<data name="OtherGameLanguage" xml:space="preserve">
<value>Utiliser la langue des paramètres du jeu au lieu de ceux de Dalamud</value>
</data>
<data name="TradeCategory" xml:space="preserve">
<value>Publicités à but d'échange</value>
</data>
<data name="FreeCompanyCategory" xml:space="preserve">
<value>Publicités de compagnie libre</value>
</data>
<data name="TradeDescription" xml:space="preserve">
<value>Messages faisant la publicité d'échanges d'objets ou de services contre des gils, tels que les omnicrafteurs à la recherche de travail ou de vendeurs d'objets rares via le tableau des marchés</value>
</data>
<data name="PhishingDescription" xml:space="preserve">
<value>Messages tentant de vous piéger dans le but de vous faire révéler les détails et identifiants de votre compte afin de vous le voler</value>
</data>
<data name="RmtGilDescription" xml:space="preserve">
<value>Ventes contre de l'argent réel impliquant des gils (aussi connus sous le nom de gils RMT : Real Money Traders)</value>
</data>
<data name="RmtContentCategory" xml:space="preserve">
<value>RMT (contenu)</value>
</data>
<data name="PhishingCategory" xml:space="preserve">
<value>Messages de phishing</value>
</data>
<data name="NormalCategory" xml:space="preserve">
<value>Messages normaux</value>
</data>
<data name="RmtGilCategory" xml:space="preserve">
<value>RMT (gils)</value>
</data>
<data name="RoleplayingCategory" xml:space="preserve">
<value>Publicités pour du jeu de rôle (RP)</value>
</data>
<data name="StaticCategory" xml:space="preserve">
<value>Recrutement de statique</value>
</data>
<data name="CommunityCategory" xml:space="preserve">
<value>Publicités communautaires</value>
</data>
<data name="StaticSubCategory" xml:space="preserve">
<value>Remplacements de statique</value>
</data>
<data name="FreeCompanyDescription" xml:space="preserve">
<value>Messages de recrutement de la part de Compagnies Libres</value>
</data>
<data name="NormalDescription" xml:space="preserve">
<value>Les messages normaux ne devraient pas être filtrés</value>
</data>
<data name="RmtContentDescription" xml:space="preserve">
<value>Ventes contre de l'argent réel impliquant la complétion de contenu (aussi connus sous le nom de content RMT : Real Money Traders)</value>
</data>
<data name="RoleplayingDescription" xml:space="preserve">
<value>Promotion pour du jeu de rôle personnel, des communautés de jeu de rôle, des événements et tout ce qui a trait au jeu de rôle</value>
</data>
<data name="StaticDescription" xml:space="preserve">
<value>Membres d'une statique à la recherche de membres, ou joueurs à la recherche de personnes qui cherchent une statique</value>
</data>
<data name="ReportToastSuccess" xml:space="preserve">
<value>Recherche d'équipe organisée par {0} signalée avec succès.</value>
</data>
<data name="ReportToastFailure" xml:space="preserve">
<value>Échec du signalement de la Recherche d'Équipe de {0].</value>
</data>
<data name="ReportColumnSender" xml:space="preserve">
<value>Expéditeur</value>
</data>
<data name="ReportColumnMessage" xml:space="preserve">
<value>Message</value>
</data>
<data name="ReportPartyFinderTab" xml:space="preserve">
<value>Recherche d'Équipe</value>
</data>
<data name="ReportColumnHost" xml:space="preserve">
<value>Recruteur</value>
</data>
<data name="ReportColumnDescription" xml:space="preserve">
<value>Description</value>
</data>
<data name="ReportModalTitle" xml:space="preserve">
<value>Signaler à {0}</value>
</data>
<data name="CommunityDescription" xml:space="preserve">
<value>Publicités pour des communautés de tous types et pour tous propos, généralement des serveurs Discord</value>
</data>
<data name="StaticSubDescription" xml:space="preserve">
<value>Statique à la recherche de remplaçants de leurs membres manquants pour leur complétion</value>
</data>
<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="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="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">
<value>Signaler</value>
</data>
<data name="ReportModalCopy" xml:space="preserve">
<value>Copier dans le presse-papiers</value>
</data>
<data name="ReportModalCancel" xml:space="preserve">
<value>Annuler</value>
</data>
<data name="ModelStatusUninitialised" xml:space="preserve">
<value>Non-initialisée</value>
</data>
<data name="ModelStatusPreparing" xml:space="preserve">
<value>Préparation de la mise à jour du modèle</value>
</data>
<data name="ModelStatusInitialised" xml:space="preserve">
<value>Initialisé</value>
</data>
<data name="ModelStatusDownloadingManifest" xml:space="preserve">
<value>Téléchargement du manifeste du modèle en cours</value>
</data>
<data name="ModelStatusDownloadingModel" xml:space="preserve">
<value>Téléchargement du modèle</value>
</data>
<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

@ -0,0 +1,292 @@
<?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>その他</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

@ -0,0 +1,285 @@
<?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:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="OtherTab" xml:space="preserve">
<value>Other</value>
</data>
<data name="LogFilteredPfs" xml:space="preserve">
<value>Log filtered PFs</value>
</data>
<data name="LogFilteredMessages" xml:space="preserve">
<value>Log filtered messages</value>
</data>
<data name="ShowReportingWindow" xml:space="preserve">
<value>Show reporting window</value>
</data>
<data name="Settings" xml:space="preserve">
<value>{0} settings</value>
</data>
<data name="AdvancedMode" xml:space="preserve">
<value>Advanced mode</value>
</data>
<data name="ModelTab" xml:space="preserve">
<value>Model</value>
</data>
<data name="ModelTabVersion" xml:space="preserve">
<value>Version: {0}</value>
</data>
<data name="ModelTabStatus" xml:space="preserve">
<value>Model status: {0}</value>
</data>
<data name="ModelTabError" xml:space="preserve">
<value>Last error: {0}</value>
</data>
<data name="UpdateModel" xml:space="preserve">
<value>Update model</value>
</data>
<data name="FiltersTab" xml:space="preserve">
<value>Filters</value>
</data>
<data name="AdvancedWarning1" xml:space="preserve">
<value>Do not change advanced settings unless you know what you are doing.</value>
</data>
<data name="AdvancedWarning2" xml:space="preserve">
<value>The machine learning model was trained with certain channels in mind.</value>
</data>
<data name="OtherFiltersTab" xml:space="preserve">
<value>Other filters</value>
</data>
<data name="ChatFilters" xml:space="preserve">
<value>Chat filters</value>
</data>
<data name="EnableCustomChatFilters" xml:space="preserve">
<value>Enable custom chat filters</value>
</data>
<data name="PartyFinderFilters" xml:space="preserve">
<value>Party Finder filters</value>
</data>
<data name="FilterIlvlPfs" xml:space="preserve">
<value>Filter PFs with item level above maximum</value>
</data>
<data name="FilterPrivatePfs" xml:space="preserve">
<value>Apply filters to private Party Finder listings</value>
</data>
<data name="EnableCustomPartyFinderFilters" xml:space="preserve">
<value>Enable custom Party Finder filters</value>
</data>
<data name="SubstringsToFilter" xml:space="preserve">
<value>Substrings to filter</value>
</data>
<data name="RegularExpressionsToFilter" xml:space="preserve">
<value>Regular expressions to filter</value>
</data>
<data name="SaveFilters" xml:space="preserve">
<value>Save filters</value>
</data>
<data name="Reporting" xml:space="preserve">
<value>{0} reporting</value>
</data>
<data name="ReportHelp" xml:space="preserve">
<value>Click on one of the entries below to report it to the developer as miscategorised.</value>
</data>
<data name="ReportStatusFailure" xml:space="preserve">
<value>failed to send</value>
</data>
<data name="ReportStatusSuccessful" xml:space="preserve">
<value>sent successfully</value>
</data>
<data name="ReportStatusInProgress" xml:space="preserve">
<value>sending</value>
</data>
<data name="ReportStatusUnknown" xml:space="preserve">
<value>unknown</value>
</data>
<data name="ReportStatusMessage" xml:space="preserve">
<value>Last report status: {0}</value>
</data>
<data name="ReportChatTab" xml:space="preserve">
<value>Chat</value>
</data>
<data name="ReportColumnTimestamp" xml:space="preserve">
<value>Timestamp</value>
</data>
<data name="ReportColumnChannel" xml:space="preserve">
<value>Channel</value>
</data>
<data name="ReportColumnReason" xml:space="preserve">
<value>Reason</value>
</data>
<data name="ReportColumnSender" xml:space="preserve">
<value>Sender</value>
</data>
<data name="ReportColumnMessage" xml:space="preserve">
<value>Message</value>
</data>
<data name="ReportPartyFinderTab" xml:space="preserve">
<value>Party Finder</value>
</data>
<data name="ReportColumnHost" xml:space="preserve">
<value>Host</value>
</data>
<data name="ReportColumnDescription" xml:space="preserve">
<value>Description</value>
</data>
<data name="ReportModalTitle" xml:space="preserve">
<value>Report to {0}</value>
</data>
<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="ReportModalHelp2" xml:space="preserve">
<value>NoSoliciting only works for English messages. Do not report non-English messages.</value>
</data>
<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">
<value>Report</value>
</data>
<data name="ReportModalCopy" xml:space="preserve">
<value>Copy to clipboard</value>
</data>
<data name="ReportModalCancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="TradeCategory" xml:space="preserve">
<value>Trade ads</value>
</data>
<data name="FreeCompanyCategory" xml:space="preserve">
<value>Free Company ads</value>
</data>
<data name="NormalCategory" xml:space="preserve">
<value>Normal messages</value>
</data>
<data name="PhishingCategory" xml:space="preserve">
<value>Phishing messages</value>
</data>
<data name="RmtContentCategory" xml:space="preserve">
<value>RMT (content)</value>
</data>
<data name="RmtGilCategory" xml:space="preserve">
<value>RMT (gil)</value>
</data>
<data name="RoleplayingCategory" xml:space="preserve">
<value>Roleplaying ads</value>
</data>
<data name="StaticCategory" xml:space="preserve">
<value>Static recruitment</value>
</data>
<data name="CommunityCategory" xml:space="preserve">
<value>Community ads</value>
</data>
<data name="StaticSubCategory" xml:space="preserve">
<value>Static substitutes</value>
</data>
<data name="TradeDescription" xml:space="preserve">
<value>Messages advertising trading items or services for gil, such as omnicrafters looking for work or people selling rare items off the market board</value>
</data>
<data name="FreeCompanyDescription" xml:space="preserve">
<value>Advertisements for Free Companies</value>
</data>
<data name="NormalDescription" xml:space="preserve">
<value>Normal messages that should not be filtered</value>
</data>
<data name="PhishingDescription" xml:space="preserve">
<value>Messages trying to trick you into revealing your account details in order to steal your account</value>
</data>
<data name="RmtContentDescription" xml:space="preserve">
<value>Real-money trade involving content (also known as content sellers)</value>
</data>
<data name="RmtGilDescription" xml:space="preserve">
<value>Real-money trade involving gil or items (also known as RMT bots)</value>
</data>
<data name="RoleplayingDescription" xml:space="preserve">
<value>Advertisements for personal RP, RP communities, venues, or anything else related to roleplaying</value>
</data>
<data name="StaticDescription" xml:space="preserve">
<value>Statics looking for members or players looking for a static</value>
</data>
<data name="CommunityDescription" xml:space="preserve">
<value>Advertisements for general-purpose communities, generally Discord servers</value>
</data>
<data name="StaticSubDescription" xml:space="preserve">
<value>Statics looking for fill-ins of missing members for clears</value>
</data>
<data name="ReportToNoSoliciting" xml:space="preserve">
<value>Report to NoSoliciting</value>
</data>
<data name="ReportToastSuccess" xml:space="preserve">
<value>Party Finder listing hosted by {0} reported successfully.</value>
</data>
<data name="ReportToastFailure" xml:space="preserve">
<value>Failed to report Party Finder listing hosted by {0}.</value>
</data>
<data name="OtherGameLanguage" xml:space="preserve">
<value>Use the game's language setting instead of Dalamud's</value>
</data>
<data name="ModelStatusUninitialised" xml:space="preserve">
<value>Uninitialised</value>
</data>
<data name="ModelStatusPreparing" xml:space="preserve">
<value>Preparing to update model</value>
</data>
<data name="ModelStatusDownloadingManifest" xml:space="preserve">
<value>Downloading model manifest</value>
</data>
<data name="ModelStatusDownloadingModel" xml:space="preserve">
<value>Downloading model</value>
</data>
<data name="ModelStatusInitialising" xml:space="preserve">
<value>Initialising model and classifier</value>
</data>
<data name="ModelStatusInitialised" xml:space="preserve">
<value>Initialised</value>
</data>
<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

@ -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="OtherGameLanguage" xml:space="preserve">
<value>使用游戏语言而不是 Dalamud 的语言作为插件语言</value>
</data>
<data name="LogFilteredPfs" xml:space="preserve">
<value>启用队员招募过滤日志</value>
</data>
<data name="ShowReportingWindow" xml:space="preserve">
<value>回报错误</value>
</data>
<data name="Settings" xml:space="preserve">
<value>{0} 设置</value>
</data>
<data name="AdvancedMode" xml:space="preserve">
<value>高级模式</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>最后一个错误:{0}</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="EnableCustomChatFilters" xml:space="preserve">
<value>启用自定义聊天消息过滤</value>
</data>
<data name="PartyFinderFilters" xml:space="preserve">
<value>队员招募过滤</value>
</data>
<data name="FilterPrivatePfs" 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="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="ReportColumnTimestamp" 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="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="FreeCompanyCategory" xml:space="preserve">
<value>部队广告</value>
</data>
<data name="StaticSubCategory" 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="RoleplayingCategory" xml:space="preserve">
<value>角色扮演广告</value>
</data>
<data name="TradeDescription" 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="RmtGilDescription" 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="CommunityDescription" xml:space="preserve">
<value>宣传玩家社区/团体的广告,例如 Discord 服务器等</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="ReportToastFailure" xml:space="preserve">
<value>由 {0} 发起的队员招募报告失败。</value>
</data>
<data name="CommunityCategory" xml:space="preserve">
<value>社区广告</value>
</data>
<data name="StaticCategory" xml:space="preserve">
<value>固定队招募</value>
</data>
<data name="OtherTab" xml:space="preserve">
<value>其他</value>
</data>
<data name="LogFilteredMessages" xml:space="preserve">
<value>启用文字消息过滤日志</value>
</data>
<data name="AdvancedWarning1" xml:space="preserve">
<value>除非你明确知道自己在干什么,否则请不要更改高级设置。</value>
</data>
<data name="FilterIlvlPfs" xml:space="preserve">
<value>过滤装备品级高于最大值的招募</value>
</data>
<data name="ReportHelp" xml:space="preserve">
<value>单击下方分类错误的项目以将其报告给开发者。</value>
</data>
<data name="ReportModalHelp1" xml:space="preserve">
<value>报告此消息将使开发者知道此消息被错误归类。</value>
</data>
<data name="ReportModalHelp2" xml:space="preserve">
<value>NoSoliciting 只对英文消息有效。请不要报告其他语言的消息。</value>
</data>
<data name="ModelStatusUninitialised" xml:space="preserve">
<value>未初始化</value>
</data>
<data name="ModelStatusPreparing" xml:space="preserve">
<value>准备更新中</value>
</data>
<data name="ModelStatusDownloadingModel" xml:space="preserve">
<value>正在下载模型</value>
</data>
<data name="ModelStatusDownloadingManifest" xml:space="preserve">
<value>正在获取模型信息</value>
</data>
<data name="ModelStatusInitialising" xml:space="preserve">
<value>正在进行初始化</value>
</data>
<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

@ -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="ReportStatusMessage" xml:space="preserve">
<value>最後報告狀態:{0}</value>
</data>
<data name="ReportModalHelp1" xml:space="preserve">
<value>報告此消息將使開發人員知道此消息被錯誤歸類。</value>
</data>
<data name="ReportModalDisabledCustom" xml:space="preserve">
<value>你不能報告被客製化過濾規則過濾的消息。</value>
</data>
<data name="OtherTab" xml:space="preserve">
<value>其他</value>
</data>
<data name="ModelTab" xml:space="preserve">
<value>機器學習模式</value>
</data>
<data name="AdvancedWarning1" xml:space="preserve">
<value>除非你明確知道自己在做甚麼,否則請不要更改高級設置。</value>
</data>
<data name="FilterPrivatePfs" 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="ReportHelp" xml:space="preserve">
<value>單擊下方分類錯誤的項目以將其報告給開發人員。</value>
</data>
<data name="ReportStatusFailure" xml:space="preserve">
<value>發送失敗</value>
</data>
<data name="ReportStatusSuccessful" 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="ReportChatTab" xml:space="preserve">
<value>聊天消息</value>
</data>
<data name="ReportColumnTimestamp" 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="ReportModalReport" xml:space="preserve">
<value>報告</value>
</data>
<data name="LogFilteredPfs" xml:space="preserve">
<value>啟用隊員招募過濾日誌</value>
</data>
<data name="LogFilteredMessages" xml:space="preserve">
<value>啟用文字消息過濾日誌</value>
</data>
<data name="ShowReportingWindow" xml:space="preserve">
<value>回報錯誤</value>
</data>
<data name="Settings" xml:space="preserve">
<value>{0} 設置</value>
</data>
<data name="AdvancedMode" 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>最後一個錯誤:{0}</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="EnableCustomChatFilters" 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="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="FreeCompanyCategory" 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="RoleplayingCategory" xml:space="preserve">
<value>角色扮演廣告</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="TradeDescription" 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="RmtGilDescription" 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="CommunityDescription" xml:space="preserve">
<value>宣傳玩家社區/團體的廣告,例如 Discord 伺服器等</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="ReportToastFailure" xml:space="preserve">
<value>由 {0} 發起的隊員招募報告失敗。</value>
</data>
<data name="OtherGameLanguage" xml:space="preserve">
<value>使用遊戲語言而不是 Dalamud 的語言作為插件語言</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>
<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

@ -1,16 +0,0 @@
using System;
using System.IO;
using Dalamud.Plugin;
namespace NoSoliciting {
public static class Util {
public static string PluginFolder(IDalamudPlugin plugin) {
return Path.Combine(new[] {
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"XIVLauncher",
"pluginConfigs",
plugin.Name,
});
}
}
}

View File

@ -1,150 +0,0 @@
# THIS FILE IS DEPRECATED AND WILL NO LONGER RECEIVE UPDATES. Please
# see the file called data.csv in NoSoliciting.Trainer for how to
# update the new machine learning model that has replaced this file.
# This file defines the filters that NoSoliciting will use for
# built-in filters.
# The version should be incremented for each commit including changes
# to this file.
# There are two main sections: chat and party_finder. The chat and
# party_finder sections are for their respective areas (the chat log
# and the Party Finder window).
# Each subsection is a separate built-in filter that can be toggled on
# and off. The option shown in the UI is defined in the subsection.
# Subsections can have ignore_case (defaults to false) and normalise
# (defaults to true) set. ignore_case will ignore casing for matching
# against the matchers, and normalise will normalise text prior to
# matching. Text normalisation consists of turning FFXIV-specific
# unicode symbols into normal ASCII characters and running a NFKD
# unicode decomposition on the result.
# Subsections also may filter based on channels with the channels key.
# A list of channels may be specified, and the message will be ignored
# if not in one of the specified channels. For the Party Finder, the
# channel is always None. An empty list (or missing channels key) will
# ignore the channel.
# Each subsection may specify whether it is enabled by default with
# the default key. This should be used sparingly. This defaults to
# false.
# The real meat of the file is the matchers. There are two types of
# matchers: required and likely. Both types have categories of strings
# or regular expressions that should match. For required matchers, at
# least one string or regex should match in *all* categories. For
# likely matchers, at least one string or regex should match in the
# value of likelihood_threshold (or greater) categories.
# If both required and likely matchers are specified, they both must
# match. This means that all the categories of the required matchers
# must find a match, *and* that at least likelihood_threshold matchers
# must find a match in likely_matchers.
# Substring matchers are faster than regular expressions and are
# specified just by using a string. Regular expression matchers are
# slower but more flexible, and they are specified by using a regex
# key, as can be seen below.
version: 68
# This is the URL the plugin will POST reports to.
report_url: https://nosol.annaclemens.io/report
chat:
rmt:
option:
basic: Filter RMT from chat
advanced: Enable built-in chat RMT filter
default: true
required_matchers:
- - 4KGOLD
- We have sufficient stock
- PVPBANK.COM
- Gil for free
- www.so9.com
- Fast & Convenient
- Cheap & Safety Guarantee
- 【Code|A O A U E
- igfans
- 4KGOLD.COM
- Cheapest Gil with
- pvp and bank on google
- Selling Cheap GIL
- ff14mogstation.com
- Cheap Gil 1000k
- gilsforyou
- server 1000K =
- gils_selling
- E A S Y.C O M
- bonus code
- mins delivery guarantee
- Sell cheap
- Salegm.com
- cheap Mog
- Off Code
- FF14Mog.com
- ff14mog.com
- 使用する5
- 5分納品
- offers Fantasia
- 5GOLD.COM
- Buy Cheap gils
- regex: '【 .+ 、. = .+ 】'
- regex: 'finalfantasyxiv\.com-\w+\.\w+' # phishing
- regex: '\.com-\w+\.\w+/' # phishing
- regex: '(?i)giveaway of \d+.?m.*discord.gg/.' # phishing
party_finder:
rmt:
option:
basic: Filter RMT from Party Finder
advanced: Enable built-in Party Finder RMT filter
default: true
ignore_case: true
required_matchers:
# selling
- - sell
- s e l l
- $ell
- $ e l l
- sale
- s a l e
- price
- cheap
- gil only
# rmt groups
- minmax
- lalakuza
- legacy
- sk7
- restocker
- 'fast&easy'
- fast and easy help
- '[helping]'
- lawyer
# more keywords
- deliver
- best team
- satisfaction
- coaching
- reliable
- regex: '\s#1'
- regex: '\bi\d{3}.+loot\b'
- regex: 'm\s*i\s*n\s*m\s*a\s*x'
- regex: 'pre.?order'
- regex: '\boffer'
# content
- - eden
- savage
- primal
- ultimate
- trial
- loot
- content
- ucob
- regex: '\bex\b'
- regex: e[1-9][0-2]?

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