Compare commits

...

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

66 changed files with 4499 additions and 1444 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);

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,24 +0,0 @@
using System;
using Dalamud.Game.Command;
namespace NoSoliciting.Lite {
public class Commands : IDisposable {
private Plugin Plugin { get; }
internal Commands(Plugin plugin) {
this.Plugin = plugin;
this.Plugin.Interface.CommandManager.AddHandler("/nolite", new CommandInfo(this.OnCommand) {
HelpMessage = "Open the NoSol Lite config",
});
}
public void Dispose() {
this.Plugin.Interface.CommandManager.RemoveHandler("/nolite");
}
private void OnCommand(string command, string args) {
this.Plugin.Ui.ToggleConfig();
}
}
}

View File

@ -1,58 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Dalamud.Configuration;
using Dalamud.Plugin;
using Newtonsoft.Json;
namespace NoSoliciting.Lite {
[Serializable]
internal class Configuration : IPluginConfiguration {
private DalamudPluginInterface Interface { get; set; } = null!;
public int Version { get; set; } = 1;
public bool CustomChatFilter { get; set; }
public List<string> ChatSubstrings { get; } = new();
public List<string> ChatRegexes { get; } = new();
[JsonIgnore]
public List<Regex> CompiledChatRegexes { get; private set; } = new();
public bool CustomPFFilter { get; set; }
public List<string> PFSubstrings { get; } = new();
public List<string> PFRegexes { get; } = new();
[JsonIgnore]
public List<Regex> CompiledPfRegexes { get; private set; } = new();
public bool LogFilteredPfs { get; set; } = true;
public bool LogFilteredChat { get; set; } = true;
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();
}
public void Save() {
this.Interface.SavePluginConfig(this);
}
public void CompileRegexes() {
this.CompiledChatRegexes = this.ChatRegexes
.Where(reg => !string.IsNullOrWhiteSpace(reg))
.Select(reg => new Regex(reg, RegexOptions.Compiled))
.ToList();
this.CompiledPfRegexes = this.PFRegexes
.Where(reg => !string.IsNullOrWhiteSpace(reg))
.Select(reg => new Regex(reg, RegexOptions.Compiled))
.ToList();
}
}
}

View File

@ -1,10 +0,0 @@
<Project>
<Target Name="PackagePlugin" AfterTargets="Build" Condition="'$(Configuration)' == 'Release'">
<DalamudPackager ProjectDir="$(ProjectDir)"
OutputPath="$(OutputPath)"
AssemblyName="$(AssemblyName)"
VersionComponents="3"
MakeZip="true"
Include="NoSoliciting.Lite.json;NoSoliciting.Lite.dll;NoSoliciting.Lite.pdb"/>
</Target>
</Project>

View File

@ -1,9 +0,0 @@
using System.Globalization;
namespace NoSoliciting.Lite {
internal static class Extensions {
internal static bool ContainsIgnoreCase(this string haystack, string needle) {
return CultureInfo.InvariantCulture.CompareInfo.IndexOf(haystack, needle, CompareOptions.IgnoreCase) >= 0;
}
}
}

View File

@ -1,59 +0,0 @@
using System;
using System.Linq;
using Dalamud.Game.Internal.Gui;
using Dalamud.Game.Internal.Gui.Structs;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin;
namespace NoSoliciting.Lite {
public class Filter : IDisposable {
private Plugin Plugin { get; }
internal Filter(Plugin plugin) {
this.Plugin = plugin;
this.Plugin.Interface.Framework.Gui.Chat.OnChatMessage += this.OnChat;
this.Plugin.Interface.Framework.Gui.PartyFinder.ReceiveListing += this.ReceiveListing;
}
public void Dispose() {
this.Plugin.Interface.Framework.Gui.PartyFinder.ReceiveListing -= this.ReceiveListing;
this.Plugin.Interface.Framework.Gui.Chat.OnChatMessage -= this.OnChat;
}
private void OnChat(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled) {
if (isHandled) {
return;
}
var text = message.TextValue;
isHandled = this.Plugin.Config.ValidChatSubstrings.Any(needle => text.ContainsIgnoreCase(needle))
|| this.Plugin.Config.CompiledChatRegexes.Any(needle => needle.IsMatch(text));
if (this.Plugin.Config.LogFilteredChat && isHandled) {
PluginLog.Log($"Filtered chat message: {text}");
}
}
private void ReceiveListing(PartyFinderListing listing, PartyFinderListingEventArgs args) {
if (!args.Visible) {
return;
}
if (listing[SearchAreaFlags.Private] && !this.Plugin.Config.ConsiderPrivatePfs) {
return;
}
var text = listing.Description.TextValue;
args.Visible = !(this.Plugin.Config.ValidPfSubstrings.Any(needle => text.ContainsIgnoreCase(needle))
|| this.Plugin.Config.CompiledPfRegexes.Any(needle => needle.IsMatch(text)));
if (this.Plugin.Config.LogFilteredPfs && !args.Visible) {
PluginLog.Log($"Filtered PF: {text}");
}
}
}
}

View File

@ -1,3 +0,0 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ResourcesMerge/>
</Weavers>

View File

@ -1,44 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<Version>1.0.0</Version>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Reference Include="Dalamud">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ImGui.NET">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\ImGui.NET.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ImGuiScene">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\ImGuiScene.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Newtonsoft.Json.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\Language.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Language.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\Language.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Language.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<PackageReference Include="DalamudPackager" Version="1.2.1" />
<PackageReference Include="Fody" Version="6.5.1" PrivateAssets="all" />
<PackageReference Include="ResourcesMerge.Fody" Version="1.0.1" PrivateAssets="all" />
</ItemGroup>
</Project>

View File

@ -1,9 +0,0 @@
author: ascclemens
name: NoSoliciting Lite
description: |-
Customisable, simple chat and Party Finder filtering.
Only filters out text that you configure.
repo_url: https://git.sr.ht/~jkcclemens/NoSoliciting
# Use higher priority to filter messages before other plugins get them.
load_priority: 100

View File

@ -1,42 +0,0 @@
using System.Globalization;
using Dalamud.Plugin;
using NoSoliciting.Lite.Resources;
namespace NoSoliciting.Lite {
// ReSharper disable once ClassNeverInstantiated.Global
public class Plugin : IDalamudPlugin {
public string Name => "NoSoliciting Lite";
internal DalamudPluginInterface Interface { get; private set; } = null!;
internal Configuration Config { get; private set; } = null!;
internal PluginUi Ui { get; private set; } = null!;
private Commands Commands { get; set; } = null!;
private Filter Filter { get; set; } = null!;
public void Initialize(DalamudPluginInterface pluginInterface) {
this.Interface = pluginInterface;
this.ConfigureLanguage();
this.Interface.OnLanguageChanged += this.ConfigureLanguage;
this.Config = this.Interface.GetPluginConfig() as Configuration ?? new Configuration();
this.Config.Initialise(this.Interface);
this.Filter = new Filter(this);
this.Ui = new PluginUi(this);
this.Commands = new Commands(this);
}
public void Dispose() {
this.Commands.Dispose();
this.Ui.Dispose();
this.Filter.Dispose();
this.Interface.OnLanguageChanged -= this.ConfigureLanguage;
}
private void ConfigureLanguage(string? langCode = null) {
langCode ??= this.Interface.UiLanguage;
Language.Culture = new CultureInfo(langCode ?? "en");
}
}
}

View File

@ -1,170 +0,0 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text.RegularExpressions;
using Dalamud.Interface;
using ImGuiNET;
using NoSoliciting.Lite.Resources;
namespace NoSoliciting.Lite {
public class PluginUi : IDisposable {
private Plugin Plugin { get; }
private bool _showWindow;
internal PluginUi(Plugin plugin) {
this.Plugin = plugin;
this.Plugin.Interface.UiBuilder.OnBuildUi += this.Draw;
this.Plugin.Interface.UiBuilder.OnOpenConfigUi += this.ToggleConfig;
}
public void Dispose() {
this.Plugin.Interface.UiBuilder.OnOpenConfigUi -= this.ToggleConfig;
this.Plugin.Interface.UiBuilder.OnBuildUi -= this.Draw;
}
internal void ToggleConfig(object? sender = null, object? args = null) {
this._showWindow = !this._showWindow;
}
private void Draw() {
if (!this._showWindow) {
return;
}
ImGui.SetNextWindowSize(new Vector2(550, 350), ImGuiCond.FirstUseEver);
var windowTitle = string.Format(Language.Settings, this.Plugin.Name);
if (!ImGui.Begin($"{windowTitle}###nosol-lite-settings", ref this._showWindow)) {
ImGui.End();
return;
}
var shouldSave = false;
if (ImGui.BeginTabBar("nosol-lite-tabs")) {
if (ImGui.BeginTabItem("Chat")) {
var customChat = this.Plugin.Config.CustomChatFilter;
if (ImGui.Checkbox(Language.EnableCustomChatFilters, ref customChat)) {
this.Plugin.Config.CustomChatFilter = customChat;
shouldSave = true;
}
if (this.Plugin.Config.CustomChatFilter) {
var substrings = this.Plugin.Config.ChatSubstrings;
var regexes = this.Plugin.Config.ChatRegexes;
this.DrawCustom("chat", ref shouldSave, ref substrings, ref regexes);
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Party Finder")) {
var considerPrivate = this.Plugin.Config.ConsiderPrivatePfs;
if (ImGui.Checkbox(Language.FilterPrivatePfs, ref considerPrivate)) {
this.Plugin.Config.ConsiderPrivatePfs = considerPrivate;
shouldSave = true;
}
var customPf = this.Plugin.Config.CustomPFFilter;
if (ImGui.Checkbox(Language.EnableCustomPartyFinderFilters, ref customPf)) {
this.Plugin.Config.CustomPFFilter = customPf;
shouldSave = true;
}
if (this.Plugin.Config.CustomPFFilter) {
var substrings = this.Plugin.Config.PFSubstrings;
var regexes = this.Plugin.Config.PFRegexes;
this.DrawCustom("pf", ref shouldSave, ref substrings, ref regexes);
}
ImGui.EndTabItem();
}
ImGui.EndTabBar();
}
ImGui.End();
if (!shouldSave) {
return;
}
this.Plugin.Config.Save();
this.Plugin.Config.CompileRegexes();
}
private void DrawCustom(string name, ref bool shouldSave, ref List<string> substrings, ref List<string> regexes) {
ImGui.Columns(2);
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)) {
substrings[i] = input;
shouldSave = true;
}
ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button($"{FontAwesomeIcon.Trash.ToIconString()}##{name}-substring-{i}-remove")) {
substrings.RemoveAt(i);
shouldSave = true;
}
ImGui.PopFont();
}
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button($"{FontAwesomeIcon.Plus.ToIconString()}##{name}-substring-add")) {
substrings.Add("");
}
ImGui.PopFont();
ImGui.EndChild();
}
ImGui.NextColumn();
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)) {
try {
_ = new Regex(input);
// update if valid
regexes[i] = input;
shouldSave = true;
} catch (ArgumentException) {
// ignore
}
}
ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button($"{FontAwesomeIcon.Trash.ToIconString()}##{name}-regex-{i}-remove")) {
regexes.RemoveAt(i);
shouldSave = true;
}
ImGui.PopFont();
}
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button($"{FontAwesomeIcon.Plus.ToIconString()}##{name}-regex-add")) {
regexes.Add("");
}
ImGui.PopFont();
ImGui.EndChild();
}
ImGui.Columns(1);
}
}
}

View File

@ -1,117 +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.Lite.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.Lite.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 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 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 Regular expressions to filter.
/// </summary>
internal static string RegularExpressionsToFilter {
get {
return ResourceManager.GetString("RegularExpressionsToFilter", 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 Substrings to filter.
/// </summary>
internal static string SubstringsToFilter {
get {
return ResourceManager.GetString("SubstringsToFilter", resourceCulture);
}
}
}
}

View File

@ -1,32 +0,0 @@
<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="EnableCustomChatFilters" xml:space="preserve">
<value>Benutzerdefinierte Chat Filter aktivieren</value>
</data>
<data name="EnableCustomPartyFinderFilters" xml:space="preserve">
<value>Benutzerdefinierte Gruppensuche Filter aktivieren</value>
</data>
<data name="FilterPrivatePfs" xml:space="preserve">
<value>Filter auf private Gruppensuchen anwenden</value>
</data>
<data name="RegularExpressionsToFilter" xml:space="preserve">
<value>zu filternde reguläre Ausdrücke</value>
</data>
<data name="Settings" xml:space="preserve">
<value>Einstellungen</value>
</data>
<data name="SubstringsToFilter" xml:space="preserve">
<value>zu filternde Zeichenketten</value>
</data>
</root>

View File

@ -1,32 +0,0 @@
<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="EnableCustomChatFilters" xml:space="preserve">
<value>Habilitar filtros de chat personalizados</value>
</data>
<data name="EnableCustomPartyFinderFilters" xml:space="preserve">
<value>Habilitar filtros del Party Finder personalizados</value>
</data>
<data name="FilterPrivatePfs" xml:space="preserve">
<value>Aplicar filtros a listados privados del Party Finder</value>
</data>
<data name="RegularExpressionsToFilter" xml:space="preserve">
<value>Expresiones regulares a filtrar</value>
</data>
<data name="Settings" xml:space="preserve">
<value>Ajustes de {0}</value>
</data>
<data name="SubstringsToFilter" xml:space="preserve">
<value>Subcadenas a filtrar</value>
</data>
</root>

View File

@ -1,32 +0,0 @@
<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="EnableCustomChatFilters" xml:space="preserve">
<value>Activer les filtres de discussion personnalisés</value>
</data>
<data name="EnableCustomPartyFinderFilters" xml:space="preserve">
<value>Activer les filtres de Recherche d'Équipe (PFs) 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="RegularExpressionsToFilter" xml:space="preserve">
<value>Expressions régulières à filtrer</value>
</data>
<data name="Settings" xml:space="preserve">
<value>Paramètres de {0}</value>
</data>
<data name="SubstringsToFilter" xml:space="preserve">
<value>Sous-chaînes (substrings) à filtrer</value>
</data>
</root>

View File

@ -1,32 +0,0 @@
<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="EnableCustomChatFilters" xml:space="preserve">
<value>カスタムチャットフィルタを有効化</value>
</data>
<data name="EnableCustomPartyFinderFilters" 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="Settings" xml:space="preserve">
<value>{0} 設定</value>
</data>
<data name="SubstringsToFilter" xml:space="preserve">
<value>フィルタするキーワード</value>
</data>
</root>

View File

@ -1,39 +0,0 @@
<?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="EnableCustomChatFilters" xml:space="preserve">
<value>Enable custom chat filters</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="Settings" xml:space="preserve">
<value>{0} settings</value>
</data>
</root>

View File

@ -1,32 +0,0 @@
<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="EnableCustomChatFilters" xml:space="preserve">
<value>启用自定义聊天消息过滤</value>
</data>
<data name="EnableCustomPartyFinderFilters" 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="Settings" xml:space="preserve">
<value>{0} 设置</value>
</data>
<data name="SubstringsToFilter" xml:space="preserve">
<value>过滤关键词</value>
</data>
</root>

View File

@ -1,32 +0,0 @@
<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="EnableCustomChatFilters" xml:space="preserve">
<value>啟用客製化聊天消息過濾</value>
</data>
<data name="EnableCustomPartyFinderFilters" 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="Settings" xml:space="preserve">
<value>{0} 設置</value>
</data>
<data name="SubstringsToFilter" xml:space="preserve">
<value>過濾關鍵詞</value>
</data>
</root>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -11,12 +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
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NoSoliciting.Lite", "NoSoliciting.Lite\NoSoliciting.Lite.csproj", "{46679548-E204-453B-AAAC-F31342071E03}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -35,18 +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
{46679548-E204-453B-AAAC-F31342071E03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{46679548-E204-453B-AAAC-F31342071E03}.Debug|Any CPU.Build.0 = Debug|Any CPU
{46679548-E204-453B-AAAC-F31342071E03}.Release|Any CPU.ActiveCfg = Release|Any CPU
{46679548-E204-453B-AAAC-F31342071E03}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -166,13 +166,10 @@
<data name="ReportModalTitle" xml:space="preserve">
<value>{0}に報告</value>
</data>
<data name="ReportModalWasNotFiltered" xml:space="preserve">
<value>具体的には、この内容はフィルタされていませんが本来フィルタリングされるべきだったと報告します。</value>
</data>
<data name="ReportModalHelp2" xml:space="preserve">
<value>NoSolicitingは英語のメッセージにのみ有効です。 他の言語でメッセージを報告しないでください。</value>
</data>
<data name="ReportModalCustom" xml:space="preserve">
<data name="ReportModalDisabledCustom" xml:space="preserve">
<value>カスタムフィルタでフィルタリングされたメッセージを報告することはできません。</value>
</data>
<data name="ReportModalReport" xml:space="preserve">
@ -256,9 +253,6 @@
<data name="ReportModalHelp1" xml:space="preserve">
<value>このメッセージを報告することで、開発者はこのメッセージが誤って分類されたことを知ることができます。</value>
</data>
<data name="ReportModalWasFiltered" xml:space="preserve">
<value>具体的には、フィルタされた内容は本来フィルタリングされるべきではなかったことを報告します。</value>
</data>
<data name="FreeCompanyCategory" xml:space="preserve">
<value>FC広告</value>
</data>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

83
icon.svg Executable file
View File

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

After

Width:  |  Height:  |  Size: 3.1 KiB