Compare commits

...

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

64 changed files with 3761 additions and 1276 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.3" 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.2"/>
<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;
}

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;
@ -105,10 +103,10 @@ namespace NoSoliciting {
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}");
}
}
@ -142,7 +140,7 @@ namespace NoSoliciting {
}
// only look at ml if message >= min words
if (!filter && this.Plugin.MlFilter != null && 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);
@ -167,7 +165,7 @@ namespace NoSoliciting {
this.Plugin.AddMessageHistory(history);
if (filter && this.Plugin.Config.LogFilteredChat) {
PluginLog.Log($"Filtered chat message ({history.FilterReason ?? "unknown"}): {text}");
Plugin.Log.Info($"Filtered chat message ({history.FilterReason ?? "unknown"}): {text}");
}
return filter;
@ -183,30 +181,34 @@ namespace NoSoliciting {
return (null, null);
}
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)) {
if (this.Plugin.Config.FilterHugeItemLevelPFs && listing.MinimumItemLevel > FilterUtil.MaxItemLevelAttainable(this.Plugin.DataManager)) {
return (null, "ilvl");
}
var desc = listing.Description.TextValue;
// step 2. check custom filters
if (this.Plugin.Config.CustomPFFilter && PartyFinder.MatchesCustomFilters(desc, this.Plugin.Config)) {
return (null, "custom");
}
// only look at ml for pfs >= min words
if (desc.Trim().Spacify().Split(' ').Length < MinWords) {
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, null);
return (category, Enum.GetName(category));
}
return (null, null);
}
private static int CountWords(string text) {
return text.Spacify().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Length;
}
}
}

View File

@ -1,9 +1,9 @@
using Dalamud.Data;
using Lumina.Excel.GeneratedSheets;
using Lumina.Excel.GeneratedSheets;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Dalamud.Plugin.Services;
using NoSoliciting.Ml;
namespace NoSoliciting {
@ -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;

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

@ -6,11 +6,14 @@ using System.Numerics;
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
@ -62,8 +65,9 @@ namespace NoSoliciting.Interface {
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;
}
@ -121,7 +125,7 @@ namespace NoSoliciting.Interface {
.Select(payload => payload.Text)
.FirstOrDefault() ?? "";
if (AddRow(message.Timestamp.ToString(CultureInfo.CurrentCulture), message.ChatType.Name(this.Plugin.Interface.Data), message.FilterReason ?? string.Empty, 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}");
}
@ -207,6 +211,7 @@ namespace NoSoliciting.Interface {
#region Modal
private MessageCategory? _reportCategory;
private bool _other;
/// <summary>
///
@ -218,12 +223,12 @@ namespace NoSoliciting.Interface {
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 false;
}
if (this._reportCategory == null) {
if (this._reportCategory == null && !this._other) {
if (message.Classification != null) {
this._reportCategory = message.Classification;
} else if (message.Classification == null && !message.Custom && !message.ItemLevel) {
@ -246,23 +251,24 @@ namespace NoSoliciting.Interface {
ImGui.TextUnformatted(Language.ReportModalSuggestedClassification);
ImGui.SetNextItemWidth(-1);
if (ImGui.BeginCombo($"##modal-classification-{message.Id}", this._reportCategory?.Name() ?? string.Empty)) {
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;
}
if (!ImGui.IsItemHovered()) {
continue;
}
ImGui.BeginTooltip();
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 24);
ImGui.TextUnformatted(category.Description());
ImGui.PopTextWrapPos();
ImGui.EndTooltip();
WrappedTooltip(category.Description());
}
if (ImGui.Selectable(Language.ReportModalClassificationOther)) {
this._reportCategory = null;
this._other = true;
}
WrappedTooltip(Language.ReportModalClassificationOtherDescription);
ImGui.EndCombo();
}
@ -287,7 +293,20 @@ namespace NoSoliciting.Interface {
errorText = Language.ReportModalDisabledSameClassification;
}
if (errorText != null) {
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(errorText);
ImGui.PopStyleColor();
@ -301,12 +320,12 @@ namespace NoSoliciting.Interface {
switch (status) {
case ReportStatus.Successful: {
var msg = Language.ReportToastSuccess;
this.Plugin.Interface.Framework.Gui.Toast.ShowNormal(string.Format(msg, message.Sender));
this.Plugin.ToastGui.ShowNormal(string.Format(msg, message.Sender));
break;
}
case ReportStatus.Failure: {
var msg = Language.ReportToastFailure;
this.Plugin.Interface.Framework.Gui.Toast.ShowError(string.Format(msg, message.Sender));
this.Plugin.ToastGui.ShowError(string.Format(msg, message.Sender));
break;
}
}
@ -351,6 +370,10 @@ namespace NoSoliciting.Interface {
ImGui.EndPopup();
if (closing) {
this._other = false;
}
return closing;
}
@ -383,6 +406,18 @@ namespace NoSoliciting.Interface {
return clicked;
}
private static void WrappedTooltip(string text) {
if (!ImGui.IsItemHovered()) {
return;
}
ImGui.BeginTooltip();
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 24);
ImGui.TextUnformatted(text);
ImGui.PopTextWrapPos();
ImGui.EndTooltip();
}
private void ReportMessage(Message message, string suggested) {
Task.Run(async () => await this.ReportMessageAsync(message, suggested));
}
@ -405,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,9 +3,9 @@ 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.Plugin.Services;
using Lumina.Excel.GeneratedSheets;
using NoSoliciting.Ml;
@ -88,7 +88,7 @@ namespace NoSoliciting {
? "custom"
: this.ItemLevel
? "ilvl"
: this.Classification?.ToModelName() ?? "unknown",
: (this.Classification ?? MessageCategory.Normal).ToModelName(),
SuggestedClassification = suggested,
};
@ -208,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";
@ -220,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,6 +41,7 @@ namespace NoSoliciting.Ml {
"STATIC" => MessageCategory.Static,
"COMMUNITY" => MessageCategory.Community,
"STATIC_SUB" => MessageCategory.StaticSub,
"FLUFF" => MessageCategory.Fluff,
_ => null,
};
@ -54,6 +57,7 @@ namespace NoSoliciting.Ml {
"Static recruitment" => MessageCategory.Static,
"Community ads" => MessageCategory.Community,
"Static substitutes" => MessageCategory.StaticSub,
"Fluff" => MessageCategory.Fluff,
_ => null,
};
#endif
@ -69,6 +73,7 @@ namespace NoSoliciting.Ml {
MessageCategory.Static => "STATIC",
MessageCategory.Community => "COMMUNITY",
MessageCategory.StaticSub => "STATIC_SUB",
MessageCategory.Fluff => "FLUFF",
_ => throw new ArgumentException("Invalid category", nameof(category)),
};
@ -83,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)),
};
@ -97,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.1.0</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.3" PrivateAssets="all"/>
<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)) {

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>
@ -519,6 +555,15 @@ namespace NoSoliciting.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Go to custom filters.
/// </summary>
internal static string ReportModalGoToCustomButton {
get {
return ResourceManager.GetString("ReportModalGoToCustomButton", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reporting this message will let the developer know that you think this message was incorrectly classified..
/// </summary>

View File

@ -307,4 +307,13 @@
<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

@ -261,4 +261,13 @@
<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

@ -289,4 +289,31 @@
<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

@ -267,4 +267,19 @@
<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

@ -307,4 +307,13 @@
<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

@ -307,4 +307,13 @@
<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