Compare commits

...

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

67 changed files with 12814 additions and 2626 deletions

37
.build.yml Normal file
View File

@ -0,0 +1,37 @@
image: fedora/latest
packages:
- dotnet
- wget
- unzip
sources:
- https://git.sr.ht/~jkcclemens/NoSoliciting
secrets:
- 92fe0dd0-db40-41e0-903a-a18489f75548
tasks:
- 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: |
cd NoSoliciting/NoSoliciting.Trainer
./bin/Release/net5/NoSoliciting.Trainer test data.csv
- create-model: |
cd NoSoliciting/NoSoliciting.Trainer
./bin/Release/net5/NoSoliciting.Trainer create-model data.csv
- update-model: |
cd NoSoliciting
LAST_COMMIT=$(ssh -o 'StrictHostKeyChecking=accept-new' actions@warm.kitteh.space model commit)
if [ "$LAST_COMMIT" = 'None' ] || git diff-tree --no-commit-id --name-only -r "$LAST_COMMIT"..HEAD | grep '^NoSoliciting\.Trainer/data\.csv'; then
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

View File

@ -1,203 +0,0 @@
# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true
# C# files
[*.{cs,json}]
#### Core EditorConfig Options ####
# Indentation and spacing
indent_size = 4
indent_style = space
tab_width = 4
# New line preferences
end_of_line = lf
insert_final_newline = true
[*.cs]
#### .NET Coding Conventions ####
# Organize usings
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false
file_header_template = unset
# this. and Me. preferences
dotnet_style_qualification_for_event = true:silent
dotnet_style_qualification_for_field = true:silent
dotnet_style_qualification_for_method = true:silent
dotnet_style_qualification_for_property = true:silent
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
# Expression-level preferences
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_object_initializer = true:suggestion
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_simplified_interpolation = true:suggestion
# Field preferences
dotnet_style_readonly_field = true:suggestion
# Parameter preferences
dotnet_code_quality_unused_parameters = all:suggestion
#### C# Coding Conventions ####
# var preferences
csharp_style_var_elsewhere = false:silent
csharp_style_var_for_built_in_types = false:silent
csharp_style_var_when_type_is_apparent = false:silent
# Expression-bodied members
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_prefer_switch_expression = true:suggestion
# Null-checking preferences
csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
csharp_prefer_static_local_function = true:suggestion
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
# Code-block preferences
csharp_prefer_braces = true:error
csharp_prefer_simple_using_statement = true:suggestion
# Expression-level preferences
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_range_operator = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:silent
#### C# Formatting Rules ####
# New line preferences
csharp_new_line_before_catch = false
csharp_new_line_before_else = false
csharp_new_line_before_finally = false
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = none
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case

2
.gitattributes vendored
View File

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

155
NoSoliciting.Interface/Data.cs Executable file
View File

@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.ML.Data;
using Microsoft.ML.Transforms;
namespace NoSoliciting.Interface {
[SuppressMessage("ReSharper", "UnusedMember.Global")]
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public class Data {
[LoadColumn(0)]
public string? Category { get; set; }
[LoadColumn(1)]
public uint Channel { get; set; }
[LoadColumn(2)]
public string Message { get; set; } = null!;
public Data() {
}
public Data(ushort channel, string message) {
this.Channel = channel;
this.Message = message;
}
#region normalisation
[CustomMappingFactoryAttribute("Normalise")]
[SuppressMessage("ReSharper", "UnusedType.Global")]
public class Normalise : CustomMappingFactory<Data, Normalise.Normalised> {
public override Action<Data, Normalised> GetMapping() {
return Convert;
}
private static void Convert(Data data, Normalised normalised) {
normalised.NormalisedMessage = NoSolUtil.Normalise(data.Message, true);
}
public class Normalised {
public string? NormalisedMessage { get; set; }
}
}
#endregion
#region computed
[CustomMappingFactoryAttribute("Compute")]
[SuppressMessage("ReSharper", "UnusedType.Global")]
public class ComputeContext : CustomMappingFactory<Data, Computed> {
private Dictionary<string, float> Weights { get; }
public ComputeContext() {
this.Weights = new Dictionary<string, float>();
}
public ComputeContext(Dictionary<string, float> weights) {
this.Weights = weights;
}
private void Compute(Data data, Computed computed) {
data.Compute(computed, this.Weights);
}
public override Action<Data, Computed> GetMapping() {
return this.Compute;
}
}
private static readonly Regex[] PlotWords = {
new(@"\bplot\b", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new(@"\bapartment\b", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new(@"\bapt\b", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new(@"p.{0,2}\d", RegexOptions.Compiled | RegexOptions.IgnoreCase),
};
private static readonly Regex[] WardWords = {
new(@"\bward\b", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new(@"w.{0,2}\d", RegexOptions.Compiled | RegexOptions.IgnoreCase),
};
private static readonly Regex NumbersRegex = new(@"\d{1,2}.{0,2}\d{1,2}", RegexOptions.Compiled);
private static readonly string[] TradeWords = {
"B> ",
"S> ",
"buy",
"sell",
"WTB",
"WTS",
};
private static readonly Regex SketchUrlRegex = new(@"\.com-\w+\.\w+", RegexOptions.IgnoreCase | RegexOptions.Compiled);
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
public class Computed {
public float Weight { get; set; } = 1;
public bool PartyFinder { get; set; }
public bool Shout { get; set; }
public bool ContainsWard { get; set; }
public bool ContainsPlot { get; set; }
public bool ContainsHousingNumbers { get; set; }
public bool ContainsTradeWords { get; set; }
public bool ContainsSketchUrl { get; set; }
}
private void Compute(Computed output, IReadOnlyDictionary<string, float> weights) {
if (this.Category != null && weights.TryGetValue(this.Category, out var weight)) {
output.Weight = weight;
}
var normalised = NoSolUtil.Normalise(this.Message);
output.PartyFinder = this.Channel == 0;
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);
output.ContainsTradeWords = TradeWords.Any(word => normalised.ContainsIgnoreCase(word));
output.ContainsSketchUrl = SketchUrlRegex.IsMatch(normalised);
}
#endregion
}
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public class Prediction {
[ColumnName("PredictedLabel")]
public string Category { get; set; } = "UNKNOWN";
[ColumnName("Score")]
public float[] Probabilities { get; set; } = Array.Empty<float>();
}
internal static class Ext {
public static bool ContainsIgnoreCase(this string haystack, string needle) {
return CultureInfo.InvariantCulture.CompareInfo.IndexOf(haystack, needle, CompareOptions.IgnoreCase) >= 0;
}
}
}

View File

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

View File

@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NoSoliciting.Interface {
public static class NoSolUtil {
private static readonly Dictionary<char, string> Replacements = new() {
// numerals
['\ue055'] = "1",
['\ue056'] = "2",
['\ue057'] = "3",
['\ue058'] = "4",
['\ue059'] = "5",
['\ue099'] = "10",
['\ue09a'] = "11",
['\ue09b'] = "12",
['\ue09c'] = "13",
['\ue09d'] = "14",
['\ue09e'] = "15",
['\ue09f'] = "16",
['\ue0a0'] = "17",
['\ue0a1'] = "18",
['\ue0a2'] = "19",
['\ue0a3'] = "20",
['\ue0a4'] = "21",
['\ue0a5'] = "22",
['\ue0a6'] = "23",
['\ue0a7'] = "24",
['\ue0a8'] = "25",
['\ue0a9'] = "26",
['\ue0aa'] = "27",
['\ue0ab'] = "28",
['\ue0ac'] = "29",
['\ue0ad'] = "30",
['\ue0ae'] = "31",
// symbols
['\ue0af'] = "+",
['\ue070'] = "?",
// letters in other sets
['\ue022'] = "A",
['\ue024'] = "_A",
['\ue0b0'] = "E",
};
private const char LowestReplacement = '\ue022';
private static readonly char[] SpaceSymbols = {
'/', '|',
'(', ')',
'[', ']',
'{', '}',
'<', '>',
'=', '+',
'.', ',', '?', '!',
'~', '-',
};
public static string Spacify(this string input) {
return SpaceSymbols.Aggregate(input, (current, sym) => current.Replace(sym, ' '));
}
public static string Normalise(string input, bool spacify = false) {
if (input == null) {
throw new ArgumentNullException(nameof(input), "input cannot be null");
}
// replace ffxiv private use chars
var builder = new StringBuilder(input.Length);
foreach (var c in input) {
if (c < LowestReplacement) {
goto AppendNormal;
}
// alphabet
if (c >= 0xe071 && c <= 0xe08a) {
builder.Append((char) (c - 0xe030));
continue;
}
// 0 to 9
if (c >= 0xe060 && c <= 0xe069) {
builder.Append((char) (c - 0xe030));
continue;
}
// 1 to 9
if (c >= 0xe0b1 && c <= 0xe0b9) {
builder.Append((char) (c - 0xe080));
continue;
}
// 1 to 9 again
if (c >= 0xe090 && c <= 0xe098) {
builder.Append((char) (c - 0xe05f));
continue;
}
// replacements in map
if (Replacements.TryGetValue(c, out var rep)) {
builder.Append(rep);
continue;
}
AppendNormal:
builder.Append(c);
}
input = builder.ToString();
// NFKD unicode normalisation
var normalised = input.Normalize(NormalizationForm.FormKD);
// replace several symbols with spaces instead
return spacify ? normalised.Spacify() : normalised;
}
}
}

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.ML" Version="2.0.1" />
</ItemGroup>
</Project>

View File

@ -1,28 +0,0 @@
using Xunit;
namespace NoSoliciting.Tests.DefinitionsTests.Chat {
public class RMT : DefinitionTest {
public RMT(DefinitionsFixture fixture) {
this.Def = fixture.defs.Chat["rmt"];
}
public static object[][] DataPositives => DefUtils.DataFromMessages(new TestMessage[] {
new TestMessage(ChatType.Shout, "FF14Mog.com selling cheap Mog Station Redeem Code,Dirndl's Attire $8.99, Chocobo Carriage $14.39 ,Use 5Off Code:FF5"),
new TestMessage(ChatType.Say, "----[4KGOLD.COM]----[Best Buy Gil Store]----[Cheapest Price]-----[4KGOLD.COM]---[Ultrafast Deliveryin 10 Mins]--[6OFF Code;LOVE]---359qe"),
new TestMessage(ChatType.Shout, "【 PVP◇NK.℃ O M 、◇ = BA 】5分納品ジル480-500HQセット希望の園エデン (野蛮)全部強奪!安い&安全保障【コード714、5OFF】!!!-ssrum"),
new TestMessage(ChatType.Shout, "【 PV■NK.℃ O M 、■ = PBA 】5分納品ジル480-500HQセット希望の園エデン (野蛮)全部強奪!安い&安全保障【コード714、5OFF】!!!-cfjyf"),
});
//public static object[][] DataNegatives => DefUtils.DataFromMessages(new TestMessage[] {
//});
[Theory]
[MemberData(nameof(DataPositives))]
public void Positives(TestMessage message) => this.Check(message, CheckType.Positive);
//[Theory]
//[MemberData(nameof(DataNegatives))]
//public void Negatives(TestMessage message) => this.Check(message, CheckType.Negative);
}
}

View File

@ -1,33 +0,0 @@
using Xunit;
namespace NoSoliciting.Tests.DefinitionsTests {
public abstract class DefinitionTest : IClassFixture<DefinitionsFixture> {
protected Definition Def { get; set; }
protected void Check(string message, CheckType type) => this.Def.Check(message, type);
protected void Check(TestMessage message, CheckType type) => this.Def.Check(message, type);
// an assortment of normal party finders/messages to make sure no crazy false positives are happening
public static object[][] DataGlobalNegatives = DefUtils.DataFromMessages(new TestMessage[] {
// party finders
new TestMessage("Static LF the listed roles in prep for 5.4 || T/W/Th 7:30-9:30 AM EST || Discord: Mia#0585"),
new TestMessage("Looking to learn second half of the fight, and then if all goes well farm!!"),
new TestMessage("T/N H/S Tethers DPS Towers KB prevention dps uptime, Partners for the rest no salt farm party"),
new TestMessage("another day another camp on the finder! u know the drill by now bahaprog or nael clean up "),
new TestMessage("♡Need help with anything?♥ Chat, hangout, and can craft! Levekits, gear, & more!☂ Pop in, take a seat, & maybe I can help! :)"),
new TestMessage("Unsync clear for my alt. Come for WT, bonus poetics, or out of the goodness of your heart."),
// messages
new TestMessage(ChatType.FreeCompany, "forgot to leave a commendation, I forget this sometimes"),
new TestMessage(ChatType.StandardEmote, "Anri Valmet laughs at Kyoya Kazuki."),
new TestMessage(ChatType.Shout, "Need a lvl 80 crafter? Buy a leve kit and get your crafter today! PST for price info :D"),
new TestMessage(ChatType.Yell, "Wedding starting at 3:00pm est, pst for an invite. You will get pets. pst"),
new TestMessage(ChatType.Shout, "LOCAL ShB Train! Spend your nuts & seals. Lets go kill things and be savages. This hunt is brought to you by ★VIII - The Double Infinity Free Company! ♥∞"),
new TestMessage(ChatType.Shout, "☆☆Shadowbringers Hunt Train • A Rank☆☆ Starting at 《Instance》 → Lakeland ( 27.6 , 15.3 )"),
});
[Theory]
[MemberData(nameof(DataGlobalNegatives))]
public void GlobalNegatives(TestMessage msg) => this.Check(msg, CheckType.Negative);
}
}

View File

@ -1,68 +0,0 @@
using Dalamud.Game.Chat;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Xunit;
namespace NoSoliciting.Tests.DefinitionsTests {
public class DefUtils {
public static object[][] DataFromStrings(string[] strings) => strings.Select(s => new object[] { s }).ToArray();
public static object[][] DataFromMessages(TestMessage[] messages) => messages.Select(m => new object[] { m }).ToArray();
}
public class DefinitionsFixture {
internal readonly Definitions defs;
public DefinitionsFixture() {
this.defs = Definitions.Load(File.ReadAllText("../../../NoSoliciting/definitions.yaml"));
var allDefs = defs.Chat
.Concat(defs.PartyFinder)
.Concat(defs.Global);
foreach (KeyValuePair<string, Definition> entry in allDefs) {
entry.Value.Initialise(entry.Key);
}
}
}
public class TestMessage {
internal ChatType channel;
internal string content;
public TestMessage(string content) : this(ChatType.None, content) { }
public TestMessage(ChatType channel, string content) {
this.content = content;
this.channel = channel;
}
public override string ToString() {
string name = channel == ChatType.None ? "PF" : channel.ToString();
return $"[{name}] {this.content}";
}
}
public enum CheckType {
Positive,
Negative,
}
internal static class DefinitionExt {
internal static void Check(this Definition def, string message, CheckType type) {
TestMessage testMsg = new TestMessage(message);
def.Check(testMsg, type);
}
internal static void Check(this Definition def, TestMessage message, CheckType type) {
switch (type) {
case CheckType.Positive:
Assert.True(def.Matches((XivChatType)message.channel, message.content), message.content);
break;
case CheckType.Negative:
Assert.False(def.Matches((XivChatType)message.channel, message.content), message.content);
break;
}
}
}
}

View File

@ -1,51 +0,0 @@
using Xunit;
namespace NoSoliciting.Tests.DefinitionsTests.Global {
public class FreeCompany : DefinitionTest {
public FreeCompany(DefinitionsFixture fixture) {
this.Def = fixture.defs.Global["free_company"];
}
public static object[][] DataPositives => DefUtils.DataFromMessages(new TestMessage[] {
// chat
new TestMessage(ChatType.Shout, "PhD: Phantasy Degree is a Rank 30 FC with a Large plot, 24/7 FC/EXP Buffs. There are NO Level Restrictions to join, we take new and old players. Ask to join or apply."),
new TestMessage(ChatType.Shout, "Wind up fc is a small company looking for new/seasoned adventures to join! If you need help with the msq/clear the latest raid well do our best to help! Send a tell/app to join"),
new TestMessage(ChatType.Shout, "Porxie Menace is recruiting! Veterans & newbies are welcome to join our plan to take over the world... with a snort! Raiding, mapping, eureka, potd/hoh, pvp and much more!!"),
new TestMessage(ChatType.Shout, "Looking for an 18+ FC that enjoys all aspects of the game? <Lusty> (Rank 30) is here for you. From RP to Hunts; Whether you're new or a vet, we're a tight-knit group offering assistance to those who'd like it. We are a different kind of Free Company than what you'd expect. We have 24/7 buffs, discord, and of course are a judgement free LGBTQA+ friendly group ;) just click on my name and send a tell to get started!"),
new TestMessage(ChatType.Shout, "join my free company. we don't got much, but we could have you. which probably also isn't much. join anyway. maps on wednesdays, discord if you feel like it, new player friendly. Ask about a house tour. <<GECKO>> /tell"),
new TestMessage(ChatType.Shout, "♡♡ PomHub <Pom> is recruiting new and veteran players to join our growing family! | 18 + | LGBTQ Friendly | Housing | Daily Buffs | Events | Discord |"),
new TestMessage(ChatType.Shout, "Have you been running alone looking for the sweeter things on Faerie? Wanting to find a rowdy, yet caring, bunch of friends? Well, look no further than <CANDY>! we are a social rank 30 FC, friendly and willing to help new or returning players! Have a discord and Large House at Shiro! 18+, RP friendly, LGBTQ friendly as well. Send a tell and join today :hearts:"),
new TestMessage(ChatType.Shout, "Need an FC? Why not come home to Amaurot? A small group with weekly events, active discord, players primarily on 3 - 11 pm EST /tell me for more info!"),
new TestMessage(ChatType.Shout, "<Memoria> is a small, social FC looking for new or active players who regularly want to socialize and do content together. If this sounds like something youre interested in, send me a /tell or apply via an application"),
new TestMessage(ChatType.Shout, @"Paw Paw Grrr is currently recruiting! We are friendly bunch with constant mood for weird ideas! New? Veteran? Crafter? Doesn't matter! \tell me or Ophelia Shepard for inv :)"),
new TestMessage(ChatType.Shout, "We have a beautiful mansion in the Mist with all comforts, a fleet farming for us and +20% battle exp buff on Saturdays! Send me a /tell if you want more info or a invite!"),
new TestMessage(ChatType.Shout, "Nothing is a small and cozy FC looking for members! If emptiness is endless, then everything rests in nothingness. DM me to join on our endless journey!"),
new TestMessage(ChatType.Shout, "Tired of wandering through the game a lonely weeb? Want Senpai to notice you?! Fat Chocobo FC is now recruiting! Top Ranked FC on Exodus & Worldwide, 6 yrs+ running strong!"),
// party finder
new TestMessage("FC recruiting new and experienced players. Interested? Join party, send me a /tell or stop by the FC house for more information. "),
new TestMessage(@"Golden Crow [FC] offering new and old players a save place to hang and have fun with - join the weebs now \o/ /pm for info "),
new TestMessage("«ToC» recruiting active members. Join the party, send me a /Tell or stop by the FC house (Goblet P13, W19) for more information."),
new TestMessage("FC Toxic looking for new memeber, Fc house, master crafter and gatherer, buffs available for new players as well as end game"),
new TestMessage("<Panic> is recruiting! We're a slowly growing fc that would appreciate some new faces. /tell for more info or an inv <3"),
new TestMessage("Free Company | Fallen Angel | is welcoming new players on Gilgamesh"),
new TestMessage("[FC recruitment] Small/New FC looking for more members to join us. New and experienced welcomed. Send tell if interested!"),
new TestMessage("<WICH> FC is now recruiting!! we are looking for all types of players. PM me for more info :slight_smile:"),
new TestMessage("Rhotano Mercantile <RM-RP> (18+ RP FC) is recruiting neutral and evil characters for dark RP, enquire within! https://rm-rp.carrd.co/"),
});
public static object[][] DataNegatives => DefUtils.DataFromMessages(new TestMessage[] {
new TestMessage("Static recruit. Not hardcore. Discord needed. tues-thurs 11:30pmEST. Join if you have questions."),
new TestMessage("LF new LGBT friends to chill with in Eorzea! Join up, let's chat, and hang out. I have discord as well :)"),
new TestMessage("(Bored)Lovely Au Ra woman [Famfrit] available to be RP girlfriend/wife for long term or short term. Send tell if in need."),
});
[Theory]
[MemberData(nameof(DataPositives))]
public void Positives(TestMessage message) => this.Check(message, CheckType.Positive);
[Theory]
[MemberData(nameof(DataNegatives))]
public void Negatives(TestMessage message) => this.Check(message, CheckType.Negative);
}
}

View File

@ -1,40 +0,0 @@
using Xunit;
namespace NoSoliciting.Tests.DefinitionsTests.PartyFinder {
public class RMT : DefinitionTest {
public RMT(DefinitionsFixture fixture) {
this.Def = fixture.defs.PartyFinder["rmt"];
}
public static object[][] DataPositives => DefUtils.DataFromStrings(new string[] {
"「Best Prices」《 Shiva Unreal ★ Warrior of Light ★ Ultimates ★ Eden's Verse i500/i505",
"「 MINMAXØ 」 SALES ≪ ❶ Savage 一 ❷ Ultimates 一 ❸ Mounts Etc. ≫ World #1 teams, instant delivery. Discord → azrael#6447",
"「」™️ Found it Cheaper? We will beat it! $elling EdenVerse, BLU, Ultimates, Primals, Discord: Valentine#5943",
"「BiS」Selling 》Sac EX, Unreal Shiva 》Savages, Ultimates 》Mounts & More | Price Match Guarantee | Discord→Present#0148",
"「 Selling 」 ♥ Raids Trials Ultimates BLU ♥ Fast Delivery ♥ Price Match ♥ Discord→ Shion#5162",
"♥ SELLING ♥ Shiva Unreal / WoL XM - Eden Savage (☆ i500/i505 ☆) - TEA/UwU/UCoB - Old raids and + | Discord add me: gin#5147",
"「MinmaxØ」 Offering any  →  →  → & more, Instant delivery. Discord→ Minmax#0001",
"[Viet Rice Farmers] is selling All Content Add on Discord Heyitsjowey#2703",
"【Selling ー All the content. You want something? We got this!ーHQ teams and speed at your service!】Discord : Victoriam#4716",
"「」 Guaranteed results! World #1 raiders! ≪ ❶ Savage 一 ❷ Ultimates 一 ❸ Mounts ≫ Discord → ashlar#6021",
});
public static object[][] DataNegatives => DefUtils.DataFromStrings(new string[] {
"Doing Art commission of your charactet with good price! more info add me on discord: d0uglaz#7409 ♥",
"Selling HQ 490 DoH/DoL sets, just in time for the Ishgard restoration project. Cheaper than MB, Whipser or join for info.",
"Looking to sell medium odder otter walls(2mil) join or tell.",
@"Selling 1x Eldthurs Horn for 8mil. Skip MB taxes \o/ Join if interested",
"Selling Phanta mats, let me know what you want and I can deliver it to you. 2k for 300k. Join the party don't /tell please",
"#1 NA WHOLESALER! NEO SET-660K! i490 SET-1.5M(w/DISCOUNT OPTIONS)! BUYING PHANTAS ANY AMOUNT 3K! JOIN PARTY!!!",
"1/2 chest Lootmaster for sam weap. If weap drops then coffer is rolled on. KB uptime. Ilya. If you just want a page or to help.",
});
[Theory]
[MemberData(nameof(DataPositives))]
public void Positives(string msg) => this.Check(msg, CheckType.Positive);
[Theory]
[MemberData(nameof(DataNegatives))]
public void Negatives(string msg) => this.Check(msg, CheckType.Negative);
}
}

View File

@ -1,51 +0,0 @@
using Xunit;
namespace NoSoliciting.Tests.DefinitionsTests.Global {
public class Roleplay : DefinitionTest {
public Roleplay(DefinitionsFixture fixture) {
this.Def = fixture.defs.Global["roleplay"];
}
public static object[][] DataPositives => DefUtils.DataFromStrings(new string[] {
"If you're looking for something to do, come find monthly contests, 4 weekly RP events and more! discord.gg/LuckySevens",
"Thorned Dragon Cosplay Event Sunday 9/6! Website for more info: tdevent.carrd.co Discord: discord.gg/thorneddragonclub",
"[Rp] The Viridian Orchid is a non-profit companion's den seeking those interested in joining our community! <viriorchid.carrd.co>",
"[RP] The Thonrned Dragon Cosplay Contest entry closes tonight at midnight est. Entry is free. Full details at [discord.gg/48R4RpP]",
"● The Pearl ● a Victorian Brothel opening Tuesday! Applications closing tonight, get yours in now! [ thepearlxiv.carrd.co ]",
"[RP] Looking for adventure? Bounties, work, odd jobs? Join Bounty Call! More info in our discord: https://discord.gg/SnjZWRf",
"[RP/+18RP] - The HROTHEL - Bathhouse & Bar [LGBTQ+Friendly]\n[Fam. W.5 - P.50 Gridania][discord.io/hrothel] Hrothgar Operated",
"( RP ) Wonderlust is a new club on Midgar! We are hiring! We also want our customers to join! https://discord.gg/Mn3QNn",
"Twine Trolley Hostel is now open in Mist, 3rd Ward (Exodus) apt#88. Please be respectful of other guests and enjoy your stay.",
"DLITE Is OPEN! Come grab a courtesan and relax in our lounge and let our expert staff see to your every whim, SIren, gob W19,43",
"If you're looking for something to do, come find monthly contests, 4 weekly RP events and more! discord.gg/LuckySevens",
"[18+]Need to get your RP/ERP fix in? Wishing to become or buy a courtesan? C'mere to Touch Fluffy Tail @ discord.gg/fCS8Zng",
"MR casino venue looking for greeters and courtesans. Join pt if interested.",
"Have a venue? Come plug yourself while checking us out! We're The - !! - https://discord.gg/S7BUVKh",
"Looking for a good time? Come to the Sapphire for frat/sorority night! 5-10EST Gilga Mist W2 P9",
"Lucky Sevens - 18+ RP community to find partners, advertise your venue, post screenies and enjoy FF14! discord.gg/LuckySevens",
"{RP} New venue coming to Siren! Join for more information ahead of our grand opening! https://www.discord.gg/nqJtQSD",
"Open RP night with the Lucky Sevens at The Gold Court, Hyperions Steps of Thal X: 11, Y: 11 - discord.gg/LuckySevens",
"LUST the original Maid Harem- welcome guests into our discord! https://discord.gg/wczaT6k ERP discord 18+",
"Lucky Sevens - Primal's largest and most active RP discord - Welcomes you! discord.gg/LuckySevens",
"SPAGHETTI WESTERN NIGHT AT SPAGET 2112! Free cowboy hats! Whiskey provided by the Whiskey Tears! Gilga Mist W21 P12",
"(RP) Adonis Blue invites you to frolick and play within our land of enchantment. All are welcome to make merry.Lamia Gob W14 L4.",
"[18+RP] Teraflare is having a VIP sleepover at 1am EST!! Talk to management, and get your VIP access to join our shenanigans!!",
"[RP] Karaoke Night at NRAID HQ! Sign up for a chance to perform on our stage! Spectators welcome! https://discord.gg/ZhgqEqf",
" Coeurlseye Bazaar Night! 9 PM EST - Vendors, food, unique trinkets and more! Learn more at tinyurl.com/CBazaar",
"[RP] [Siren] The Black Flower Lounge is looking for new staff! waiter, bartender and escort positions open! join for info!",
"The Starlight Room is hosting a gothic masquerade tonight from 8PM EST til 1AM EST. The Goblet War 10 Plot 19 Sargatanas!",
});
public static object[][] DataNegatives => DefUtils.DataFromStrings(new string[] {
"«ToC» recruiting active members. Join the party, send me a /Tell or stop by the FC house (Goblet P13, W19) for more information.",
});
[Theory]
[MemberData(nameof(DataPositives))]
public void Positives(string msg) => this.Check(msg, CheckType.Positive);
[Theory]
[MemberData(nameof(DataNegatives))]
public void Negatives(string msg) => this.Check(msg, CheckType.Negative);
}
}

View File

@ -1,97 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\xunit.runner.visualstudio.2.4.3\build\net452\xunit.runner.visualstudio.props" Condition="Exists('..\packages\xunit.runner.visualstudio.2.4.3\build\net452\xunit.runner.visualstudio.props')" />
<Import Project="..\packages\xunit.core.2.4.1\build\xunit.core.props" Condition="Exists('..\packages\xunit.core.2.4.1\build\xunit.core.props')" />
<Import Project="..\packages\MSTest.TestAdapter.2.1.1\build\net45\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.2.1.1\build\net45\MSTest.TestAdapter.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{1962D91F-543A-4214-88FD-788BB7ACECE3}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>NoSoliciting.Tests</RootNamespace>
<AssemblyName>NoSoliciting.Tests</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Dalamud">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\Dalamud.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll</HintPath>
</Reference>
<Reference Include="xunit.assert, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll</HintPath>
</Reference>
<Reference Include="xunit.core, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll</HintPath>
</Reference>
<Reference Include="xunit.execution.desktop, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="DefinitionsTests\Global.FreeCompany.cs" />
<Compile Include="DefinitionsTests\Chat.RMT.cs" />
<Compile Include="DefinitionsTests\DefinitionsTests.cs" />
<Compile Include="DefinitionsTests\DefinitionTest.cs" />
<Compile Include="DefinitionsTests\PartyFinder.RMT.cs" />
<Compile Include="DefinitionsTests\Roleplay.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NoSoliciting\NoSoliciting.csproj">
<Project>{e4c12987-9064-4788-8783-be418b2c0142}</Project>
<Name>NoSoliciting</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\packages\xunit.analyzers.0.10.0\analyzers\dotnet\cs\xunit.analyzers.dll" />
</ItemGroup>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.2.1.1\build\net45\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.1.1\build\net45\MSTest.TestAdapter.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.2.1.1\build\net45\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.1.1\build\net45\MSTest.TestAdapter.targets'))" />
<Error Condition="!Exists('..\packages\xunit.core.2.4.1\build\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.4.1\build\xunit.core.props'))" />
<Error Condition="!Exists('..\packages\xunit.core.2.4.1\build\xunit.core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.4.1\build\xunit.core.targets'))" />
<Error Condition="!Exists('..\packages\xunit.runner.visualstudio.2.4.3\build\net452\xunit.runner.visualstudio.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.runner.visualstudio.2.4.3\build\net452\xunit.runner.visualstudio.props'))" />
</Target>
<Import Project="..\packages\MSTest.TestAdapter.2.1.1\build\net45\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.2.1.1\build\net45\MSTest.TestAdapter.targets')" />
<Import Project="..\packages\xunit.core.2.4.1\build\xunit.core.targets" Condition="Exists('..\packages\xunit.core.2.4.1\build\xunit.core.targets')" />
</Project>

View File

@ -1,20 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("NoSoliciting.Tests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("NoSoliciting.Tests")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("1962d91f-543a-4214-88fd-788bb7acece3")]
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="MSTest.TestAdapter" version="2.1.1" targetFramework="net48" />
<package id="MSTest.TestFramework" version="2.1.1" targetFramework="net48" />
<package id="xunit" version="2.4.1" targetFramework="net48" />
<package id="xunit.abstractions" version="2.0.3" targetFramework="net48" />
<package id="xunit.analyzers" version="0.10.0" targetFramework="net48" />
<package id="xunit.assert" version="2.4.1" targetFramework="net48" />
<package id="xunit.core" version="2.4.1" targetFramework="net48" />
<package id="xunit.extensibility.core" version="2.4.1" targetFramework="net48" />
<package id="xunit.extensibility.execution" version="2.4.1" targetFramework="net48" />
<package id="xunit.runner.visualstudio" version="2.4.3" targetFramework="net48" developmentDependency="true" />
</packages>

1
NoSoliciting.Trainer/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
model.zip

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<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"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,273 @@
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 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",
"http",
"https",
"18",
"come",
"join",
"blu",
"mounts",
"ffxiv",
};
private enum Mode {
Test,
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) {
var mode = args[0] switch {
"test" => Mode.Test,
"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) {
path = args[1];
}
var parentDir = Directory.GetParent(path);
if (parentDir == null) {
throw new ArgumentException("data.csv did not have a parent directory");
}
var ctx = new MLContext(1);
List<Data> records;
using (var fileStream = new FileStream(path, FileMode.Open)) {
using var stream = new StreamReader(fileStream);
using var csv = new CsvReader(stream, new CsvConfiguration(CultureInfo.InvariantCulture) {
HeaderValidated = null,
});
records = csv
.GetRecords<Data>()
.Select(rec => {
rec.Message = rec.Message
.Replace("", "") // auto-translate start
.Replace("", "") // auto-translate end
.Replace("\r\n", " ")
.Replace("\r", " ")
.Replace("\n", " ");
return rec;
})
.OrderBy(rec => rec.Category)
.ThenBy(rec => rec.Channel)
.ThenBy(rec => rec.Message)
.ToList();
}
using (var fileStream = new FileStream(path, FileMode.Create)) {
using var stream = new StreamWriter(fileStream);
using var csv = new CsvWriter(stream, new CsvConfiguration(CultureInfo.InvariantCulture) {
NewLine = "\n",
});
csv.WriteRecords(records);
}
var classes = new Dictionary<string, uint>();
foreach (var record in records) {
// keep track of how many message of each category we have
if (!classes.ContainsKey(record.Category!)) {
classes[record.Category] = 0;
}
classes[record.Category] += 1;
}
// calculate class weights
var weights = new Dictionary<string, float>();
foreach (var (category, count) in classes) {
var nSamples = (float) records.Count;
var nClasses = (float) classes.Count;
var nSamplesJ = (float) count;
var w = nSamples / (nClasses * nSamplesJ);
weights[category] = w;
}
var df = ctx.Data.LoadFromEnumerable(records);
var ttd = ctx.Data.TrainTestSplit(df, 0.2, seed: 1);
var compute = new Data.ComputeContext(weights);
var normalise = new Data.Normalise();
ctx.ComponentCatalog.RegisterAssembly(typeof(Data).Assembly);
var pipeline = ctx.Transforms.Conversion.MapValueToKey("Label", nameof(Data.Category))
.Append(ctx.Transforms.CustomMapping(compute.GetMapping(), "Compute"))
.Append(ctx.Transforms.CustomMapping(normalise.GetMapping(), "Normalise"))
.Append(ctx.Transforms.Text.NormalizeText("MsgNormal", nameof(Data.Normalise.Normalised.NormalisedMessage), keepPunctuations: false, keepNumbers: false))
.Append(ctx.Transforms.Text.TokenizeIntoWords("MsgTokens", "MsgNormal"))
.Append(ctx.Transforms.Text.RemoveDefaultStopWords("MsgNoDefStop", "MsgTokens"))
.Append(ctx.Transforms.Text.RemoveStopWords("MsgNoStop", "MsgNoDefStop", StopWords))
.Append(ctx.Transforms.Conversion.MapValueToKey("MsgKey", "MsgNoStop"))
.Append(ctx.Transforms.Text.ProduceNgrams("MsgNgrams", "MsgKey", weighting: NgramExtractingEstimator.WeightingCriteria.Tf))
.Append(ctx.Transforms.NormalizeLpNorm("FeaturisedMessage", "MsgNgrams"))
.Append(ctx.Transforms.Conversion.ConvertType("CPartyFinder", nameof(Data.Computed.PartyFinder)))
.Append(ctx.Transforms.Conversion.ConvertType("CShout", nameof(Data.Computed.Shout)))
.Append(ctx.Transforms.Conversion.ConvertType("CTrade", nameof(Data.Computed.ContainsTradeWords)))
.Append(ctx.Transforms.Conversion.ConvertType("CSketch", nameof(Data.Computed.ContainsSketchUrl)))
.Append(ctx.Transforms.Conversion.ConvertType("HasWard", nameof(Data.Computed.ContainsWard)))
.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"))
// 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 {
Mode.Test => ttd.TrainSet,
Mode.CreateModel => df,
Mode.Interactive => ttd.TrainSet,
Mode.InteractiveFull => df,
_ => throw new ArgumentOutOfRangeException($"mode {mode} not handled"),
};
var model = pipeline.Fit(train);
if (mode == Mode.CreateModel) {
var savePath = Path.Join(parentDir.FullName, "model.zip");
ctx.Model.Save(model, train.Schema, savePath);
}
var testPredictions = model.Transform(ttd.TestSet);
var eval = ctx.MulticlassClassification.Evaluate(testPredictions);
var predEngine = ctx.Model.CreatePredictionEngine<Data, Prediction>(model);
var slotNames = new VBuffer<ReadOnlyMemory<char>>();
predEngine.OutputSchema["Score"].GetSlotNames(ref slotNames);
var names = slotNames.DenseValues()
.Select(column => column.ToString())
.ToList();
var cols = new string[1 + names.Count];
cols[0] = "";
for (var j = 0; j < names.Count; j++) {
cols[j + 1] = names[j];
}
var table = new ConsoleTable(cols);
for (var i = 0; i < names.Count; i++) {
var name = names[i];
var confuse = eval.ConfusionMatrix.Counts[i];
var row = new object[1 + confuse.Count];
row[0] = name;
for (var j = 0; j < confuse.Count; j++) {
if (i == j) {
row[j + 1] = $"= {confuse[j]} =";
} else {
row[j + 1] = confuse[j];
}
}
table.AddRow(row);
}
Console.WriteLine("Rows are expected classification and columns are actual classification.");
Console.WriteLine();
Console.WriteLine(table.ToString());
Console.WriteLine($"Log loss : {eval.LogLoss * 100}");
Console.WriteLine($"Macro acc: {eval.MacroAccuracy * 100}");
Console.WriteLine($"Micro acc: {eval.MicroAccuracy * 100}");
switch (mode) {
case Mode.Test:
case Mode.CreateModel:
return;
}
while (true) {
var msg = Console.ReadLine()!.Trim();
var parts = msg.Split(' ', 2);
if (parts.Length < 2 || !ushort.TryParse(parts[0], out var channel)) {
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);
Console.WriteLine(pred.Category);
for (var i = 0; i < names.Count; i++) {
Console.WriteLine($" {names[i]}: {pred.Probabilities[i] * 100}");
}
}
}
}
}

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);
}
}
}

6507
NoSoliciting.Trainer/data.csv Executable file

File diff suppressed because it is too large Load Diff

19
NoSoliciting.sln Normal file → Executable file
View File

@ -6,11 +6,10 @@ MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NoSoliciting", "NoSoliciting\NoSoliciting.csproj", "{E4C12987-9064-4788-8783-BE418B2C0142}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0C424003-6609-47D7-8D27-BF309C34E909}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NoSoliciting.Tests", "NoSoliciting.Tests\NoSoliciting.Tests.csproj", "{1962D91F-543A-4214-88FD-788BB7ACECE3}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NoSoliciting.Interface", "NoSoliciting.Interface\NoSoliciting.Interface.csproj", "{E88E57AB-EFB8-4F2F-93DB-F63123638C44}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NoSoliciting.Trainer", "NoSoliciting.Trainer\NoSoliciting.Trainer.csproj", "{3D774127-F7A9-4B6D-AB2F-3AAF80D15586}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -22,10 +21,14 @@ Global
{E4C12987-9064-4788-8783-BE418B2C0142}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E4C12987-9064-4788-8783-BE418B2C0142}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E4C12987-9064-4788-8783-BE418B2C0142}.Release|Any CPU.Build.0 = Release|Any CPU
{1962D91F-543A-4214-88FD-788BB7ACECE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1962D91F-543A-4214-88FD-788BB7ACECE3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1962D91F-543A-4214-88FD-788BB7ACECE3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1962D91F-543A-4214-88FD-788BB7ACECE3}.Release|Any CPU.Build.0 = Release|Any CPU
{E88E57AB-EFB8-4F2F-93DB-F63123638C44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E88E57AB-EFB8-4F2F-93DB-F63123638C44}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E88E57AB-EFB8-4F2F-93DB-F63123638C44}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E88E57AB-EFB8-4F2F-93DB-F63123638C44}.Release|Any CPU.Build.0 = Release|Any CPU
{3D774127-F7A9-4B6D-AB2F-3AAF80D15586}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

38
NoSoliciting/Commands.cs Executable file
View File

@ -0,0 +1,38 @@
using System;
using Dalamud.Game.Command;
namespace NoSoliciting {
public class Commands : IDisposable {
private Plugin Plugin { get; }
internal Commands(Plugin plugin) {
this.Plugin = plugin;
this.Plugin.CommandManager.AddHandler("/prmt", new CommandInfo(this.OnCommand) {
HelpMessage = "Opens the NoSoliciting configuration (deprecated)",
ShowInHelp = false,
});
this.Plugin.CommandManager.AddHandler("/nosol", new CommandInfo(this.OnCommand) {
HelpMessage = "Opens the NoSoliciting configuration",
});
}
public void Dispose() {
this.Plugin.CommandManager.RemoveHandler("/nosol");
this.Plugin.CommandManager.RemoveHandler("/prmt");
}
private void OnCommand(string command, string args) {
if (command == "/prmt") {
this.Plugin.ChatGui.PrintError($"[{Plugin.Name}] The /prmt command is deprecated and will be removed. Please use /nosol instead.");
}
if (args == "report") {
this.Plugin.Ui.Report.Toggle();
return;
}
this.Plugin.Ui.Settings.Toggle();
}
}
}

54
NoSoliciting/ContextMenu.cs Executable file
View File

@ -0,0 +1,54 @@
using System;
using System.Linq;
using Dalamud.ContextMenu;
using NoSoliciting.Resources;
namespace NoSoliciting {
public class ContextMenu : IDisposable {
private Plugin Plugin { get; }
internal ContextMenu(Plugin plugin) {
this.Plugin = plugin;
this.Plugin.DalamudContextMenu.OnOpenGameObjectContextMenu += this.OnOpenContextMenu;
}
public void Dispose() {
this.Plugin.DalamudContextMenu.OnOpenGameObjectContextMenu -= this.OnOpenContextMenu;
}
private void OnOpenContextMenu(GameObjectContextMenuOpenArgs args) {
if (args.ParentAddonName != "LookingForGroup") {
return;
}
if (args.ContentIdLower == 0) {
return;
}
var label = Language.ReportToNoSoliciting;
args.AddCustomItem(new GameObjectContextMenuItem(label, this.Report));
}
private void Report(GameObjectContextMenuItemSelectedArgs args) {
if (args.ContentIdLower == 0) {
return;
}
var listing = this.Plugin.Common.Functions.PartyFinder.CurrentListings
.Values
.FirstOrDefault(listing => listing.ContentIdLower == args.ContentIdLower);
if (listing == null) {
return;
}
var message = this.Plugin.PartyFinderHistory.FirstOrDefault(message => message.ActorId == listing.ContentIdLower);
if (message == null) {
return;
}
this.Plugin.Ui.Report.ToShowModal = message;
}
}
}

View File

@ -0,0 +1,9 @@
<Project>
<Target Name="PackagePlugin" AfterTargets="Build" Condition="'$(Configuration)' == 'Release'">
<DalamudPackager ProjectDir="$(ProjectDir)"
OutputPath="$(OutputPath)"
AssemblyName="$(AssemblyName)"
VersionComponents="3"
MakeZip="true"/>
</Target>
</Project>

View File

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

View File

@ -1,21 +1,22 @@
using System;
using System.Linq;
using NoSoliciting.Interface;
namespace NoSoliciting {
public partial class Filter {
public static class Chat {
private static class Chat {
public static bool MatchesCustomFilters(string msg, PluginConfiguration config) {
if (config == null) {
throw new ArgumentNullException(nameof(config), "PluginConfiguration cannot be null");
}
if (!config.AdvancedMode || !config.CustomChatFilter) {
if (!config.CustomChatFilter) {
return false;
}
msg = FilterUtil.Normalise(msg);
msg = NoSolUtil.Normalise(msg);
return config.ChatSubstrings.Any(needle => msg.ContainsIgnoreCase(needle))
return config.ValidChatSubstrings.Any(needle => msg.ContainsIgnoreCase(needle))
|| config.CompiledChatRegexes.Any(needle => needle.IsMatch(msg));
}
}

View File

@ -1,22 +1,23 @@
using System;
using System.Linq;
using NoSoliciting.Interface;
namespace NoSoliciting {
public partial class Filter {
public static class PartyFinder {
private static class PartyFinder {
public static bool MatchesCustomFilters(string msg, PluginConfiguration config) {
if (config == null) {
throw new ArgumentNullException(nameof(config), "PluginConfiguration cannot be null");
}
if (!config.AdvancedMode || !config.CustomPFFilter) {
if (!config.CustomPFFilter) {
return false;
}
msg = FilterUtil.Normalise(msg);
msg = NoSolUtil.Normalise(msg);
return config.PFSubstrings.Any(needle => msg.ContainsIgnoreCase(needle))
|| config.CompiledPFRegexes.Any(needle => needle.IsMatch(msg));
return config.ValidPfSubstrings.Any(needle => msg.ContainsIgnoreCase(needle))
|| config.CompiledPfRegexes.Any(needle => needle.IsMatch(msg));
}
}
}

View File

@ -1,190 +1,214 @@
using Dalamud.Game.Chat;
using Dalamud.Game.Chat.SeStringHandling;
using Dalamud.Hooking;
using Dalamud.Plugin;
using System;
using System.Runtime.InteropServices;
using System;
using Dalamud.Game.Gui.PartyFinder.Types;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using NoSoliciting.Interface;
using NoSoliciting.Ml;
namespace NoSoliciting {
public partial class Filter : IDisposable {
private readonly Plugin plugin;
private bool clearOnNext = false;
private const uint MinWords = 4;
private delegate void HandlePFPacketDelegate(IntPtr param_1, IntPtr param_2);
private readonly Hook<HandlePFPacketDelegate> handlePacketHook;
public static readonly ChatType[] FilteredChatTypes = {
ChatType.Say,
ChatType.Yell,
ChatType.Shout,
ChatType.TellIncoming,
ChatType.Party,
ChatType.CrossParty,
ChatType.Alliance,
ChatType.FreeCompany,
ChatType.PvpTeam,
ChatType.CrossLinkshell1,
ChatType.CrossLinkshell2,
ChatType.CrossLinkshell3,
ChatType.CrossLinkshell4,
ChatType.CrossLinkshell5,
ChatType.CrossLinkshell6,
ChatType.CrossLinkshell7,
ChatType.CrossLinkshell8,
ChatType.Linkshell1,
ChatType.Linkshell2,
ChatType.Linkshell3,
ChatType.Linkshell4,
ChatType.Linkshell5,
ChatType.Linkshell6,
ChatType.Linkshell7,
ChatType.Linkshell8,
ChatType.NoviceNetwork,
};
private delegate long HandlePFSummaryDelegate(long param_1, long param_2);
private readonly Hook<HandlePFSummaryDelegate> handleSummaryHook;
private Plugin Plugin { get; }
private int LastBatch { get; set; } = -1;
private bool disposedValue;
private bool _disposedValue;
public Filter(Plugin plugin) {
this.plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "Plugin cannot be null");
this.Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "Plugin cannot be null");
IntPtr listingPtr = this.plugin.Interface.TargetModuleScanner.ScanText("40 53 41 57 48 83 EC 28 48 8B D9");
IntPtr summaryPtr = this.plugin.Interface.TargetModuleScanner.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B CE E8 ?? ?? ?? ?? 49 8B 07");
if (listingPtr == IntPtr.Zero || summaryPtr == IntPtr.Zero) {
PluginLog.Log("Party Finder filtering disabled because hook could not be created.");
this.Plugin.ChatGui.CheckMessageHandled += this.OnChat;
this.Plugin.PartyFinderGui.ReceiveListing += this.OnListing;
}
private void Dispose(bool disposing) {
if (this._disposedValue) {
return;
}
this.handlePacketHook = new Hook<HandlePFPacketDelegate>(listingPtr, new HandlePFPacketDelegate(this.HandlePFPacket));
this.handlePacketHook.Enable();
this.handleSummaryHook = new Hook<HandlePFSummaryDelegate>(summaryPtr, new HandlePFSummaryDelegate(this.HandleSummary));
this.handleSummaryHook.Enable();
}
private void HandlePFPacket(IntPtr param_1, IntPtr param_2) {
if (this.plugin.Definitions == null) {
this.handlePacketHook.Original(param_1, param_2);
return;
if (disposing) {
this.Plugin.ChatGui.CheckMessageHandled -= this.OnChat;
this.Plugin.PartyFinderGui.ReceiveListing -= this.OnListing;
}
if (this.clearOnNext) {
this.plugin.ClearPartyFinderHistory();
this.clearOnNext = false;
}
IntPtr dataPtr = param_2 + 0x10;
// parse the packet into a struct
PFPacket packet = Marshal.PtrToStructure<PFPacket>(dataPtr);
for (int i = 0; i < packet.listings.Length; i++) {
PFListing listing = packet.listings[i];
// only look at listings that aren't null
if (listing.IsNull()) {
continue;
}
string desc = listing.Description();
string reason = null;
bool filter = false;
filter = filter || (this.plugin.Config.FilterHugeItemLevelPFs
&& listing.minimumItemLevel > FilterUtil.MaxItemLevelAttainable(this.plugin.Interface.Data)
&& SetReason(ref reason, "ilvl"));
foreach (Definition def in this.plugin.Definitions.PartyFinder.Values) {
filter = filter || (this.plugin.Config.FilterStatus.TryGetValue(def.Id, out bool enabled)
&& enabled
&& def.Matches(XivChatType.None, desc)
&& SetReason(ref reason, def.Id));
}
// check for custom filters if enabled
filter = filter || (this.plugin.Config.CustomPFFilter
&& PartyFinder.MatchesCustomFilters(desc, this.plugin.Config)
&& SetReason(ref reason, "custom"));
this.plugin.AddPartyFinderHistory(new Message(
defs: this.plugin.Definitions,
type: ChatType.None,
sender: listing.Name(),
content: listing.Description(),
reason: reason
));
if (!filter) {
continue;
}
// replace the listing with an empty one
packet.listings[i] = new PFListing();
PluginLog.Log($"Filtered PF listing from {listing.Name()} ({reason}): {listing.Description()}");
}
// get some memory for writing to
byte[] newPacket = new byte[PacketInfo.packetSize];
GCHandle pinnedArray = GCHandle.Alloc(newPacket, GCHandleType.Pinned);
IntPtr pointer = pinnedArray.AddrOfPinnedObject();
// write our struct into the memory (doing this directly crashes the game)
Marshal.StructureToPtr(packet, pointer, false);
// copy our new memory over the game's
Marshal.Copy(newPacket, 0, dataPtr, PacketInfo.packetSize);
// free memory
pinnedArray.Free();
// call original function
this.handlePacketHook.Original(param_1, param_2);
}
private long HandleSummary(long param_1, long param_2) {
this.clearOnNext = true;
return this.handleSummaryHook.Original(param_1, param_2);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "fulfilling a delegate")]
public void OnChat(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled) {
if (message == null) {
throw new ArgumentNullException(nameof(message), "SeString cannot be null");
}
if (this.plugin.Definitions == null || ChatTypeExt.FromDalamud(type).IsBattle()) {
return;
}
string text = message.TextValue;
string reason = null;
bool filter = false;
foreach (Definition def in this.plugin.Definitions.Chat.Values) {
filter = filter || (this.plugin.Config.FilterStatus.TryGetValue(def.Id, out bool enabled)
&& enabled
&& def.Matches(type, text)
&& SetReason(ref reason, def.Id));
}
// check for custom filters if enabled
filter = filter || (this.plugin.Config.CustomChatFilter
&& Chat.MatchesCustomFilters(text, this.plugin.Config)
&& SetReason(ref reason, "custom"));
this.plugin.AddMessageHistory(new Message(
defs: this.plugin.Definitions,
type: ChatTypeExt.FromDalamud(type),
sender: sender,
content: message,
reason: reason
));
if (!filter) {
return;
}
PluginLog.Log($"Filtered chat message ({reason}): {text}");
isHandled = true;
}
private static bool SetReason(ref string reason, string value) {
reason = value;
return true;
}
protected virtual void Dispose(bool disposing) {
if (!disposedValue) {
if (disposing) {
this.handlePacketHook?.Dispose();
this.handleSummaryHook?.Dispose();
}
disposedValue = true;
}
this._disposedValue = true;
}
public void Dispose() {
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
this.Dispose(true);
GC.SuppressFinalize(this);
}
private void OnChat(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled) {
isHandled = isHandled || this.FilterMessage(type, senderId, sender, message);
}
private void OnListing(PartyFinderListing listing, PartyFinderListingEventArgs args) {
try {
if (this.LastBatch != args.BatchNumber) {
this.Plugin.ClearPartyFinderHistory();
}
this.LastBatch = args.BatchNumber;
var version = this.Plugin.MlFilter?.Version;
var (category, reason) = this.MlListingFilterReason(listing);
this.Plugin.AddPartyFinderHistory(new Message(
version,
ChatType.None,
listing.ContentIdLower,
listing.Name,
listing.Description,
category,
reason == "custom",
reason == "ilvl",
this.Plugin.Config.CreateFiltersClone()
));
if (category == null && reason == null) {
return;
}
args.Visible = false;
if (this.Plugin.Config.LogFilteredPfs) {
Plugin.Log.Info($"Filtered PF listing from {listing.Name.TextValue} ({reason}): {listing.Description.TextValue}");
}
} catch (Exception ex) {
Plugin.Log.Error($"Error in PF listing event: {ex}");
}
}
private bool FilterMessage(XivChatType type, uint senderId, SeString sender, SeString message) {
if (message == null) {
throw new ArgumentNullException(nameof(message), "SeString cannot be null");
}
return this.MlFilterMessage(type, senderId, sender, message);
}
private bool MlFilterMessage(XivChatType type, uint senderId, SeString sender, SeString message) {
var chatType = ChatTypeExt.FromDalamud(type);
// NOTE: don't filter on user-controlled chat types here because custom filters are supposed to check all
// messages except battle messages
if (chatType.IsBattle()) {
return false;
}
var text = message.TextValue;
var custom = false;
MessageCategory? classification = null;
// step 1. check for custom filters if enabled
var filter = false;
if (this.Plugin.Config.CustomChatFilter && Chat.MatchesCustomFilters(text, this.Plugin.Config)) {
filter = true;
custom = true;
}
// only look at ml if message >= min words
if (!filter && this.Plugin.MlFilter != null && CountWords(text) >= MinWords) {
// step 2. classify the message using the model
var category = this.Plugin.MlFilter.ClassifyMessage((ushort) chatType, text);
// step 2a. only filter if configured to act on this channel
if (category != MessageCategory.Normal && this.Plugin.Config.MlEnabledOn(category, chatType)) {
filter = true;
classification = category;
}
}
var history = new Message(
this.Plugin.MlFilter?.Version,
ChatTypeExt.FromDalamud(type),
senderId,
sender,
message,
classification,
custom,
false,
this.Plugin.Config.CreateFiltersClone()
);
this.Plugin.AddMessageHistory(history);
if (filter && this.Plugin.Config.LogFilteredChat) {
Plugin.Log.Info($"Filtered chat message ({history.FilterReason ?? "unknown"}): {text}");
}
return filter;
}
private (MessageCategory?, string?) MlListingFilterReason(PartyFinderListing listing) {
if (this.Plugin.MlFilter == null) {
return (null, null);
}
// ignore private listings if configured
if (!this.Plugin.Config.ConsiderPrivatePfs && listing[SearchAreaFlags.Private]) {
return (null, null);
}
// step 1. check if pf has an item level that's too high
if (this.Plugin.Config.FilterHugeItemLevelPFs && listing.MinimumItemLevel > FilterUtil.MaxItemLevelAttainable(this.Plugin.DataManager)) {
return (null, "ilvl");
}
var desc = listing.Description.TextValue;
// step 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 (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, Enum.GetName(category));
}
return (null, null);
}
private static int CountWords(string text) {
return text.Spacify().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Length;
}
}
}

View File

@ -1,110 +1,16 @@
using Dalamud.Data;
using Lumina.Excel.GeneratedSheets;
using Lumina.Excel.GeneratedSheets;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using Dalamud.Plugin.Services;
using NoSoliciting.Ml;
namespace NoSoliciting {
public static class FilterUtil {
private static readonly Dictionary<char, string> replacements = new Dictionary<char, string>() {
// numerals
['\ue055'] = "1",
['\ue056'] = "2",
['\ue057'] = "3",
['\ue058'] = "4",
['\ue059'] = "5",
private static int MaxItemLevel { get; set; }
['\ue099'] = "10",
['\ue09a'] = "11",
['\ue09b'] = "12",
['\ue09c'] = "13",
['\ue09d'] = "14",
['\ue09e'] = "15",
['\ue09f'] = "16",
['\ue0a0'] = "17",
['\ue0a1'] = "18",
['\ue0a2'] = "19",
['\ue0a3'] = "20",
['\ue0a4'] = "21",
['\ue0a5'] = "22",
['\ue0a6'] = "23",
['\ue0a7'] = "24",
['\ue0a8'] = "25",
['\ue0a9'] = "26",
['\ue0aa'] = "27",
['\ue0ab'] = "28",
['\ue0ac'] = "29",
['\ue0ad'] = "30",
['\ue0ae'] = "31",
// symbols
['\ue0af'] = "+",
['\ue070'] = "?",
// letters in other sets
['\ue022'] = "A",
['\ue024'] = "_A",
['\ue0b0'] = "E",
};
private const char lowestReplacement = '\ue022';
public static string Normalise(string input) {
if (input == null) {
throw new ArgumentNullException(nameof(input), "input cannot be null");
}
// replace ffxiv private use chars
var builder = new StringBuilder(input.Length);
foreach (char c in input) {
if (c < lowestReplacement) {
goto AppendNormal;
}
// alphabet
if (c >= 0xe071 && c <= 0xe08a) {
builder.Append((char)(c - 0xe030));
continue;
}
// 0 to 9
if (c >= 0xe060 && c <= 0xe069) {
builder.Append((char)(c - 0xe030));
continue;
}
// 1 to 9
if (c >= 0xe0b1 && c <= 0xe0b9) {
builder.Append((char)(c - 0xe080));
continue;
}
// 1 to 9 again
if (c >= 0xe090 && c <= 0xe098) {
builder.Append((char)(c - 0xe05f));
continue;
}
// replacements in map
if (replacements.TryGetValue(c, out string rep)) {
builder.Append(rep);
continue;
}
AppendNormal:
builder.Append(c);
}
input = builder.ToString();
// NFKD unicode normalisation
return input.Normalize(NormalizationForm.FormKD);
}
private static int MaxItemLevel { get; set; } = 0;
enum Slot {
private enum Slot {
MainHand,
OffHand,
Head,
@ -120,42 +26,68 @@ namespace NoSoliciting {
RingR,
}
static Slot? SlotFromItem(Item item) {
EquipSlotCategory cat = item.EquipSlotCategory.Value;
private static Slot? SlotFromItem(Item item) {
var cat = item.EquipSlotCategory.Value;
if (cat == null) {
return null;
}
if (cat.MainHand != 0) {
return Slot.MainHand;
} else if (cat.Head != 0) {
}
if (cat.Head != 0) {
return Slot.Head;
} else if (cat.Body != 0) {
}
if (cat.Body != 0) {
return Slot.Chest;
} else if (cat.Gloves != 0) {
}
if (cat.Gloves != 0) {
return Slot.Hands;
} else if (cat.Waist != 0) {
}
if (cat.Waist != 0) {
return Slot.Waist;
} else if (cat.Legs != 0) {
}
if (cat.Legs != 0) {
return Slot.Legs;
} else if (cat.Feet != 0) {
}
if (cat.Feet != 0) {
return Slot.Feet;
} else if (cat.OffHand != 0) {
}
if (cat.OffHand != 0) {
return Slot.OffHand;
} else if (cat.Ears != 0) {
}
if (cat.Ears != 0) {
return Slot.Earrings;
} else if (cat.Neck != 0) {
}
if (cat.Neck != 0) {
return Slot.Neck;
} else if (cat.FingerL != 0) {
}
if (cat.Wrists != 0) {
return Slot.Wrist;
}
if (cat.FingerL != 0) {
return Slot.RingL;
} else if (cat.FingerR != 0) {
}
if (cat.FingerR != 0) {
return Slot.RingR;
}
return null;
}
public static int MaxItemLevelAttainable(DataManager data) {
public static int MaxItemLevelAttainable(IDataManager data) {
if (MaxItemLevel > 0) {
return MaxItemLevel;
}
@ -164,36 +96,40 @@ namespace NoSoliciting {
throw new ArgumentNullException(nameof(data), "DataManager cannot be null");
}
Dictionary<Slot, int> ilvls = new Dictionary<Slot, int>();
var ilvls = new Dictionary<Slot, int>();
foreach (Item item in data.GetExcelSheet<Item>()) {
Slot? slot = SlotFromItem(item);
foreach (var item in data.GetExcelSheet<Item>()!) {
var slot = SlotFromItem(item);
if (slot == null) {
continue;
}
int itemLevel = 0;
ItemLevel ilvl = item.LevelItem.Value;
var itemLevel = 0;
var ilvl = item.LevelItem.Value;
if (ilvl != null) {
itemLevel = (int)ilvl.RowId;
itemLevel = (int) ilvl.RowId;
}
if (ilvls.TryGetValue((Slot)slot, out int currentMax) && currentMax > itemLevel) {
if (ilvls.TryGetValue((Slot) slot, out var currentMax) && currentMax > itemLevel) {
continue;
}
ilvls[(Slot)slot] = itemLevel;
ilvls[(Slot) slot] = itemLevel;
}
MaxItemLevel = (int)ilvls.Values.Average();
MaxItemLevel = (int) ilvls.Values.Average();
return MaxItemLevel;
}
}
public static class RMTExtensions {
public static class Extensions {
public static bool ContainsIgnoreCase(this string haystack, string needle) {
return CultureInfo.InvariantCulture.CompareInfo.IndexOf(haystack, needle, CompareOptions.IgnoreCase) >= 0;
}
public static bool WasEnabled(this IEnumerable<MessageCategory> enabled, MessageCategory category) {
return category == MessageCategory.Normal || enabled.Contains(category);
}
}
}

View File

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

View File

@ -1,16 +0,0 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Scope = "type", Target = "~T:NoSoliciting.PFPacket")]
[assembly: SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Scope = "type", Target = "~T:NoSoliciting.PFListing")]
[assembly: SuppressMessage("Design", "CA1051:Do not declare visible instance fields", Scope = "member", Target = "~F:NoSoliciting.PFPacket.padding1")]
[assembly: SuppressMessage("Design", "CA1051:Do not declare visible instance fields", Scope = "member", Target = "~F:NoSoliciting.PFPacket.listings")]
[assembly: SuppressMessage("Design", "CA1051:Do not declare visible instance fields", Scope = "member", Target = "~F:NoSoliciting.PFListing.header")]
[assembly: SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Scope = "module")]
[assembly: SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "no", Scope = "module")]
[assembly: SuppressMessage("Design", "CA1034:Nested types should not be visible", Scope = "module")]
[assembly: SuppressMessage("Design", "CA1724", Scope = "module")]

View File

@ -0,0 +1,29 @@
using System;
namespace NoSoliciting.Interface {
public class PluginUi : IDisposable {
private Plugin Plugin { get; }
public Settings Settings { get; }
public Report Report { get; }
public PluginUi(Plugin plugin) {
this.Plugin = plugin;
this.Settings = new Settings(plugin, this);
this.Report = new Report(plugin);
this.Plugin.Interface.UiBuilder.Draw += this.Draw;
}
public void Dispose() {
this.Plugin.Interface.UiBuilder.Draw -= this.Draw;
this.Settings.Dispose();
}
private void Draw() {
this.Settings.Draw();
this.Report.Draw();
}
}
}

456
NoSoliciting/Interface/Report.cs Executable file
View File

@ -0,0 +1,456 @@
using System;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Numerics;
using System.Threading.Tasks;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using ImGuiNET;
using NoSoliciting.Ml;
using NoSoliciting.Resources;
#if DEBUG
using System.Text;
#endif
namespace NoSoliciting.Interface {
public class Report {
private const ImGuiTableFlags TableFlags = ImGuiTableFlags.Borders
& ~ImGuiTableFlags.BordersOuterV
| ImGuiTableFlags.PadOuterX
| ImGuiTableFlags.RowBg
| ImGuiTableFlags.SizingFixedFit
| ImGuiTableFlags.ScrollY
| ImGuiTableFlags.Hideable
| ImGuiTableFlags.Reorderable
| ImGuiTableFlags.Resizable;
private Plugin Plugin { get; }
private ReportStatus LastReportStatus { get; set; } = ReportStatus.None;
private bool _showReporting;
private bool ShowReporting {
get => this._showReporting;
set => this._showReporting = value;
}
internal Message? ToShowModal { get; set; }
public Report(Plugin plugin) {
this.Plugin = plugin;
}
public void Open() {
this.ShowReporting = true;
}
public void Toggle() {
this.ShowReporting = !this.ShowReporting;
}
public void Draw() {
var toShow = this.ToShowModal;
if (toShow != null) {
if (!this.SetUpReportModal(toShow)) {
ImGui.OpenPopup($"###modal-message-{toShow.Id}");
}
}
if (!this.ShowReporting) {
return;
}
ImGui.SetNextWindowSize(new Vector2(1_000, 350), ImGuiCond.FirstUseEver);
var windowTitle = string.Format(Language.Reporting, Plugin.Name);
if (!ImGui.Begin($"{windowTitle}###NoSoliciting reporting", ref this._showReporting)) {
ImGui.End();
return;
}
ImGui.TextUnformatted(Language.ReportHelp);
if (this.LastReportStatus != ReportStatus.None) {
var status = this.LastReportStatus switch {
ReportStatus.Failure => Language.ReportStatusFailure,
ReportStatus.Successful => Language.ReportStatusSuccessful,
ReportStatus.InProgress => Language.ReportStatusInProgress,
_ => Language.ReportStatusUnknown,
};
var reportStatus = Language.ReportStatusMessage;
ImGui.TextUnformatted(string.Format(reportStatus, status));
}
ImGui.Separator();
ImGui.Spacing();
if (ImGui.BeginTabBar("##report-tabs")) {
this.ChatTab();
this.PartyFinderTab();
ImGui.EndTabBar();
}
ImGui.End();
}
private void ChatTab() {
if (!ImGui.BeginTabItem($"{Language.ReportChatTab}###chat-report")) {
return;
}
if (ImGui.BeginChild("##chat-messages", new Vector2(-1, -1))) {
if (ImGui.BeginTable("nosol-chat-report-table", 5, TableFlags)) {
ImGui.TableSetupColumn(Language.ReportColumnTimestamp);
ImGui.TableSetupColumn(Language.ReportColumnChannel);
ImGui.TableSetupColumn(Language.ReportColumnReason);
ImGui.TableSetupColumn(Language.ReportColumnSender);
ImGui.TableSetupColumn(Language.ReportColumnMessage, ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableHeadersRow();
foreach (var message in this.Plugin.MessageHistory) {
ImGui.TableNextRow();
if (message.Filtered) {
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(238f / 255f, 71f / 255f, 71f / 255f, 1f));
}
var sender = message.Sender.Payloads
.Where(payload => payload.Type == PayloadType.RawText)
.Cast<TextPayload>()
.Select(payload => payload.Text)
.FirstOrDefault() ?? "";
if (AddRow(message.Timestamp.ToString(CultureInfo.CurrentCulture), message.ChatType.Name(this.Plugin.DataManager), message.FilterReason ?? string.Empty, sender, message.Content.TextValue)) {
ImGui.OpenPopup($"###modal-message-{message.Id}");
}
if (message.Filtered) {
ImGui.PopStyleColor();
}
this.SetUpReportModal(message);
}
ImGui.EndTable();
}
ImGui.EndChild();
}
ImGui.EndTabItem();
}
private void PartyFinderTab() {
if (!ImGui.BeginTabItem($"{Language.ReportPartyFinderTab}###pf-report")) {
return;
}
#if DEBUG
if (ImGui.Button("Copy CSV")) {
var builder = new StringBuilder();
foreach (var message in this.Plugin.PartyFinderHistory) {
if (message.Classification == null) {
continue;
}
message.ToCsv(builder).Append('\n');
}
ImGui.SetClipboardText(builder.ToString());
}
#endif
if (ImGui.BeginChild("##pf-messages", new Vector2(-1, -1))) {
if (ImGui.BeginTable("nosol-pf-report-table", 4, TableFlags)) {
ImGui.TableSetupColumn(Language.ReportColumnTimestamp);
ImGui.TableSetupColumn(Language.ReportColumnReason);
ImGui.TableSetupColumn(Language.ReportColumnHost);
ImGui.TableSetupColumn(Language.ReportColumnDescription, ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableHeadersRow();
foreach (var message in this.Plugin.PartyFinderHistory) {
ImGui.TableNextRow();
if (message.Filtered) {
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(238f / 255f, 71f / 255f, 71f / 255f, 1f));
}
var sender = message.Sender.Payloads
.Where(payload => payload.Type == PayloadType.RawText)
.Cast<TextPayload>()
.Select(payload => payload.Text)
.FirstOrDefault() ?? "";
if (AddRow(message.Timestamp.ToString(CultureInfo.CurrentCulture), message.FilterReason ?? string.Empty, sender, message.Content.TextValue)) {
ImGui.OpenPopup($"###modal-message-{message.Id}");
}
if (message.Filtered) {
ImGui.PopStyleColor();
}
this.SetUpReportModal(message);
}
ImGui.EndTable();
}
ImGui.EndChild();
}
ImGui.EndTabItem();
}
#region Modal
private MessageCategory? _reportCategory;
private bool _other;
/// <summary>
///
/// </summary>
/// <param name="message"></param>
/// <returns>true if modal is closing</returns>
private bool SetUpReportModal(Message message) {
var closing = false;
ImGui.SetNextWindowSize(new Vector2(350, -1));
var modalTitle = string.Format(Language.ReportModalTitle, Plugin.Name);
if (!ImGui.BeginPopupModal($"{modalTitle}###modal-message-{message.Id}")) {
return false;
}
if (this._reportCategory == null && !this._other) {
if (message.Classification != null) {
this._reportCategory = message.Classification;
} else if (message.Classification == null && !message.Custom && !message.ItemLevel) {
this._reportCategory = MessageCategory.Normal;
}
}
ImGui.PushTextWrapPos();
ImGui.TextUnformatted(Language.ReportModalHelp1);
ImGui.Separator();
ImGui.TextUnformatted(message.Content.TextValue);
ImGui.Separator();
ImGui.TextUnformatted(string.Format(Language.ReportModalOriginalClassification, message.FilterReason ?? MessageCategory.Normal.Name()));
ImGui.TextUnformatted(Language.ReportModalSuggestedClassification);
ImGui.SetNextItemWidth(-1);
var preview = this._reportCategory?.Name() ?? (this._other ? Language.ReportModalClassificationOther : string.Empty);
if (ImGui.BeginCombo($"##modal-classification-{message.Id}", preview)) {
foreach (var category in (MessageCategory[]) Enum.GetValues(typeof(MessageCategory))) {
if (ImGui.Selectable($"{category.Name()}##modal-option-{message.Id}", this._reportCategory == category)) {
this._reportCategory = category;
this._other = false;
}
WrappedTooltip(category.Description());
}
if (ImGui.Selectable(Language.ReportModalClassificationOther)) {
this._reportCategory = null;
this._other = true;
}
WrappedTooltip(Language.ReportModalClassificationOtherDescription);
ImGui.EndCombo();
}
ImGui.Separator();
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0f, 0f, 1f));
ImGui.TextUnformatted(Language.ReportModalHelp2);
ImGui.PopStyleColor();
ImGui.Separator();
string? errorText = null;
if (message.Custom) {
errorText = Language.ReportModalDisabledCustom;
} else if (message.ItemLevel) {
errorText = Language.ReportModalDisabledItemLevel;
} else if (message.ModelVersion == null) {
errorText = Language.ReportModalDisabledBadModel;
} else if (!message.EnabledSnapshot.WasEnabled(this._reportCategory ?? MessageCategory.Normal)) {
errorText = Language.ReportModalDisabledFilterNotEnabled;
} else if (this._reportCategory == (message.Classification ?? MessageCategory.Normal)) {
errorText = Language.ReportModalDisabledSameClassification;
}
if (this._other) {
if (ImGui.Button($"{Language.ReportModalGoToCustomButton}##report-goto-custom-{message.Id}")) {
ImGui.CloseCurrentPopup();
closing = true;
if (message == this.ToShowModal) {
this.ToShowModal = null;
}
this.Plugin.Ui.Settings.ShowOtherFilters = true;
this.Plugin.Ui.Settings.Show();
}
ImGui.SameLine();
} else if (errorText != null) {
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0f, 0f, 1f));
ImGui.TextUnformatted(errorText);
ImGui.PopStyleColor();
} else {
if (ImGui.Button($"{Language.ReportModalReport}##report-submit-{message.Id}")) {
var suggested = this._reportCategory?.ToModelName() ?? "none (this is a bug)";
this._reportCategory = null;
if (message == this.ToShowModal) {
Task.Run(async () => {
var status = await this.Plugin.Ui.Report.ReportMessageAsync(message, suggested);
switch (status) {
case ReportStatus.Successful: {
var msg = Language.ReportToastSuccess;
this.Plugin.ToastGui.ShowNormal(string.Format(msg, message.Sender));
break;
}
case ReportStatus.Failure: {
var msg = Language.ReportToastFailure;
this.Plugin.ToastGui.ShowError(string.Format(msg, message.Sender));
break;
}
}
});
this.ToShowModal = null;
} else {
this.ReportMessage(message, suggested);
}
ImGui.CloseCurrentPopup();
closing = true;
}
ImGui.SameLine();
}
if (ImGui.Button($"{Language.ReportModalCopy}##report-copy-{message.Id}")) {
ImGui.SetClipboardText(message.Content.TextValue);
}
#if DEBUG
ImGui.SameLine();
if (ImGui.Button("Copy CSV")) {
ImGui.SetClipboardText(message.ToCsv().ToString());
}
#endif
ImGui.SameLine();
var cancelButton = Language.ReportModalCancel;
if (ImGui.Button($"{cancelButton}##report-cancel-{message.Id}")) {
this._reportCategory = null;
if (message == this.ToShowModal) {
this.ToShowModal = null;
}
ImGui.CloseCurrentPopup();
closing = true;
}
ImGui.PopTextWrapPos();
ImGui.EndPopup();
if (closing) {
this._other = false;
}
return closing;
}
#endregion
#region Utility
private static bool AddRow(params string[] args) {
var clicked = false;
for (var i = 0; i < args.Length; i++) {
ImGui.TableNextColumn();
var arg = args[i];
var last = i == args.Length - 1;
if (last) {
ImGui.PushTextWrapPos();
}
ImGui.TextUnformatted(arg);
if (last) {
ImGui.PopTextWrapPos();
}
clicked = clicked || ImGui.IsItemClicked();
}
return clicked;
}
private static void WrappedTooltip(string text) {
if (!ImGui.IsItemHovered()) {
return;
}
ImGui.BeginTooltip();
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 24);
ImGui.TextUnformatted(text);
ImGui.PopTextWrapPos();
ImGui.EndTooltip();
}
private void ReportMessage(Message message, string suggested) {
Task.Run(async () => await this.ReportMessageAsync(message, suggested));
}
private async Task<ReportStatus> ReportMessageAsync(Message message, string suggested) {
string? resp = null;
try {
using var client = new WebClient();
this.LastReportStatus = ReportStatus.InProgress;
var reportUrl = this.Plugin.MlFilter?.ReportUrl;
if (reportUrl != null) {
var json = message.ToJson(suggested);
if (json != null) {
resp = await client.UploadStringTaskAsync(reportUrl, json).ConfigureAwait(true);
}
}
} catch (Exception) {
// ignored
}
var status = resp == "{\"message\":\"ok\"}" ? ReportStatus.Successful : ReportStatus.Failure;
if (status == ReportStatus.Failure) {
Plugin.Log.Warning($"Failed to report message:\n{resp}");
}
this.LastReportStatus = status;
Plugin.Log.Info(resp == null
? "Report not sent. ML model not set."
: $"Report sent. Response: {resp}");
return status;
}
#endregion
}
}

View File

@ -0,0 +1,8 @@
namespace NoSoliciting.Interface {
public enum ReportStatus {
None = -1,
Failure = 0,
Successful = 1,
InProgress = 2,
}
}

View File

@ -0,0 +1,368 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text;
using System.Text.RegularExpressions;
using Dalamud.Interface;
using ImGuiNET;
using NoSoliciting.Ml;
using NoSoliciting.Resources;
namespace NoSoliciting.Interface {
public class Settings : IDisposable {
private Plugin Plugin { get; }
private PluginUi Ui { get; }
private bool _showSettings;
private bool ShowSettings {
get => this._showSettings;
set => this._showSettings = value;
}
public Settings(Plugin plugin, PluginUi ui) {
this.Plugin = plugin;
this.Ui = ui;
this.Plugin.Interface.UiBuilder.OpenConfigUi += this.Open;
}
public void Dispose() {
this.Plugin.Interface.UiBuilder.OpenConfigUi -= this.Open;
}
private void Open() {
this.ShowSettings = true;
}
public void Toggle() {
this.ShowSettings = !this.ShowSettings;
}
public void Show() {
this.ShowSettings = true;
}
public void Draw() {
var windowTitle = string.Format(Language.Settings, Plugin.Name);
if (!this.ShowSettings || !ImGui.Begin($"{windowTitle}###NoSoliciting settings", ref this._showSettings)) {
return;
}
var advanced = this.Plugin.Config.AdvancedMode;
if (ImGui.Checkbox(Language.AdvancedMode, ref advanced)) {
this.Plugin.Config.AdvancedMode = advanced;
this.Plugin.Config.Save();
}
ImGui.Spacing();
if (!ImGui.BeginTabBar("##nosoliciting-tabs")) {
return;
}
this.DrawMachineLearningConfig();
this.DrawOtherFilters();
this.DrawOtherTab();
ImGui.EndTabBar();
ImGui.Separator();
if (ImGui.Button(Language.ShowReportingWindow)) {
this.Ui.Report.Open();
}
ImGui.End();
}
#region ML config
private void DrawMachineLearningConfig() {
if (this.Plugin.Config.AdvancedMode) {
this.DrawAdvancedMachineLearningConfig();
} else {
this.DrawBasicMachineLearningConfig();
}
if (!ImGui.BeginTabItem($"{Language.ModelTab}###model-tab")) {
return;
}
ImGui.TextUnformatted(string.Format(Language.ModelTabVersion, this.Plugin.MlFilter?.Version));
ImGui.TextUnformatted(string.Format(Language.ModelTabStatus, this.Plugin.MlStatus.Description()));
var lastError = MlFilter.LastError;
if (lastError != null) {
ImGui.TextUnformatted(string.Format(Language.ModelTabError, lastError));
}
if (ImGui.Button(Language.UpdateModel)) {
// prevent issues when people spam the button
if (ImGui.GetIO().KeyCtrl || this.Plugin.MlStatus is MlFilterStatus.Uninitialised or MlFilterStatus.Initialised) {
this.Plugin.MlFilter?.Dispose();
this.Plugin.MlFilter = null;
this.Plugin.MlStatus = MlFilterStatus.Uninitialised;
this.Plugin.InitialiseMachineLearning(ImGui.GetIO().KeyAlt);
}
}
ImGui.EndTabItem();
}
private void DrawBasicMachineLearningConfig() {
if (!ImGui.BeginTabItem($"{Language.FiltersTab}###filters-tab")) {
return;
}
foreach (var category in MessageCategoryExt.UiOrder) {
var check = this.Plugin.Config.BasicMlFilters.Contains(category);
if (ImGui.Checkbox(category.Name(), ref check)) {
if (check) {
this.Plugin.Config.BasicMlFilters.Add(category);
} else {
this.Plugin.Config.BasicMlFilters.Remove(category);
}
this.Plugin.Config.Save();
}
if (!ImGui.IsItemHovered()) {
continue;
}
ImGui.BeginTooltip();
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 24);
ImGui.TextUnformatted(category.Description());
ImGui.PopTextWrapPos();
ImGui.EndTooltip();
}
ImGui.EndTabItem();
}
private void DrawAdvancedMachineLearningConfig() {
if (!ImGui.BeginTabItem($"{Language.FiltersTab}###filters-tab")) {
return;
}
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(255f, 204f, 0f, 1f));
ImGui.TextUnformatted(Language.AdvancedWarning1);
ImGui.TextUnformatted(Language.AdvancedWarning2);
ImGui.PopStyleColor();
foreach (var category in MessageCategoryExt.UiOrder) {
if (!ImGui.CollapsingHeader(category.Name())) {
continue;
}
if (!this.Plugin.Config.MlFilters.ContainsKey(category)) {
this.Plugin.Config.MlFilters[category] = new HashSet<ChatType>();
}
var types = this.Plugin.Config.MlFilters[category];
void DrawTypes(ChatType type, string id) {
var name = type.Name(this.Plugin.DataManager);
var check = types.Contains(type);
if (!ImGui.Checkbox($"{name}##{id}", ref check)) {
return;
}
if (check) {
types.Add(type);
} else {
types.Remove(type);
}
this.Plugin.Config.Save();
}
DrawTypes(ChatType.None, category.ToString());
foreach (var type in Filter.FilteredChatTypes) {
DrawTypes(type, category.ToString());
}
}
ImGui.EndTabItem();
}
#endregion
#region Other config
internal bool ShowOtherFilters;
private static unsafe bool BeginTabItem(string label, ImGuiTabItemFlags flags) {
var unterminatedLabelBytes = Encoding.UTF8.GetBytes(label);
var labelBytes = stackalloc byte[unterminatedLabelBytes.Length + 1];
fixed (byte* unterminatedPtr = unterminatedLabelBytes) {
Buffer.MemoryCopy(unterminatedPtr, labelBytes, unterminatedLabelBytes.Length + 1, unterminatedLabelBytes.Length);
}
labelBytes[unterminatedLabelBytes.Length] = 0;
var num2 = (int) ImGuiNative.igBeginTabItem(labelBytes, null, flags);
return (uint) num2 > 0U;
}
private void DrawOtherFilters() {
var flags = this.ShowOtherFilters ? ImGuiTabItemFlags.SetSelected : ImGuiTabItemFlags.None;
this.ShowOtherFilters = false;
if (!BeginTabItem($"{Language.OtherFiltersTab}###other-filters-tab", flags)) {
return;
}
if (ImGui.CollapsingHeader(Language.ChatFilters)) {
var customChat = this.Plugin.Config.CustomChatFilter;
if (ImGui.Checkbox(Language.EnableCustomChatFilters, ref customChat)) {
this.Plugin.Config.CustomChatFilter = customChat;
this.Plugin.Config.Save();
}
if (this.Plugin.Config.CustomChatFilter) {
var substrings = this.Plugin.Config.ChatSubstrings;
var regexes = this.Plugin.Config.ChatRegexes;
this.DrawCustom("chat", ref substrings, ref regexes);
}
}
if (ImGui.CollapsingHeader(Language.PartyFinderFilters)) {
var filterHugeItemLevelPFs = this.Plugin.Config.FilterHugeItemLevelPFs;
// ReSharper disable once InvertIf
if (ImGui.Checkbox(Language.FilterIlvlPfs, ref filterHugeItemLevelPFs)) {
this.Plugin.Config.FilterHugeItemLevelPFs = filterHugeItemLevelPFs;
this.Plugin.Config.Save();
}
var considerPrivate = this.Plugin.Config.ConsiderPrivatePfs;
if (ImGui.Checkbox(Language.FilterPrivatePfs, ref considerPrivate)) {
this.Plugin.Config.ConsiderPrivatePfs = considerPrivate;
this.Plugin.Config.Save();
}
var customPf = this.Plugin.Config.CustomPFFilter;
if (ImGui.Checkbox(Language.EnableCustomPartyFinderFilters, ref customPf)) {
this.Plugin.Config.CustomPFFilter = customPf;
this.Plugin.Config.Save();
}
if (this.Plugin.Config.CustomPFFilter) {
var substrings = this.Plugin.Config.PFSubstrings;
var regexes = this.Plugin.Config.PFRegexes;
this.DrawCustom("pf", ref substrings, ref regexes);
}
}
ImGui.EndTabItem();
}
private void DrawCustom(string name, 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;
}
ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button($"{FontAwesomeIcon.Trash.ToIconString()}##{name}-substring-{i}-remove")) {
substrings.RemoveAt(i);
}
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;
} catch (ArgumentException) {
// ignore
}
}
ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button($"{FontAwesomeIcon.Trash.ToIconString()}##{name}-regex-{i}-remove")) {
regexes.RemoveAt(i);
}
ImGui.PopFont();
}
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button($"{FontAwesomeIcon.Plus.ToIconString()}##{name}-regex-add")) {
regexes.Add("");
}
ImGui.PopFont();
ImGui.EndChild();
}
ImGui.Columns(1);
// ReSharper disable once InvertIf
var saveLoc = Language.SaveFilters;
if (ImGui.Button($"{saveLoc}##{name}-save")) {
this.Plugin.Config.Save();
this.Plugin.Config.CompileRegexes();
}
}
#endregion
private void DrawOtherTab() {
if (!ImGui.BeginTabItem($"{Language.OtherTab}###other-tab")) {
return;
}
var useGameLanguage = this.Plugin.Config.FollowGameLanguage;
if (ImGui.Checkbox(Language.OtherGameLanguage, ref useGameLanguage)) {
this.Plugin.Config.FollowGameLanguage = useGameLanguage;
this.Plugin.Config.Save();
this.Plugin.ConfigureLanguage();
}
var logFilteredPfs = this.Plugin.Config.LogFilteredPfs;
if (ImGui.Checkbox(Language.LogFilteredPfs, ref logFilteredPfs)) {
this.Plugin.Config.LogFilteredPfs = logFilteredPfs;
this.Plugin.Config.Save();
}
var logFilteredMessages = this.Plugin.Config.LogFilteredChat;
if (ImGui.Checkbox(Language.LogFilteredMessages, ref logFilteredMessages)) {
this.Plugin.Config.LogFilteredChat = logFilteredMessages;
this.Plugin.Config.Save();
}
ImGui.EndTabItem();
}
}
}

View File

@ -1,73 +1,118 @@
using Dalamud.Game.Chat;
using Dalamud.Game.Chat.SeStringHandling;
using Dalamud.Game.Chat.SeStringHandling.Payloads;
using Newtonsoft.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services;
using Lumina.Excel.GeneratedSheets;
using NoSoliciting.Ml;
#if DEBUG
using System.Text;
#endif
namespace NoSoliciting {
[Serializable]
public class Message {
public Guid Id { get; private set; }
public uint DefinitionsVersion { get; private set; }
public DateTime Timestamp { get; private set; }
public ChatType ChatType { get; private set; }
public SeString Sender { get; private set; }
public SeString Content { get; private set; }
public string FilterReason { get; private set; }
public Guid Id { get; }
public Message(Definitions defs, ChatType type, SeString sender, SeString content, string reason) {
if (defs == null) {
throw new ArgumentNullException(nameof(defs), "Definitions cannot be null");
}
[JsonIgnore]
public uint ActorId { get; }
public uint? ModelVersion { get; }
public DateTime Timestamp { get; }
public ChatType ChatType { get; }
public SeString Sender { get; }
public SeString Content { get; }
public IEnumerable<MessageCategory> EnabledSnapshot { get; }
public MessageCategory? Classification { get; }
public bool Custom { get; }
public bool ItemLevel { get; }
public bool Filtered => this.Custom || this.ItemLevel || this.Classification != null;
public string? FilterReason => this.Custom
? "custom"
: this.ItemLevel
? "ilvl"
: this.Classification?.Name();
internal Message(uint? defsVersion, ChatType type, uint actorId, SeString sender, SeString content, MessageCategory? classification, bool custom, bool ilvl, IEnumerable<MessageCategory> enabledSnapshot) {
this.Id = Guid.NewGuid();
this.DefinitionsVersion = defs.Version;
this.ModelVersion = defsVersion;
this.Timestamp = DateTime.Now;
this.ChatType = type;
this.ActorId = actorId;
this.Sender = sender;
this.Content = content;
this.FilterReason = reason;
this.Classification = classification;
this.Custom = custom;
this.ItemLevel = ilvl;
this.EnabledSnapshot = enabledSnapshot;
}
public Message(Definitions defs, ChatType type, string sender, string content, string reason) : this(
defs,
type,
new SeString(new Payload[] { new TextPayload(sender) }),
new SeString(new Payload[] { new TextPayload(content) }),
reason
) { }
[Serializable]
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
private class JsonMessage {
public Guid Id { get; set; }
public uint DefinitionsVersion { get; set; }
public uint ReportVersion { get; } = 2;
public uint ModelVersion { get; set; }
public DateTime Timestamp { get; set; }
public ushort Type { get; set; }
// note: cannot use byte[] because Newtonsoft thinks it's a good idea to always base64 byte[]
// and I don't want to write a custom converter to overwrite their stupiditiy
// and I don't want to write a custom converter to overwrite their stupidity
public List<byte> Sender { get; set; }
public List<byte> Content { get; set; }
public string Reason { get; set; }
public string? Reason { get; set; }
public string? SuggestedClassification { get; set; }
}
public string ToJson() {
JsonMessage msg = new JsonMessage {
Id = this.Id,
DefinitionsVersion = this.DefinitionsVersion,
public string? ToJson(string suggested) {
if (this.ModelVersion == null) {
return null;
}
var msg = new JsonMessage {
ModelVersion = this.ModelVersion.Value,
Timestamp = this.Timestamp,
Type = (ushort)this.ChatType,
Type = (ushort) this.ChatType,
Sender = this.Sender.Encode().ToList(),
Content = this.Content.Encode().ToList(),
Reason = this.FilterReason,
Reason = this.Custom
? "custom"
: this.ItemLevel
? "ilvl"
: (this.Classification ?? MessageCategory.Normal).ToModelName(),
SuggestedClassification = suggested,
};
return JsonConvert.SerializeObject(msg, new JsonSerializerSettings {
TypeNameHandling = TypeNameHandling.None,
});
}
#if DEBUG
public StringBuilder ToCsv(StringBuilder? builder = null) {
builder ??= new StringBuilder();
builder.Append(this.Classification?.ToModelName());
builder.Append(',');
builder.Append((int) this.ChatType);
builder.Append(",\"");
builder.Append(this.Content.TextValue
.Replace("\"", "\"\"")
.Replace("\r", " "));
builder.Append('"');
return builder;
}
#endif
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1028:Enum Storage should be Int32")]
@ -156,15 +201,35 @@ namespace NoSoliciting {
}
public static class ChatTypeExt {
//private const ushort THRESHOLD = 127;
private const ushort CLEAR_7 = ~(~0 << 7);
private const ushort Clear7 = ~(~0 << 7);
public static byte LogKind(this ChatType type) => type switch {
ChatType.TellIncoming => (byte) ChatType.TellOutgoing,
_ => (byte) type,
};
public static string Name(this ChatType type, IDataManager data) {
switch (type) {
case ChatType.None:
return "Party Finder";
case ChatType.TellIncoming:
return "Tell (Incoming)";
case ChatType.TellOutgoing:
return "Tell (Outgoing)";
case ChatType.CrossParty:
return "Party (Cross-world)";
}
var lf = data.GetExcelSheet<LogFilter>()!.FirstOrDefault(lf => lf.LogKind == type.LogKind());
return lf?.Name?.ToString() ?? type.ToString();
}
public static ChatType FromCode(ushort code) {
return (ChatType)(code & CLEAR_7);
return (ChatType) (code & Clear7);
}
public static ChatType FromDalamud(XivChatType type) {
return ChatTypeExt.FromCode((ushort)type);
return FromCode((ushort) type);
}
public static bool IsBattle(this ChatType type) {

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

@ -0,0 +1,13 @@
using System;
namespace NoSoliciting.Ml {
[Serializable]
public class Manifest {
public uint Version { get; set; }
public Uri ModelUrl { get; set; }
public string ModelHash { get; set; }
public Uri ReportUrl { get; set; }
public byte[] Hash() => Convert.FromBase64String(this.ModelHash);
}
}

231
NoSoliciting/Ml/MlFilter.cs Normal file
View File

@ -0,0 +1,231 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using NoSoliciting.Interface;
using NoSoliciting.Resources;
using YamlDotNet.Core;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace NoSoliciting.Ml {
public class MlFilter : IDisposable {
public static string? LastError { get; private set; }
private const string ManifestName = "manifest.yaml";
private const string ModelName = "model.zip";
#if DEBUG
private const string Url = "http://localhost:8000/manifest.yaml";
#else
private const string Url = "https://annaclemens.io/assets/nosol/ml/manifest.yaml";
#endif
public uint Version { get; }
public Uri ReportUrl { get; }
private IClassifier Classifier { get; }
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.Classify(channel, message);
var category = MessageCategoryExt.FromString(prediction);
if (category != null) {
return (MessageCategory) category;
}
Plugin.Log.Warning($"Unknown message category: {prediction}");
return MessageCategory.Normal;
}
public static async Task<MlFilter?> Load(Plugin plugin, bool showWindow) {
plugin.MlStatus = MlFilterStatus.DownloadingManifest;
// download and parse the remote manifest
var manifest = await DownloadManifest();
if (manifest == null) {
Plugin.Log.Warning("Could not download manifest. Will attempt to fall back on cached version.");
}
// model zip file data
byte[]? data = null;
// load the cached manifest
var localManifest = LoadCachedManifest(plugin);
// if there is a cached manifest and we either couldn't download/parse the remote OR the cached version is the same as remote version
if (localManifest != null && (manifest?.Item1 == null || localManifest.Version == manifest.Value.manifest.Version)) {
try {
// try to reach the cached model
data = await File.ReadAllBytesAsync(CachedFilePath(plugin, ModelName));
// set the manifest to our local one and an empty string for the source
manifest ??= (localManifest, string.Empty);
} catch (IOException) {
// ignored
}
}
// if there is source for the manifest
if (!string.IsNullOrEmpty(manifest?.source)) {
plugin.MlStatus = MlFilterStatus.DownloadingModel;
// download the model if necessary
data ??= await DownloadModel(manifest!.Value.manifest!.ModelUrl);
}
// give up if we couldn't get any data at this point
if (data == null) {
plugin.MlStatus = MlFilterStatus.Uninitialised;
return null;
}
// validate checksum
var retries = 0;
const int maxRetries = 3;
var correctHash = manifest!.Value.manifest!.Hash();
using (var sha = SHA256.Create()) {
var hash = sha.ComputeHash(data);
while (!hash.SequenceEqual(correctHash) && retries < maxRetries) {
Plugin.Log.Warning($"Model checksum did not match. Redownloading (attempt {retries + 1}/{maxRetries})");
retries += 1;
data = await DownloadModel(manifest!.Value.manifest!.ModelUrl);
if (data != null) {
hash = sha.ComputeHash(data);
}
}
}
// give up if we couldn't get any data at this point
if (data == null) {
plugin.MlStatus = MlFilterStatus.Uninitialised;
return null;
}
plugin.MlStatus = MlFilterStatus.Initialising;
// if there is source for the manifest
if (!string.IsNullOrEmpty(manifest!.Value.source)) {
// update the cached files
UpdateCachedFile(plugin, ModelName, data);
UpdateCachedFile(plugin, ManifestName, Encoding.UTF8.GetBytes(manifest.Value.source));
}
// initialise the classifier
var classifier = new Classifier();
classifier.Initialise(data);
return new MlFilter(
manifest.Value.manifest!.Version,
manifest.Value.manifest!.ReportUrl,
classifier
);
}
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) {
Plugin.Log.Error("Could not download newest model.");
Plugin.Log.Error(e.ToString());
LastError = e.Message;
return null;
}
}
private static string CachedFilePath(Plugin plugin, string name) {
var pluginFolder = plugin.Interface.ConfigDirectory.ToString();
Directory.CreateDirectory(pluginFolder);
return Path.Combine(pluginFolder, name);
}
private static async void UpdateCachedFile(Plugin plugin, string name, byte[] data) {
var cachePath = CachedFilePath(plugin, name);
var file = File.Create(cachePath);
await file.WriteAsync(data, 0, data.Length);
await file.FlushAsync();
await file.DisposeAsync();
}
private static async Task<(Manifest manifest, string source)?> DownloadManifest() {
try {
using var client = new WebClient();
var data = await client.DownloadStringTaskAsync(Url);
LastError = null;
return (LoadYaml<Manifest>(data), data);
} catch (Exception e) when (e is WebException or YamlException) {
Plugin.Log.Error("Could not download newest model manifest.");
Plugin.Log.Error(e.ToString());
LastError = e.Message;
return null;
}
}
private static Manifest? LoadCachedManifest(Plugin plugin) {
var manifestPath = CachedFilePath(plugin, ManifestName);
if (!File.Exists(manifestPath)) {
return null;
}
string data;
try {
data = File.ReadAllText(manifestPath);
} catch (IOException) {
return null;
}
try {
return LoadYaml<Manifest>(data);
} catch (YamlException) {
return null;
}
}
private static T LoadYaml<T>(string data) {
var de = new DeserializerBuilder()
.WithNamingConvention(UnderscoredNamingConvention.Instance)
.IgnoreUnmatchedProperties()
.Build();
return de.Deserialize<T>(data);
}
public void Dispose() {
this.Classifier.Dispose();
}
}
public enum MlFilterStatus {
Uninitialised,
Preparing,
DownloadingManifest,
DownloadingModel,
Initialising,
Initialised,
}
public static class MlFilterStatusExt {
public static string Description(this MlFilterStatus status) {
return status switch {
MlFilterStatus.Uninitialised => Language.ModelStatusUninitialised,
MlFilterStatus.Preparing => Language.ModelStatusPreparing,
MlFilterStatus.DownloadingManifest => Language.ModelStatusDownloadingManifest,
MlFilterStatus.DownloadingModel => Language.ModelStatusDownloadingModel,
MlFilterStatus.Initialising => Language.ModelStatusInitialising,
MlFilterStatus.Initialised => Language.ModelStatusInitialised,
_ => status.ToString(),
};
}
}
}

110
NoSoliciting/Ml/Models.cs Normal file
View File

@ -0,0 +1,110 @@
using System;
using NoSoliciting.Resources;
namespace NoSoliciting.Ml {
public enum MessageCategory {
Trade,
FreeCompany,
Normal,
Phishing,
RmtContent,
RmtGil,
Roleplaying,
Static,
Community,
StaticSub,
Fluff,
}
public static class MessageCategoryExt {
public static readonly MessageCategory[] UiOrder = {
MessageCategory.Trade,
MessageCategory.FreeCompany,
MessageCategory.Phishing,
MessageCategory.RmtContent,
MessageCategory.RmtGil,
MessageCategory.Roleplaying,
MessageCategory.Static,
MessageCategory.StaticSub,
MessageCategory.Community,
MessageCategory.Fluff,
};
public static MessageCategory? FromString(string? category) => category switch {
"TRADE" => MessageCategory.Trade,
"FC" => MessageCategory.FreeCompany,
"NORMAL" => MessageCategory.Normal,
"PHISH" => MessageCategory.Phishing,
"RMT_C" => MessageCategory.RmtContent,
"RMT_G" => MessageCategory.RmtGil,
"RP" => MessageCategory.Roleplaying,
"STATIC" => MessageCategory.Static,
"COMMUNITY" => MessageCategory.Community,
"STATIC_SUB" => MessageCategory.StaticSub,
"FLUFF" => MessageCategory.Fluff,
_ => null,
};
#if DEBUG
public static MessageCategory? FromName(string? category) => category switch {
"Trade ads" => MessageCategory.Trade,
"Free Company ads" => MessageCategory.FreeCompany,
"Normal messages" => MessageCategory.Normal,
"Phishing messages" => MessageCategory.Phishing,
"RMT (content)" => MessageCategory.RmtContent,
"RMT (gil)" => MessageCategory.RmtGil,
"Roleplaying ads" => MessageCategory.Roleplaying,
"Static recruitment" => MessageCategory.Static,
"Community ads" => MessageCategory.Community,
"Static substitutes" => MessageCategory.StaticSub,
"Fluff" => MessageCategory.Fluff,
_ => null,
};
#endif
public static string ToModelName(this MessageCategory category) => category switch {
MessageCategory.Trade => "TRADE",
MessageCategory.FreeCompany => "FC",
MessageCategory.Normal => "NORMAL",
MessageCategory.Phishing => "PHISH",
MessageCategory.RmtContent => "RMT_C",
MessageCategory.RmtGil => "RMT_G",
MessageCategory.Roleplaying => "RP",
MessageCategory.Static => "STATIC",
MessageCategory.Community => "COMMUNITY",
MessageCategory.StaticSub => "STATIC_SUB",
MessageCategory.Fluff => "FLUFF",
_ => throw new ArgumentException("Invalid category", nameof(category)),
};
public static string Name(this MessageCategory category) => category switch {
MessageCategory.Trade => Language.TradeCategory,
MessageCategory.FreeCompany => Language.FreeCompanyCategory,
MessageCategory.Normal => Language.NormalCategory,
MessageCategory.Phishing => Language.PhishingCategory,
MessageCategory.RmtContent => Language.RmtContentCategory,
MessageCategory.RmtGil => Language.RmtGilCategory,
MessageCategory.Roleplaying => Language.RoleplayingCategory,
MessageCategory.Static => Language.StaticCategory,
MessageCategory.Community => Language.CommunityCategory,
MessageCategory.StaticSub => Language.StaticSubCategory,
MessageCategory.Fluff => Language.FluffCategory,
_ => throw new ArgumentException("Invalid category", nameof(category)),
};
public static string Description(this MessageCategory category) => category switch {
MessageCategory.Trade => Language.TradeDescription,
MessageCategory.FreeCompany => Language.FreeCompanyDescription,
MessageCategory.Normal => Language.NormalDescription,
MessageCategory.Phishing => Language.PhishingDescription,
MessageCategory.RmtContent => Language.RmtContentDescription,
MessageCategory.RmtGil => Language.RmtGilDescription,
MessageCategory.Roleplaying => Language.RoleplayingDescription,
MessageCategory.Static => Language.StaticDescription,
MessageCategory.Community => Language.CommunityDescription,
MessageCategory.StaticSub => Language.StaticSubDescription,
MessageCategory.Fluff => Language.FluffDescription,
_ => throw new ArgumentException("Invalid category", nameof(category)),
};
}
}

190
NoSoliciting/NoSoliciting.csproj Normal file → Executable file
View File

@ -1,122 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Microsoft.CodeAnalysis.FxCopAnalyzers.2.9.6\build\Microsoft.CodeAnalysis.FxCopAnalyzers.props" Condition="Exists('..\packages\Microsoft.CodeAnalysis.FxCopAnalyzers.2.9.6\build\Microsoft.CodeAnalysis.FxCopAnalyzers.props')" />
<Import Project="..\packages\Microsoft.NetFramework.Analyzers.2.9.6\build\Microsoft.NetFramework.Analyzers.props" Condition="Exists('..\packages\Microsoft.NetFramework.Analyzers.2.9.6\build\Microsoft.NetFramework.Analyzers.props')" />
<Import Project="..\packages\Microsoft.NetCore.Analyzers.2.9.6\build\Microsoft.NetCore.Analyzers.props" Condition="Exists('..\packages\Microsoft.NetCore.Analyzers.2.9.6\build\Microsoft.NetCore.Analyzers.props')" />
<Import Project="..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\build\Microsoft.CodeQuality.Analyzers.props" Condition="Exists('..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\build\Microsoft.CodeQuality.Analyzers.props')" />
<Import Project="..\packages\Microsoft.CodeAnalysis.VersionCheckAnalyzer.2.9.6\build\Microsoft.CodeAnalysis.VersionCheckAnalyzer.props" Condition="Exists('..\packages\Microsoft.CodeAnalysis.VersionCheckAnalyzer.2.9.6\build\Microsoft.CodeAnalysis.VersionCheckAnalyzer.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{E4C12987-9064-4788-8783-BE418B2C0142}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>NoSoliciting</RootNamespace>
<AssemblyName>NoSoliciting</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Dalamud">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\Dalamud.dll</HintPath>
</Reference>
<Reference Include="ImGui.NET, Version=1.72.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\ImGui.NET.dll</HintPath>
</Reference>
<Reference Include="ImGuiScene, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\ImGuiScene.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="YamlDotNet, Version=8.0.0.0, Culture=neutral, PublicKeyToken=ec19458f3c15af5e, processorArchitecture=MSIL">
<HintPath>..\packages\YamlDotNet.8.1.2\lib\net45\YamlDotNet.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Definitions.cs" />
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="Message.cs" />
<Compile Include="PFPacket.cs" />
<Compile Include="Plugin.cs" />
<Compile Include="PluginConfiguration.cs" />
<Compile Include="PluginUI.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Filter.Chat.cs" />
<Compile Include="Filter.cs" />
<Compile Include="Filter.PartyFinder.cs" />
<Compile Include="FilterUtil.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="definitions.yaml" />
<None Include="NoSoliciting.json" />
<None Include="package.sh" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.VersionCheckAnalyzer.2.9.6\analyzers\dotnet\cs\Microsoft.CodeAnalysis.VersionCheckAnalyzer.resources.dll" />
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.VersionCheckAnalyzer.2.9.6\analyzers\dotnet\Microsoft.CodeAnalysis.VersionCheckAnalyzer.dll" />
<Analyzer Include="..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\analyzers\dotnet\cs\Humanizer.dll" />
<Analyzer Include="..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\analyzers\dotnet\cs\Microsoft.CodeQuality.Analyzers.dll" />
<Analyzer Include="..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\analyzers\dotnet\cs\Microsoft.CodeQuality.CSharp.Analyzers.dll" />
<Analyzer Include="..\packages\Microsoft.NetCore.Analyzers.2.9.6\analyzers\dotnet\cs\Microsoft.NetCore.Analyzers.dll" />
<Analyzer Include="..\packages\Microsoft.NetCore.Analyzers.2.9.6\analyzers\dotnet\cs\Microsoft.NetCore.CSharp.Analyzers.dll" />
<Analyzer Include="..\packages\Microsoft.NetFramework.Analyzers.2.9.6\analyzers\dotnet\cs\Microsoft.NetFramework.Analyzers.dll" />
<Analyzer Include="..\packages\Microsoft.NetFramework.Analyzers.2.9.6\analyzers\dotnet\cs\Microsoft.NetFramework.CSharp.Analyzers.dll" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<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>
<Error Condition="!Exists('..\packages\Microsoft.CodeAnalysis.VersionCheckAnalyzer.2.9.6\build\Microsoft.CodeAnalysis.VersionCheckAnalyzer.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.CodeAnalysis.VersionCheckAnalyzer.2.9.6\build\Microsoft.CodeAnalysis.VersionCheckAnalyzer.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\build\Microsoft.CodeQuality.Analyzers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\build\Microsoft.CodeQuality.Analyzers.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.NetCore.Analyzers.2.9.6\build\Microsoft.NetCore.Analyzers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.NetCore.Analyzers.2.9.6\build\Microsoft.NetCore.Analyzers.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.NetFramework.Analyzers.2.9.6\build\Microsoft.NetFramework.Analyzers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.NetFramework.Analyzers.2.9.6\build\Microsoft.NetFramework.Analyzers.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.CodeAnalysis.FxCopAnalyzers.2.9.6\build\Microsoft.CodeAnalysis.FxCopAnalyzers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.CodeAnalysis.FxCopAnalyzers.2.9.6\build\Microsoft.CodeAnalysis.FxCopAnalyzers.props'))" />
</Target>
</Project>
<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>$(DalamudLibPath)\Dalamud.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ImGui.NET">
<HintPath>$(DalamudLibPath)\ImGui.NET.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Lumina">
<HintPath>$(DalamudLibPath)\Lumina.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Lumina.Excel">
<HintPath>$(DalamudLibPath)\Lumina.Excel.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>$(DalamudLibPath)\Newtonsoft.Json.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<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 Update="Resources\Language.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Language.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NoSoliciting.Interface\NoSoliciting.Interface.csproj"/>
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\Language.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>NoSoliciting.resx</DependentUpon>
</Compile>
</ItemGroup>
</Project>

View File

@ -1,10 +0,0 @@
{
"Author": "ascclemens",
"Name": "NoSoliciting",
"Description": "Hides RMT in chat and the party finder. /prmt",
"InternalName": "NoSoliciting",
"AssemblyVersion": "1.4.6",
"RepoUrl": "https://git.sr.ht/~jkcclemens/NoSoliciting",
"ApplicableVersion": "any",
"DalamudApiLevel": 1
}

View File

@ -0,0 +1,19 @@
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
for the following:
- RMT (both gil and content)
- FC ads
- RP ads
- Phishing messages
- Static recruitment
- Trade ads
- Community ads
- Any PF with an item level over the max
repo_url: https://git.anna.lgbt/anna/NoSoliciting
# Use higher priority to filter messages before other plugins get them.
load_priority: 100

View File

@ -1,115 +0,0 @@
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace NoSoliciting {
public static class PacketInfo {
public static readonly int packetSize = Marshal.SizeOf<PFPacket>();
}
[StructLayout(LayoutKind.Sequential)]
public struct PFPacket {
private readonly int unk0;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
private readonly byte[] padding1;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public PFListing[] listings;
}
[StructLayout(LayoutKind.Sequential)]
public struct PFListing {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
private readonly byte[] header1;
internal readonly uint id;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
private readonly byte[] header2;
private readonly uint unknownInt1;
private readonly ushort unknownShort1;
private readonly ushort unknownShort2;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
private readonly byte[] header3;
internal readonly byte category;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
private readonly byte[] header4;
internal readonly ushort duty;
internal readonly byte dutyType;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]
private readonly byte[] header5;
internal readonly ushort world;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
private readonly byte[] header6;
internal readonly byte objective;
internal readonly byte beginnersWelcome;
internal readonly byte conditions;
internal readonly byte dutyFinderSettings;
internal readonly byte lootRules;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
internal readonly byte[] header7; // all zero in every pf I've examined
internal readonly uint lastPatchHotfixTimestamp; // last time the servers were restarted?
internal readonly ushort secondsRemaining;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
private readonly byte[] header8; // 00 00 01 00 00 00 in every pf I've examined
internal readonly ushort minimumItemLevel;
internal readonly ushort homeWorld;
internal readonly ushort currentWorld;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
private readonly byte[] header9; // 02 XX 01 00 in every pf I've examined
internal readonly byte searchArea;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
private readonly byte[] header10; // 00 01 00 00 00 for every pf except alliance raids where it's 01 03 00 00 00 (second byte # parties?)
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
internal readonly uint[] slots;
private readonly uint job; // job started as?
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
private readonly byte[] header11; // all zero in every pf I've examined
// Note that ByValTStr will not work here because the strings are UTF-8 and there's only a CharSet for UTF-16 in C#.
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
private readonly byte[] name;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 192)]
private readonly byte[] description;
// 128 (0x80) before name and desc
// 160 (0xA0) with name (32 bytes/0x20)
// 352 (0x160) with both (192 bytes/0xC0)
private static string HandleString(byte[] bytes) {
byte[] nonNull = bytes.TakeWhile(b => b != 0).ToArray();
return Encoding.UTF8.GetString(nonNull);
}
internal string Name() {
return HandleString(this.name);
}
internal string Description() {
return HandleString(this.description);
}
internal bool IsNull() {
// a valid party finder must have at least one slot set
return this.slots.All(slot => slot == 0);
}
}
}

View File

@ -1,106 +1,177 @@
using Dalamud.Game.Command;
using Dalamud.Plugin;
using Dalamud.Plugin;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Dalamud;
using Dalamud.ContextMenu;
using Dalamud.IoC;
using Dalamud.Plugin.Services;
using NoSoliciting.Interface;
using NoSoliciting.Ml;
using NoSoliciting.Resources;
using XivCommon;
namespace NoSoliciting {
public partial class Plugin : IDalamudPlugin {
private bool disposedValue;
// ReSharper disable once ClassNeverInstantiated.Global
public class Plugin : IDalamudPlugin {
private bool _disposedValue;
public string Name => "NoSoliciting";
internal static string Name => "NoSoliciting";
private PluginUI ui;
private Filter filter;
private Filter Filter { get; }
public DalamudPluginInterface Interface { get; private set; }
public PluginConfiguration Config { get; private set; }
public Definitions Definitions { get; private set; }
[PluginService]
internal static IPluginLog Log { get; private set; } = null!;
private readonly List<Message> messageHistory = new List<Message>();
public IReadOnlyCollection<Message> MessageHistory { get => this.messageHistory; }
[PluginService]
internal DalamudPluginInterface Interface { get; init; } = null!;
private readonly List<Message> partyFinderHistory = new List<Message>();
public IReadOnlyCollection<Message> PartyFinderHistory { get => this.partyFinderHistory; }
[PluginService]
private IClientState ClientState { get; init; } = null!;
public void Initialize(DalamudPluginInterface pluginInterface) {
this.Interface = pluginInterface ?? throw new ArgumentNullException(nameof(pluginInterface), "DalamudPluginInterface cannot be null");
this.ui = new PluginUI(this);
[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();
internal IEnumerable<Message> MessageHistory => this._messageHistory;
private readonly List<Message> _partyFinderHistory = new();
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 Plugin() {
string path = Environment.GetEnvironmentVariable("PATH")!;
string newPath = Path.GetDirectoryName(this.AssemblyLocation)!;
Environment.SetEnvironmentVariable("PATH", $"{path};{newPath}");
this.Config = this.Interface.GetPluginConfig() as PluginConfiguration ?? new PluginConfiguration();
this.Config.Initialise(this.Interface);
this.UpdateDefinitions();
this.ConfigureLanguage();
this.Interface.LanguageChanged += this.OnLanguageUpdate;
this.filter = new Filter(this);
this.Common = new XivCommonBase(this.Interface, Hooks.PartyFinderListings);
this.DalamudContextMenu = new DalamudContextMenu(this.Interface);
this.Ui = new PluginUi(this);
this.Commands = new Commands(this);
this.ContextMenu = new ContextMenu(this);
this.Filter = new Filter(this);
this.InitialiseMachineLearning(false);
// pre-compute the max ilvl to prevent stutter
Task.Run(async () => {
while (!this.Interface.Data.IsDataReady) {
await Task.Delay(1_000).ConfigureAwait(true);
}
FilterUtil.MaxItemLevelAttainable(this.Interface.Data);
});
this.Interface.Framework.Gui.Chat.OnCheckMessageHandled += this.filter.OnChat;
this.Interface.UiBuilder.OnBuildUi += this.ui.Draw;
this.Interface.UiBuilder.OnOpenConfigUi += this.ui.OpenSettings;
this.Interface.CommandManager.AddHandler("/prmt", new CommandInfo(OnCommand) {
HelpMessage = "Opens the NoSoliciting configuration",
});
try {
FilterUtil.MaxItemLevelAttainable(this.DataManager);
} catch (Exception ex) {
Plugin.Log.Error(ex, "Exception while computing max item level");
}
}
internal void UpdateDefinitions() {
Task.Run(async () => {
Definitions defs = await Definitions.UpdateAndCache(this).ConfigureAwait(true);
// this shouldn't be possible, but what do I know
if (defs != null) {
defs.Initialise(this);
this.Definitions = defs;
Definitions.LastUpdate = DateTime.Now;
}
});
protected virtual void Dispose(bool disposing) {
if (this._disposedValue) {
return;
}
if (disposing) {
this.Filter.Dispose();
this.MlFilter?.Dispose();
this.ContextMenu.Dispose();
this.Commands.Dispose();
this.Ui.Dispose();
this.DalamudContextMenu.Dispose();
this.Common.Dispose();
this.Interface.LanguageChanged -= this.OnLanguageUpdate;
}
this._disposedValue = true;
}
public void OnCommand(string command, string args) {
this.ui.OpenSettings(null, null);
private void OnLanguageUpdate(string langCode) {
this.ConfigureLanguage(langCode);
}
internal void ConfigureLanguage(string? langCode = null) {
if (this.Config.FollowGameLanguage) {
langCode = this.ClientState.ClientLanguage switch {
ClientLanguage.Japanese => "ja",
ClientLanguage.English => "en",
ClientLanguage.German => "de",
ClientLanguage.French => "fr",
_ => throw new ArgumentOutOfRangeException(nameof(this.ClientState.ClientLanguage), "Unknown ClientLanguage"),
};
}
langCode ??= this.Interface.UiLanguage;
// I don't fucking trust this. Not since last time.
// ReSharper disable once ConstantNullCoalescingCondition
Language.Culture = new CultureInfo(langCode ?? "en");
}
internal void InitialiseMachineLearning(bool showWindow) {
if (this.MlFilter != null) {
return;
}
Task.Run(async () => this.MlFilter = await MlFilter.Load(this, showWindow))
.ContinueWith(e => {
if (e.IsFaulted) {
this.MlStatus = MlFilterStatus.Uninitialised;
return;
}
this.MlStatus = MlFilterStatus.Initialised;
Log.Info("Machine learning model loaded");
});
}
public void AddMessageHistory(Message message) {
this.messageHistory.Insert(0, message);
this._messageHistory.Insert(0, message);
while (this.messageHistory.Count > 250) {
this.messageHistory.RemoveAt(this.messageHistory.Count - 1);
while (this._messageHistory.Count > 250) {
this._messageHistory.RemoveAt(this._messageHistory.Count - 1);
}
}
public void ClearPartyFinderHistory() {
this.partyFinderHistory.Clear();
this._partyFinderHistory.Clear();
}
public void AddPartyFinderHistory(Message message) {
this.partyFinderHistory.Add(message);
}
protected virtual void Dispose(bool disposing) {
if (!this.disposedValue) {
if (disposing) {
this.filter.Dispose();
this.Interface.Framework.Gui.Chat.OnCheckMessageHandled -= this.filter.OnChat;
this.Interface.UiBuilder.OnBuildUi -= this.ui.Draw;
this.Interface.UiBuilder.OnOpenConfigUi -= this.ui.OpenSettings;
this.Interface.CommandManager.RemoveHandler("/prmt");
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
disposedValue = true;
}
this._partyFinderHistory.Add(message);
}
public void Dispose() {
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
this.Dispose(true);
GC.SuppressFinalize(this);
}
}

View File

@ -5,61 +5,158 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using NoSoliciting.Ml;
namespace NoSoliciting {
[Serializable]
public class PluginConfiguration : IPluginConfiguration {
[NonSerialized]
private DalamudPluginInterface pi;
public static readonly PluginConfiguration Default = new();
public int Version { get; set; } = 1;
private DalamudPluginInterface Interface { get; set; } = null!;
[Obsolete("Use FilterStatus")]
public bool FilterChat { get; set; } = true;
[Obsolete("Use FilterStatus")]
public bool FilterFCRecruitments { get; set; } = false;
[Obsolete("Use FilterStatus")]
public bool FilterChatRPAds { get; set; } = false;
public int Version { get; set; } = 2;
[Obsolete("Use FilterStatus")]
public bool FilterPartyFinder { get; set; } = true;
[Obsolete("Use FilterStatus")]
public bool FilterPartyFinderRPAds { get; set; } = false;
public bool AdvancedMode { get; set; }
public Dictionary<string, bool> FilterStatus { get; private set; } = new Dictionary<string, bool>();
public bool CustomChatFilter { get; set; }
public List<string> ChatSubstrings { get; } = new();
public List<string> ChatRegexes { get; } = new();
public bool AdvancedMode { get; set; } = false;
public bool CustomChatFilter { get; set; } = false;
public List<string> ChatSubstrings { get; } = new List<string>();
public List<string> ChatRegexes { get; } = new List<string>();
[JsonIgnore]
public List<Regex> CompiledChatRegexes { get; private set; } = new List<Regex>();
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();
public bool CustomPFFilter { get; set; } = false;
public List<string> PFSubstrings { get; } = new List<string>();
public List<string> PFRegexes { get; } = new List<string>();
[JsonIgnore]
public List<Regex> CompiledPFRegexes { get; private set; } = new List<Regex>();
public List<Regex> CompiledPfRegexes { get; private set; } = new();
public bool FilterHugeItemLevelPFs { get; set; } = false;
public bool FilterHugeItemLevelPFs { get; set; }
public bool FollowGameLanguage { get; set; }
public HashSet<MessageCategory> BasicMlFilters { get; set; } = new() {
MessageCategory.RmtGil,
MessageCategory.RmtContent,
MessageCategory.Phishing,
};
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> {
ChatType.None,
ChatType.Shout,
ChatType.Yell,
},
[MessageCategory.FreeCompany] = new HashSet<ChatType> {
ChatType.None,
ChatType.Shout,
ChatType.Yell,
ChatType.TellIncoming,
},
[MessageCategory.Static] = new HashSet<ChatType> {
ChatType.None,
},
[MessageCategory.StaticSub] = new HashSet<ChatType> {
ChatType.None,
},
[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,
},
};
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.pi = pi ?? throw new ArgumentNullException(nameof(pi), "DalamudPluginInterface cannot be null");
this.Interface = pi;
this.CompileRegexes();
}
public void Save() {
this.pi.SavePluginConfig(this);
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
this.CompiledPfRegexes = this.PFRegexes
.Where(reg => !string.IsNullOrWhiteSpace(reg))
.Select(reg => new Regex(reg, RegexOptions.Compiled))
.ToList();
}
internal bool MlEnabledOn(MessageCategory category, ChatType chatType) {
HashSet<ChatType>? filtered;
if (this.AdvancedMode) {
if (!this.MlFilters.TryGetValue(category, out filtered)) {
return false;
}
} else {
// check to see if the user has this category filtered
if (!this.BasicMlFilters.Contains(category)) {
return false;
}
// get the chat types that this category is enabled on by default
if (!Default.MlFilters.TryGetValue(category, out filtered)) {
return false;
}
}
return filtered.Contains(chatType);
}
internal IEnumerable<MessageCategory> CreateFiltersClone() {
var filters = new HashSet<MessageCategory>();
foreach (var category in (MessageCategory[]) Enum.GetValues(typeof(MessageCategory))) {
if (this.AdvancedMode) {
if (this.MlFilters.TryGetValue(category, out var filtered) && filtered.Count > 0) {
filters.Add(category);
}
} else {
if (this.BasicMlFilters.Contains(category)) {
filters.Add(category);
}
}
}
return filters;
}
}
}

View File

@ -1,443 +0,0 @@
using Dalamud.Game.Chat.SeStringHandling;
using Dalamud.Game.Chat.SeStringHandling.Payloads;
using Dalamud.Interface;
using Dalamud.Plugin;
using ImGuiNET;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Numerics;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace NoSoliciting {
public class PluginUI {
private readonly Plugin plugin;
private bool resizeWindow = false;
private ReportStatus lastReportStatus = ReportStatus.None;
private bool _showSettings;
public bool ShowSettings { get => this._showSettings; set => this._showSettings = value; }
private bool _showReporting;
public bool ShowReporting { get => this._showReporting; set => this._showReporting = value; }
public PluginUI(Plugin plugin) {
this.plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "Plugin cannot be null");
}
public void OpenSettings(object sender, EventArgs e) {
this.ShowSettings = true;
}
public void Draw() {
if (this.ShowSettings) {
this.DrawSettings();
}
if (this.ShowReporting) {
this.DrawReportWindow();
}
}
public void DrawSettings() {
if (this.resizeWindow) {
this.resizeWindow = false;
ImGui.SetNextWindowSize(new Vector2(this.plugin.Config.AdvancedMode ? 650 : 0, 0));
} else {
ImGui.SetNextWindowSize(new Vector2(0, 0), ImGuiCond.FirstUseEver);
}
if (ImGui.Begin($"{this.plugin.Name} settings", ref this._showSettings)) {
if (this.plugin.Config.AdvancedMode) {
this.DrawAdvancedSettings();
} else {
this.DrawBasicSettings();
}
ImGui.Separator();
bool advanced = this.plugin.Config.AdvancedMode;
if (ImGui.Checkbox("Advanced mode", ref advanced)) {
this.plugin.Config.AdvancedMode = advanced;
this.plugin.Config.Save();
resizeWindow = true;
}
ImGui.SameLine();
if (ImGui.Button("Show reporting window")) {
this.ShowReporting = true;
}
ImGui.End();
}
}
private void DrawBasicSettings() {
if (this.plugin.Definitions == null) {
return;
}
this.DrawCheckboxes(this.plugin.Definitions.Chat.Values, true, "chat");
ImGui.Separator();
this.DrawCheckboxes(this.plugin.Definitions.PartyFinder.Values, true, "Party Finder");
bool filterHugeItemLevelPFs = this.plugin.Config.FilterHugeItemLevelPFs;
if (ImGui.Checkbox("Filter PFs with item level above maximum", ref filterHugeItemLevelPFs)) {
this.plugin.Config.FilterHugeItemLevelPFs = filterHugeItemLevelPFs;
this.plugin.Config.Save();
}
}
private void DrawAdvancedSettings() {
if (ImGui.BeginTabBar("##nosoliciting-tabs")) {
if (this.plugin.Definitions != null) {
if (ImGui.BeginTabItem("Chat")) {
this.DrawCheckboxes(this.plugin.Definitions.Chat.Values, false, "chat");
bool customChat = this.plugin.Config.CustomChatFilter;
if (ImGui.Checkbox("Enable custom chat filters", ref customChat)) {
this.plugin.Config.CustomChatFilter = customChat;
this.plugin.Config.Save();
}
if (this.plugin.Config.CustomChatFilter) {
List<string> substrings = this.plugin.Config.ChatSubstrings;
List<string> regexes = this.plugin.Config.ChatRegexes;
this.DrawCustom("chat", ref substrings, ref regexes);
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Party Finder")) {
this.DrawCheckboxes(this.plugin.Definitions.PartyFinder.Values, false, "Party Finder");
bool filterHugeItemLevelPFs = this.plugin.Config.FilterHugeItemLevelPFs;
if (ImGui.Checkbox("Enable built-in maximum item level filter", ref filterHugeItemLevelPFs)) {
this.plugin.Config.FilterHugeItemLevelPFs = filterHugeItemLevelPFs;
this.plugin.Config.Save();
}
bool customPF = this.plugin.Config.CustomPFFilter;
if (ImGui.Checkbox("Enable custom Party Finder filters", ref customPF)) {
this.plugin.Config.CustomPFFilter = customPF;
this.plugin.Config.Save();
}
if (this.plugin.Config.CustomPFFilter) {
List<string> substrings = this.plugin.Config.PFSubstrings;
List<string> regexes = this.plugin.Config.PFRegexes;
this.DrawCustom("pf", ref substrings, ref regexes);
}
ImGui.EndTabItem();
}
}
if (ImGui.BeginTabItem("Definitions")) {
if (this.plugin.Definitions != null) {
ImGui.Text($"Version: {this.plugin.Definitions.Version}");
}
if (Definitions.LastUpdate != null) {
ImGui.Text($"Last update: {Definitions.LastUpdate}");
}
string error = Definitions.LastError;
if (error != null) {
ImGui.Text($"Last error: {error}");
}
if (ImGui.Button("Update definitions")) {
this.plugin.UpdateDefinitions();
}
}
ImGui.EndTabBar();
}
}
private void DrawCustom(string name, ref List<string> substrings, ref List<string> regexes) {
ImGui.Columns(2);
ImGui.Text("Substrings to filter");
if (ImGui.BeginChild($"##{name}-substrings", new Vector2(0, 175))) {
for (int i = 0; i < substrings.Count; i++) {
string input = substrings[i];
if (ImGui.InputText($"##{name}-substring-{i}", ref input, 1_000)) {
if (input.Length != 0) {
substrings[i] = input;
}
}
ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button($"{FontAwesomeIcon.Trash.ToIconString()}##{name}-substring-{i}-remove")) {
substrings.RemoveAt(i);
}
ImGui.PopFont();
}
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button($"{FontAwesomeIcon.Plus.ToIconString()}##{name}-substring-add")) {
substrings.Add("");
}
ImGui.PopFont();
ImGui.EndChild();
}
ImGui.NextColumn();
ImGui.Text("Regular expressions to filter");
if (ImGui.BeginChild($"##{name}-regexes", new Vector2(0, 175))) {
for (int i = 0; i < regexes.Count; i++) {
string input = regexes[i];
if (ImGui.InputText($"##{name}-regex-{i}", ref input, 1_000)) {
bool valid = true;
try {
_ = new Regex(input);
} catch (ArgumentException) {
valid = false;
}
if (valid && input.Length != 0) {
regexes[i] = input;
}
}
ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button($"{FontAwesomeIcon.Trash.ToIconString()}##{name}-regex-{i}-remove")) {
regexes.RemoveAt(i);
}
ImGui.PopFont();
}
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button($"{FontAwesomeIcon.Plus.ToIconString()}##{name}-regex-add")) {
regexes.Add("");
}
ImGui.PopFont();
ImGui.EndChild();
}
ImGui.Columns(1);
if (ImGui.Button($"Save filters##{name}-save")) {
this.plugin.Config.Save();
this.plugin.Config.CompileRegexes();
}
}
private void DrawCheckboxes(IEnumerable<Definition> defs, bool basic, string labelFillIn) {
foreach (Definition def in defs) {
this.plugin.Config.FilterStatus.TryGetValue(def.Id, out bool enabled);
string label = basic ? def.Option.Basic : def.Option.Advanced;
label = label.Replace("{}", labelFillIn);
if (ImGui.Checkbox(label, ref enabled)) {
this.plugin.Config.FilterStatus[def.Id] = enabled;
this.plugin.Config.Save();
}
}
}
private void DrawReportWindow() {
ImGui.SetNextWindowSize(new Vector2(1_000, 350), ImGuiCond.FirstUseEver);
if (!ImGui.Begin("NoSoliciting reporting", ref this._showReporting)) {
return;
}
ImGui.Text("Click on one of the entries below to report it to the developer as miscategorised.");
if (this.lastReportStatus != ReportStatus.None) {
string status = this.lastReportStatus switch {
ReportStatus.Failure => "failed to send",
ReportStatus.Successful => "sent successfully",
ReportStatus.InProgress => "sending",
_ => "unknown",
};
ImGui.Text($"Last report status: {status}");
}
ImGui.Separator();
ImGui.Spacing();
if (ImGui.BeginTabBar("##report-tabs")) {
if (ImGui.BeginTabItem("Chat##chat-report")) {
float[] maxSizes = { 0f, 0f, 0f, 0f };
if (ImGui.BeginChild("##chat-messages", new Vector2(-1, -1))) {
ImGui.Columns(5);
AddColumn(maxSizes, "Timestamp", "Channel", "Reason", "Sender", "Message");
ImGui.Separator();
foreach (Message message in this.plugin.MessageHistory) {
if (message.FilterReason != null) {
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(238f / 255f, 71f / 255f, 71f / 255f, 1f));
}
string sender = message.Sender.Payloads
.Where(payload => payload.Type == PayloadType.RawText)
.Cast<TextPayload>()
.Select(payload => payload.Text)
.FirstOrDefault() ?? "";
if (AddColumn(maxSizes, message.Timestamp.ToString(CultureInfo.CurrentCulture), message.ChatType.ToString(), message.FilterReason ?? "", sender, message.Content.TextValue)) {
ImGui.OpenPopup($"###modal-message-{message.Id}");
}
if (message.FilterReason != null) {
ImGui.PopStyleColor();
}
this.SetUpReportModal(message);
}
for (int idx = 0; idx < maxSizes.Length; idx++) {
ImGui.SetColumnWidth(idx, maxSizes[idx] + ImGui.GetStyle().ItemSpacing.X * 2);
}
ImGui.Columns(1);
ImGui.EndChild();
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Party Finder##pf-report")) {
float[] maxSizes = { 0f, 0f, 0f };
if (ImGui.BeginChild("##pf-messages", new Vector2(-1, -1))) {
ImGui.Columns(4);
AddColumn(maxSizes, "Timestamp", "Reason", "Host", "Description");
ImGui.Separator();
foreach (Message message in this.plugin.PartyFinderHistory) {
if (message.FilterReason != null) {
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(238f / 255f, 71f / 255f, 71f / 255f, 1f));
}
string sender = message.Sender.Payloads
.Where(payload => payload.Type == PayloadType.RawText)
.Cast<TextPayload>()
.Select(payload => payload.Text)
.FirstOrDefault() ?? "";
if (AddColumn(maxSizes, message.Timestamp.ToString(CultureInfo.CurrentCulture), message.FilterReason ?? "", sender, message.Content.TextValue)) {
ImGui.OpenPopup($"###modal-message-{message.Id}");
}
if (message.FilterReason != null) {
ImGui.PopStyleColor();
}
this.SetUpReportModal(message);
}
for (int idx = 0; idx < maxSizes.Length; idx++) {
ImGui.SetColumnWidth(idx, maxSizes[idx] + ImGui.GetStyle().ItemSpacing.X * 2);
}
ImGui.Columns(1);
ImGui.EndChild();
}
ImGui.EndTabItem();
}
ImGui.EndTabBar();
}
ImGui.End();
}
private void SetUpReportModal(Message message) {
ImGui.SetNextWindowSize(new Vector2(350, -1));
if (!ImGui.BeginPopupModal($"Report to NoSoliciting###modal-message-{message.Id}")) {
return;
}
ImGui.PushTextWrapPos();
ImGui.Text("Reporting this message will let the developer know that you think this message was incorrectly classified.");
if (message.FilterReason != null) {
ImGui.Text("Specifically, this message WAS filtered but shouldn't have been.");
} else {
ImGui.Text("Specifically, this message WAS NOT filtered but should have been.");
}
ImGui.Separator();
ImGui.Text(message.Content.TextValue);
ImGui.Separator();
if (message.FilterReason == "custom") {
ImGui.TextColored(new Vector4(1f, 0f, 0f, 1f), "You cannot report messages filtered because of a custom filter.");
} else {
if (ImGui.Button("Report")) {
Task.Run(async () => {
string resp = null;
try {
using WebClient client = new WebClient();
this.lastReportStatus = ReportStatus.InProgress;
resp = await client.UploadStringTaskAsync(this.plugin.Definitions.ReportUrl, message.ToJson()).ConfigureAwait(true);
#pragma warning disable CA1031 // Do not catch general exception types
} catch (Exception) { }
#pragma warning restore CA1031 // Do not catch general exception types
this.lastReportStatus = resp == "{\"message\":\"ok\"}" ? ReportStatus.Successful : ReportStatus.Failure;
PluginLog.Log($"Report sent. Response: {resp}");
});
ImGui.CloseCurrentPopup();
}
ImGui.SameLine();
}
if (ImGui.Button("Cancel")) {
ImGui.CloseCurrentPopup();
}
ImGui.PopTextWrapPos();
ImGui.EndPopup();
}
private enum ReportStatus {
None = -1,
Failure = 0,
Successful = 1,
InProgress = 2,
}
private static bool AddColumn(float[] maxSizes, params string[] args) {
bool clicked = false;
for (int i = 0; i < args.Length; i++) {
string arg = args[i];
bool last = i == args.Length - 1;
if (last) {
ImGui.PushTextWrapPos();
}
ImGui.TextUnformatted(arg);
if (last) {
ImGui.PopTextWrapPos();
}
clicked = clicked || ImGui.IsItemClicked();
if (!last) {
maxSizes[i] = Math.Max(maxSizes[i], ImGui.CalcTextSize(arg).X);
}
ImGui.NextColumn();
}
return clicked;
}
}
}

View File

@ -1,38 +0,0 @@
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("NoSoliciting")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("NoSoliciting")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("e4c12987-9064-4788-8783-be418b2c0142")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.4.6")]
[assembly: AssemblyFileVersion("1.4.6")]
[assembly: NeutralResourcesLanguage("en-GB")]

View File

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

View File

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

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

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

View File

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

View File

@ -0,0 +1,319 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="LogFilteredPfs" xml:space="preserve">
<value>Registrar PFs filtrados</value>
</data>
<data name="LogFilteredMessages" xml:space="preserve">
<value>Registrar mensajes filtrados</value>
</data>
<data name="Settings" xml:space="preserve">
<value>Ajustes de {0}</value>
</data>
<data name="AdvancedMode" xml:space="preserve">
<value>Modo avanzado</value>
</data>
<data name="ModelTab" xml:space="preserve">
<value>Modelo</value>
</data>
<data name="ModelTabVersion" xml:space="preserve">
<value>Versión: {0}</value>
</data>
<data name="ModelTabStatus" xml:space="preserve">
<value>Estado del modelo: {0}</value>
</data>
<data name="ModelTabError" xml:space="preserve">
<value>Último error: {0}</value>
</data>
<data name="FiltersTab" xml:space="preserve">
<value>Filtros</value>
</data>
<data name="AdvancedWarning1" xml:space="preserve">
<value>No modifiques los ajustes avanzados salvo que sepas lo que haces.</value>
</data>
<data name="OtherFiltersTab" xml:space="preserve">
<value>Otros filtros</value>
</data>
<data name="ChatFilters" xml:space="preserve">
<value>Filtros de chat</value>
</data>
<data name="EnableCustomChatFilters" xml:space="preserve">
<value>Habilitar filtros de chat personalizados</value>
</data>
<data name="PartyFinderFilters" xml:space="preserve">
<value>Filtros del Party Finder</value>
</data>
<data name="FilterIlvlPfs" xml:space="preserve">
<value>Filtrar PFs con el item level por encima del máximo</value>
</data>
<data name="FilterPrivatePfs" xml:space="preserve">
<value>Aplicar filtros a listados privados del Party Finder</value>
</data>
<data name="SubstringsToFilter" xml:space="preserve">
<value>Subcadenas a filtrar</value>
</data>
<data name="RegularExpressionsToFilter" xml:space="preserve">
<value>Expresiones regulares a filtrar</value>
</data>
<data name="SaveFilters" xml:space="preserve">
<value>Guardar filtros</value>
</data>
<data name="Reporting" xml:space="preserve">
<value>Reportes de {0}</value>
</data>
<data name="ReportHelp" xml:space="preserve">
<value>Haz click en una de las entradas de abajo para informar de que ha sido categorizada erróneamente.</value>
</data>
<data name="ReportStatusFailure" xml:space="preserve">
<value>No se ha podido enviar</value>
</data>
<data name="ReportStatusSuccessful" xml:space="preserve">
<value>Enviado con éxito</value>
</data>
<data name="ReportStatusInProgress" xml:space="preserve">
<value>enviando</value>
</data>
<data name="ReportStatusUnknown" xml:space="preserve">
<value>desconocido</value>
</data>
<data name="ReportChatTab" xml:space="preserve">
<value>Chat</value>
</data>
<data name="ReportColumnTimestamp" xml:space="preserve">
<value>Marca de tiempo</value>
</data>
<data name="ReportColumnChannel" xml:space="preserve">
<value>Canal</value>
</data>
<data name="ReportColumnReason" xml:space="preserve">
<value>Razón</value>
</data>
<data name="ReportColumnSender" xml:space="preserve">
<value>Remitente</value>
</data>
<data name="ReportColumnMessage" xml:space="preserve">
<value>Mensaje</value>
</data>
<data name="ReportPartyFinderTab" xml:space="preserve">
<value>Party Finder</value>
</data>
<data name="ReportColumnHost" xml:space="preserve">
<value>Anfitrión</value>
</data>
<data name="ReportColumnDescription" xml:space="preserve">
<value>Descripción</value>
</data>
<data name="ReportModalTitle" xml:space="preserve">
<value>Informar a {0}</value>
</data>
<data name="ReportModalHelp1" xml:space="preserve">
<value>Al informar de este mensaje le harás saber a la desarrolladora que crees que este mensaje ha sido clasificado de manera errónea.</value>
</data>
<data name="ReportModalHelp2" xml:space="preserve">
<value>NoSoliciting sólo funciona con mensajes en inglés. No informes de mensajes en otros idiomas.</value>
</data>
<data name="ReportModalDisabledCustom" xml:space="preserve">
<value>No puedes informar de mensajes que han sido filtrados debido a un filtro personalizado.</value>
</data>
<data name="ReportModalReport" xml:space="preserve">
<value>Reportar</value>
</data>
<data name="ReportModalCopy" xml:space="preserve">
<value>Copiar al portapapeles</value>
</data>
<data name="ReportModalCancel" xml:space="preserve">
<value>Cancelar</value>
</data>
<data name="TradeCategory" xml:space="preserve">
<value>Anuncios de intercambio</value>
</data>
<data name="FreeCompanyCategory" xml:space="preserve">
<value>Anuncios de Free Company</value>
</data>
<data name="PhishingCategory" xml:space="preserve">
<value>Mensajes de phishing</value>
</data>
<data name="RmtContentCategory" xml:space="preserve">
<value>RMT (contenido)</value>
</data>
<data name="RmtGilCategory" xml:space="preserve">
<value>RMT (guil)</value>
</data>
<data name="RoleplayingCategory" xml:space="preserve">
<value>Anuncios de RP</value>
</data>
<data name="StaticCategory" xml:space="preserve">
<value>Reclutamiento de statics</value>
</data>
<data name="CommunityCategory" xml:space="preserve">
<value>Anuncios de comunidades</value>
</data>
<data name="FreeCompanyDescription" xml:space="preserve">
<value>Anuncios de Free Companies</value>
</data>
<data name="NormalDescription" xml:space="preserve">
<value>Mensajes normales que no deberían ser filtrados</value>
</data>
<data name="PhishingDescription" xml:space="preserve">
<value>Mensajes intentando engañarte para que reveles detalles de tu cuenta con el objetivo de robártela</value>
</data>
<data name="RmtContentDescription" xml:space="preserve">
<value>Intercambio con dinero real relacionado con contenido (también conocidos como vendedores de contenido)</value>
</data>
<data name="RoleplayingDescription" xml:space="preserve">
<value>Anuncios de RP personal, comunidades de rol, establecimientos donde rolear, o cualquier otra cosa relacionada con el RP</value>
</data>
<data name="StaticDescription" xml:space="preserve">
<value>Statics buscando miembros o jugadores buscando static</value>
</data>
<data name="CommunityDescription" xml:space="preserve">
<value>Anuncios de comunidades de uso general, normalmente servidores de Discord</value>
</data>
<data name="ReportToNoSoliciting" xml:space="preserve">
<value>Informar a NoSoliciting</value>
</data>
<data name="ReportToastSuccess" xml:space="preserve">
<value>El listado de Party Finder de {0} ha sido reportado con éxito.</value>
</data>
<data name="ReportToastFailure" xml:space="preserve">
<value>No se ha podido reportar el listado de Party Finder de {0}.</value>
</data>
<data name="OtherGameLanguage" xml:space="preserve">
<value>Usar el idioma del juego en lugar del idioma en el que está Dalamud</value>
</data>
<data name="OtherTab" xml:space="preserve">
<value>Otros</value>
</data>
<data name="ShowReportingWindow" xml:space="preserve">
<value>Mostrar ventana de reportes</value>
</data>
<data name="UpdateModel" xml:space="preserve">
<value>Actualizar modelo</value>
</data>
<data name="AdvancedWarning2" xml:space="preserve">
<value>El modelo de machine learning ha sido entrenado con ciertos canales en mente.</value>
</data>
<data name="EnableCustomPartyFinderFilters" xml:space="preserve">
<value>Habilitar filtros del Party Finder personalizados</value>
</data>
<data name="ReportStatusMessage" xml:space="preserve">
<value>Estado del último reporte: {0}</value>
</data>
<data name="NormalCategory" xml:space="preserve">
<value>Mensajes normales</value>
</data>
<data name="StaticSubCategory" xml:space="preserve">
<value>Sustitutos de statics</value>
</data>
<data name="TradeDescription" xml:space="preserve">
<value>Mensajes que anuncian el intercambio de objetos o servicios a cambio de guiles, como crafters buscando trabajo o gente vendiendo objetos poco comunes</value>
</data>
<data name="RmtGilDescription" xml:space="preserve">
<value>Intercambio de dinero real relacionado con guiles u objetos (también conocidos como bots de RMT o vendeoros)</value>
</data>
<data name="StaticSubDescription" xml:space="preserve">
<value>Statics buscando sustitutos temporales para clears</value>
</data>
<data name="ModelStatusUninitialised" xml:space="preserve">
<value>Sin inicializar</value>
</data>
<data name="ModelStatusPreparing" xml:space="preserve">
<value>Preparando actualización del modelo</value>
</data>
<data name="ModelStatusDownloadingManifest" xml:space="preserve">
<value>Descargando manifiesto del modelo</value>
</data>
<data name="ModelStatusDownloadingModel" xml:space="preserve">
<value>Descargando modelo</value>
</data>
<data name="ModelStatusInitialising" xml:space="preserve">
<value>Inicializando modelo y clasificador</value>
</data>
<data name="ModelStatusInitialised" xml:space="preserve">
<value>Inicializado</value>
</data>
<data name="ReportModalSuggestedClassification" xml:space="preserve">
<value>¿Cómo crees que se debería haber clasificado este mensaje?</value>
</data>
<data name="ReportModalDisabledBadModel" xml:space="preserve">
<value>No puedes reportar este mensaje porque tu modelo ML no estaba funcionando correctamente cuando viste este mensaje.</value>
</data>
<data name="ReportModalDisabledSameClassification" xml:space="preserve">
<value>No puedes reportar este mensaje porque debes elegir una clasificación distinta a la original.</value>
</data>
<data name="ReportModalDisabledItemLevel" xml:space="preserve">
<value>No puedes reportar mensajes filtrados por item level.</value>
</data>
<data name="ReportModalOriginalClassification" xml:space="preserve">
<value>La clasificación original de este mensaje: {0}</value>
</data>
<data name="ReportModalDisabledFilterNotEnabled" xml:space="preserve">
<value>No puedes reportar este mensaje porque no estabas filtrando este tipo de mensaje cuando lo viste.</value>
</data>
<data name="ReportModalClassificationOther" xml:space="preserve">
<value>Otro</value>
</data>
<data name="ReportModalGoToCustomButton" xml:space="preserve">
<value>Ir a filtros personalizados</value>
</data>
<data name="ReportModalClassificationOtherDescription" xml:space="preserve">
<value>Mensajes que no encajan en ninguna otra categoría.</value>
</data>
</root>

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,319 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="OtherGameLanguage" xml:space="preserve">
<value>使用游戏语言而不是 Dalamud 的语言作为插件语言</value>
</data>
<data name="LogFilteredPfs" xml:space="preserve">
<value>启用队员招募过滤日志</value>
</data>
<data name="ShowReportingWindow" xml:space="preserve">
<value>回报错误</value>
</data>
<data name="Settings" xml:space="preserve">
<value>{0} 设置</value>
</data>
<data name="AdvancedMode" xml:space="preserve">
<value>高级模式</value>
</data>
<data name="ModelTab" xml:space="preserve">
<value>机器学习模型</value>
</data>
<data name="ModelTabVersion" xml:space="preserve">
<value>版本:{0}</value>
</data>
<data name="ModelTabStatus" xml:space="preserve">
<value>状态:{0}</value>
</data>
<data name="ModelTabError" xml:space="preserve">
<value>最后一个错误:{0}</value>
</data>
<data name="UpdateModel" xml:space="preserve">
<value>更新模型</value>
</data>
<data name="FiltersTab" xml:space="preserve">
<value>过滤规则</value>
</data>
<data name="AdvancedWarning2" xml:space="preserve">
<value>机器学习模型已经对部分频道进行了训练。</value>
</data>
<data name="OtherFiltersTab" xml:space="preserve">
<value>其他过滤规则</value>
</data>
<data name="ChatFilters" xml:space="preserve">
<value>聊天消息过滤</value>
</data>
<data name="EnableCustomChatFilters" xml:space="preserve">
<value>启用自定义聊天消息过滤</value>
</data>
<data name="PartyFinderFilters" xml:space="preserve">
<value>队员招募过滤</value>
</data>
<data name="FilterPrivatePfs" xml:space="preserve">
<value>应用过滤规则至带密码的招募</value>
</data>
<data name="EnableCustomPartyFinderFilters" xml:space="preserve">
<value>启用自定义队员招募过滤</value>
</data>
<data name="SubstringsToFilter" xml:space="preserve">
<value>过滤关键词</value>
</data>
<data name="RegularExpressionsToFilter" xml:space="preserve">
<value>过滤正则表达式</value>
</data>
<data name="SaveFilters" xml:space="preserve">
<value>保存过滤规则</value>
</data>
<data name="Reporting" xml:space="preserve">
<value>{0} 错误报告</value>
</data>
<data name="ReportStatusFailure" xml:space="preserve">
<value>发送失败</value>
</data>
<data name="ReportStatusSuccessful" xml:space="preserve">
<value>发送成功</value>
</data>
<data name="ReportStatusInProgress" xml:space="preserve">
<value>发送中</value>
</data>
<data name="ReportStatusUnknown" xml:space="preserve">
<value>未知</value>
</data>
<data name="ReportStatusMessage" xml:space="preserve">
<value>最后报告状态:{0}</value>
</data>
<data name="ReportChatTab" xml:space="preserve">
<value>聊天消息</value>
</data>
<data name="ReportColumnTimestamp" xml:space="preserve">
<value>时间</value>
</data>
<data name="ReportColumnChannel" xml:space="preserve">
<value>频道</value>
</data>
<data name="ReportColumnReason" xml:space="preserve">
<value>原因</value>
</data>
<data name="ReportColumnSender" xml:space="preserve">
<value>发送者</value>
</data>
<data name="ReportColumnMessage" xml:space="preserve">
<value>消息内容</value>
</data>
<data name="ReportPartyFinderTab" xml:space="preserve">
<value>队员招募</value>
</data>
<data name="ReportColumnHost" xml:space="preserve">
<value>招募者</value>
</data>
<data name="ReportColumnDescription" xml:space="preserve">
<value>说明</value>
</data>
<data name="ReportModalTitle" xml:space="preserve">
<value>报告给 {0}</value>
</data>
<data name="ReportModalDisabledCustom" xml:space="preserve">
<value>你不能报告被自定义过滤规则过滤的消息。</value>
</data>
<data name="ReportModalReport" xml:space="preserve">
<value>报告</value>
</data>
<data name="ReportModalCopy" xml:space="preserve">
<value>复制到剪贴板</value>
</data>
<data name="ReportModalCancel" xml:space="preserve">
<value>取消</value>
</data>
<data name="TradeCategory" xml:space="preserve">
<value>交易广告</value>
</data>
<data name="FreeCompanyCategory" xml:space="preserve">
<value>部队广告</value>
</data>
<data name="StaticSubCategory" xml:space="preserve">
<value>固定队补位招募</value>
</data>
<data name="NormalCategory" xml:space="preserve">
<value>普通消息</value>
</data>
<data name="PhishingCategory" xml:space="preserve">
<value>钓鱼消息</value>
</data>
<data name="RmtContentCategory" xml:space="preserve">
<value>RMT副本</value>
</data>
<data name="RmtGilCategory" xml:space="preserve">
<value>RMT金币</value>
</data>
<data name="RoleplayingCategory" xml:space="preserve">
<value>角色扮演广告</value>
</data>
<data name="TradeDescription" xml:space="preserve">
<value>宣传使用金币交易物品或代打的消息,例如寻找工作或贩卖稀有物品</value>
</data>
<data name="FreeCompanyDescription" xml:space="preserve">
<value>部队广告</value>
</data>
<data name="NormalDescription" xml:space="preserve">
<value>普通消息,不应该被过滤</value>
</data>
<data name="PhishingDescription" xml:space="preserve">
<value>试图欺骗你透露账号信息以进行盗号等行为的消息</value>
</data>
<data name="RmtContentDescription" xml:space="preserve">
<value>使用现实货币进行/招募代打</value>
</data>
<data name="RmtGilDescription" xml:space="preserve">
<value>使用现实货币购买/售卖金币或物品</value>
</data>
<data name="RoleplayingDescription" xml:space="preserve">
<value>宣传个人RP、RP团体、场所或其他与角色扮演有关的消息</value>
</data>
<data name="StaticDescription" xml:space="preserve">
<value>固定队寻找队友或玩家正在寻找固定队</value>
</data>
<data name="CommunityDescription" xml:space="preserve">
<value>宣传玩家社区/团体的广告,例如 Discord 服务器等</value>
</data>
<data name="StaticSubDescription" xml:space="preserve">
<value>固定队寻找补位成员</value>
</data>
<data name="ReportToNoSoliciting" xml:space="preserve">
<value>报告给 NoSoliciting</value>
</data>
<data name="ReportToastSuccess" xml:space="preserve">
<value>由 {0} 发起的队员招募已报告成功。</value>
</data>
<data name="ReportToastFailure" xml:space="preserve">
<value>由 {0} 发起的队员招募报告失败。</value>
</data>
<data name="CommunityCategory" xml:space="preserve">
<value>社区广告</value>
</data>
<data name="StaticCategory" xml:space="preserve">
<value>固定队招募</value>
</data>
<data name="OtherTab" xml:space="preserve">
<value>其他</value>
</data>
<data name="LogFilteredMessages" xml:space="preserve">
<value>启用文字消息过滤日志</value>
</data>
<data name="AdvancedWarning1" xml:space="preserve">
<value>除非你明确知道自己在干什么,否则请不要更改高级设置。</value>
</data>
<data name="FilterIlvlPfs" xml:space="preserve">
<value>过滤装备品级高于最大值的招募</value>
</data>
<data name="ReportHelp" xml:space="preserve">
<value>单击下方分类错误的项目以将其报告给开发者。</value>
</data>
<data name="ReportModalHelp1" xml:space="preserve">
<value>报告此消息将使开发者知道此消息被错误归类。</value>
</data>
<data name="ReportModalHelp2" xml:space="preserve">
<value>NoSoliciting 只对英文消息有效。请不要报告其他语言的消息。</value>
</data>
<data name="ModelStatusUninitialised" xml:space="preserve">
<value>未初始化</value>
</data>
<data name="ModelStatusPreparing" xml:space="preserve">
<value>准备更新中</value>
</data>
<data name="ModelStatusDownloadingModel" xml:space="preserve">
<value>正在下载模型</value>
</data>
<data name="ModelStatusDownloadingManifest" xml:space="preserve">
<value>正在获取模型信息</value>
</data>
<data name="ModelStatusInitialising" xml:space="preserve">
<value>正在进行初始化</value>
</data>
<data name="ModelStatusInitialised" xml:space="preserve">
<value>初始化完成</value>
</data>
<data name="ReportModalDisabledFilterNotEnabled" xml:space="preserve">
<value>您暂时无法报告此消息。因为您在当时并没有过滤此类消息。</value>
</data>
<data name="ReportModalDisabledBadModel" xml:space="preserve">
<value>您暂时无法报告此消息。因为机器学习模型在当时并未启用。</value>
</data>
<data name="ReportModalDisabledSameClassification" xml:space="preserve">
<value>您暂时无法报告此消息。因为您必须选择与原始分类不同的分类。</value>
</data>
<data name="ReportModalDisabledItemLevel" xml:space="preserve">
<value>您暂时无法报告此消息。因为它的平均品级实在是太高了!</value>
</data>
<data name="ReportModalOriginalClassification" xml:space="preserve">
<value>此消息的原始分类:{0}</value>
</data>
<data name="ReportModalSuggestedClassification" xml:space="preserve">
<value>您觉得此消息应当被归为何种分类?</value>
</data>
<data name="ReportModalGoToCustomButton" xml:space="preserve">
<value>前往自定义过滤规则</value>
</data>
<data name="ReportModalClassificationOther" xml:space="preserve">
<value>其他</value>
</data>
<data name="ReportModalClassificationOtherDescription" xml:space="preserve">
<value>不属于任何类别的消息。</value>
</data>
</root>

View File

@ -0,0 +1,319 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ReportStatusMessage" xml:space="preserve">
<value>最後報告狀態:{0}</value>
</data>
<data name="ReportModalHelp1" xml:space="preserve">
<value>報告此消息將使開發人員知道此消息被錯誤歸類。</value>
</data>
<data name="ReportModalDisabledCustom" xml:space="preserve">
<value>你不能報告被客製化過濾規則過濾的消息。</value>
</data>
<data name="OtherTab" xml:space="preserve">
<value>其他</value>
</data>
<data name="ModelTab" xml:space="preserve">
<value>機器學習模式</value>
</data>
<data name="AdvancedWarning1" xml:space="preserve">
<value>除非你明確知道自己在做甚麼,否則請不要更改高級設置。</value>
</data>
<data name="FilterPrivatePfs" xml:space="preserve">
<value>應用過濾規則至帶密碼的招募</value>
</data>
<data name="RegularExpressionsToFilter" xml:space="preserve">
<value>過濾正則表達式</value>
</data>
<data name="SaveFilters" xml:space="preserve">
<value>保存過濾規則</value>
</data>
<data name="Reporting" xml:space="preserve">
<value>{0} 錯誤報告</value>
</data>
<data name="ReportHelp" xml:space="preserve">
<value>單擊下方分類錯誤的項目以將其報告給開發人員。</value>
</data>
<data name="ReportStatusFailure" xml:space="preserve">
<value>發送失敗</value>
</data>
<data name="ReportStatusSuccessful" xml:space="preserve">
<value>發送成功</value>
</data>
<data name="ReportStatusInProgress" xml:space="preserve">
<value>發送中</value>
</data>
<data name="ReportStatusUnknown" xml:space="preserve">
<value>未知</value>
</data>
<data name="ReportChatTab" xml:space="preserve">
<value>聊天消息</value>
</data>
<data name="ReportColumnTimestamp" xml:space="preserve">
<value>時間</value>
</data>
<data name="ReportColumnChannel" xml:space="preserve">
<value>頻道</value>
</data>
<data name="ReportColumnReason" xml:space="preserve">
<value>原因</value>
</data>
<data name="ReportColumnSender" xml:space="preserve">
<value>發送者</value>
</data>
<data name="ReportColumnMessage" xml:space="preserve">
<value>消息內容</value>
</data>
<data name="ReportPartyFinderTab" xml:space="preserve">
<value>隊員招募</value>
</data>
<data name="ReportColumnHost" xml:space="preserve">
<value>招募者</value>
</data>
<data name="ReportColumnDescription" xml:space="preserve">
<value>說明</value>
</data>
<data name="ReportModalTitle" xml:space="preserve">
<value>報告給 {0}</value>
</data>
<data name="ReportModalHelp2" xml:space="preserve">
<value>NoSoliciting 只對英文消息有效。請不要報告其他語言的消息。</value>
</data>
<data name="ReportModalReport" xml:space="preserve">
<value>報告</value>
</data>
<data name="LogFilteredPfs" xml:space="preserve">
<value>啟用隊員招募過濾日誌</value>
</data>
<data name="LogFilteredMessages" xml:space="preserve">
<value>啟用文字消息過濾日誌</value>
</data>
<data name="ShowReportingWindow" xml:space="preserve">
<value>回報錯誤</value>
</data>
<data name="Settings" xml:space="preserve">
<value>{0} 設置</value>
</data>
<data name="AdvancedMode" xml:space="preserve">
<value>高級模式</value>
</data>
<data name="ModelTabVersion" xml:space="preserve">
<value>版本:{0}</value>
</data>
<data name="ModelTabStatus" xml:space="preserve">
<value>狀態:{0}</value>
</data>
<data name="ModelTabError" xml:space="preserve">
<value>最後一個錯誤:{0}</value>
</data>
<data name="UpdateModel" xml:space="preserve">
<value>更新模型</value>
</data>
<data name="FiltersTab" xml:space="preserve">
<value>過濾規則</value>
</data>
<data name="AdvancedWarning2" xml:space="preserve">
<value>機器學習模型已經對部分頻道進行了訓練。</value>
</data>
<data name="OtherFiltersTab" xml:space="preserve">
<value>其他過濾規則</value>
</data>
<data name="ChatFilters" xml:space="preserve">
<value>聊天消息過濾</value>
</data>
<data name="EnableCustomChatFilters" xml:space="preserve">
<value>啟用客製化聊天消息過濾</value>
</data>
<data name="PartyFinderFilters" xml:space="preserve">
<value>隊員招募過濾</value>
</data>
<data name="FilterIlvlPfs" xml:space="preserve">
<value>過濾裝備品級高於最大值的招募</value>
</data>
<data name="EnableCustomPartyFinderFilters" xml:space="preserve">
<value>啟用客製化隊員招募過濾</value>
</data>
<data name="SubstringsToFilter" xml:space="preserve">
<value>過濾關鍵詞</value>
</data>
<data name="ReportModalCopy" xml:space="preserve">
<value>複製到剪貼板</value>
</data>
<data name="ReportModalCancel" xml:space="preserve">
<value>取消</value>
</data>
<data name="TradeCategory" xml:space="preserve">
<value>交易廣告</value>
</data>
<data name="FreeCompanyCategory" xml:space="preserve">
<value>部隊廣告</value>
</data>
<data name="NormalCategory" xml:space="preserve">
<value>普通消息</value>
</data>
<data name="PhishingCategory" xml:space="preserve">
<value>釣魚消息</value>
</data>
<data name="RmtContentCategory" xml:space="preserve">
<value>RMT副本</value>
</data>
<data name="RmtGilCategory" xml:space="preserve">
<value>RMT金幣</value>
</data>
<data name="RoleplayingCategory" xml:space="preserve">
<value>角色扮演廣告</value>
</data>
<data name="StaticCategory" xml:space="preserve">
<value>固定隊招募</value>
</data>
<data name="CommunityCategory" xml:space="preserve">
<value>社區廣告</value>
</data>
<data name="StaticSubCategory" xml:space="preserve">
<value>固定隊補位招募</value>
</data>
<data name="TradeDescription" xml:space="preserve">
<value>宣傳使用金幣交易物品或代打的消息,例如尋找工作或販賣稀有物品</value>
</data>
<data name="FreeCompanyDescription" xml:space="preserve">
<value>宣傳部隊</value>
</data>
<data name="NormalDescription" xml:space="preserve">
<value>普通消息,不應該被過濾</value>
</data>
<data name="PhishingDescription" xml:space="preserve">
<value>試圖欺騙你透露賬號信息以進行盜號等行為的消息</value>
</data>
<data name="RmtContentDescription" xml:space="preserve">
<value>使用現實貨幣進行/招募代打</value>
</data>
<data name="RmtGilDescription" xml:space="preserve">
<value>使用現實貨幣購買/售賣金幣或物品</value>
</data>
<data name="RoleplayingDescription" xml:space="preserve">
<value>宣傳個人RP、RP團體、場所或其他與角色扮演有關的消息</value>
</data>
<data name="StaticDescription" xml:space="preserve">
<value>固定隊尋找隊友或玩家正在尋找固定隊</value>
</data>
<data name="CommunityDescription" xml:space="preserve">
<value>宣傳玩家社區/團體的廣告,例如 Discord 伺服器等</value>
</data>
<data name="StaticSubDescription" xml:space="preserve">
<value>固定隊尋找補位隊員</value>
</data>
<data name="ReportToNoSoliciting" xml:space="preserve">
<value>報告給 NoSoliciting</value>
</data>
<data name="ReportToastSuccess" xml:space="preserve">
<value>由 {0} 發起的隊員招募以報告成功。</value>
</data>
<data name="ReportToastFailure" xml:space="preserve">
<value>由 {0} 發起的隊員招募報告失敗。</value>
</data>
<data name="OtherGameLanguage" xml:space="preserve">
<value>使用遊戲語言而不是 Dalamud 的語言作為插件語言</value>
</data>
<data name="ModelStatusUninitialised" xml:space="preserve">
<value>未初始化</value>
</data>
<data name="ModelStatusPreparing" xml:space="preserve">
<value>準備更新中</value>
</data>
<data name="ModelStatusDownloadingManifest" xml:space="preserve">
<value>正在獲取模型信息</value>
</data>
<data name="ModelStatusDownloadingModel" xml:space="preserve">
<value>正在下載模型</value>
</data>
<data name="ModelStatusInitialising" xml:space="preserve">
<value>正在進行初始化</value>
</data>
<data name="ModelStatusInitialised" xml:space="preserve">
<value>初始化完成</value>
</data>
<data name="ReportModalDisabledBadModel" xml:space="preserve">
<value>您暫時無法報告此消息。因為機器學習模型在當時並未啟用。</value>
</data>
<data name="ReportModalDisabledSameClassification" xml:space="preserve">
<value>您暫時無法報告此消息。因為您必須選擇與原始分類不同的分類。</value>
</data>
<data name="ReportModalDisabledItemLevel" xml:space="preserve">
<value>您暫時無法報告此消息。因為它的平均品級太高了。</value>
</data>
<data name="ReportModalOriginalClassification" xml:space="preserve">
<value>此消息的原始分類:{0}</value>
</data>
<data name="ReportModalSuggestedClassification" xml:space="preserve">
<value>您覺得此消息應當被歸為何種分類?</value>
</data>
<data name="ReportModalDisabledFilterNotEnabled" xml:space="preserve">
<value>您暫時無法報告此消息。因為您在當時並沒有過濾此類消息。</value>
</data>
<data name="ReportModalClassificationOther" xml:space="preserve">
<value>其他</value>
</data>
<data name="ReportModalGoToCustomButton" xml:space="preserve">
<value>前往客製化過濾規則</value>
</data>
<data name="ReportModalClassificationOtherDescription" xml:space="preserve">
<value>不屬於任何類別的消息。</value>
</data>
</root>

View File

@ -1,269 +0,0 @@
# This file defines the filters that NoSoliciting will use for
# built-in filters.
# The version should be incremented for each commit including changes
# to this file.
# There are three main sections: chat, party_finder, and global. The
# chat and party_finder sections are for their respective areas (the
# chat log and the Party Finder window), and the global section
# applies to both.
# Each subsection is a separate built-in filter that can be toggled on
# and off. The option shown in the UI is defined in the
# subsection. For global subsections, {} can be inserted into the
# option name to substitute either "chat" or "Party Finder" as
# appropriate.
# Subsections can have ignore_case (defaults to false) and normalise
# (defaults to true) set. ignore_case will ignore casing for matching
# against the matchers, and normalise will normalise text prior to
# matching. Text normalisation consists of turning FFXIV-specific
# unicode symbols into normal ASCII characters and running a NFKD
# unicode decomposition on the result.
# Subsections also may filter based on channels with the channels key.
# A list of channels may be specified, and the message will be ignored
# if not in one of the specified channels. For the Party Finder, the
# channel is always None. An empty list (or missing channels key) will
# ignore the channel.
# Each subsection may specify whether it is enabled by default with the
# default key. This should be used sparingly. This defaults to false.
# The real meat of the file is the matchers. There are two types of
# matchers: required and likely. Both types have categories of strings
# or regular expressions that should match. For required matchers, at
# least one string or regex should match in *all* categories. For
# likely matchers, at least one string or regex should match in the
# value of likelihood_threshold (or greater) categories.
# If both required and likely matchers are specified, they both must
# match. This means that all the categories of the required matchers
# must find a match, *and* that at least likelihood_threshold matchers
# must find a match in likely_matchers.
# Substring matchers are faster than regular expressions and are
# specified just by using a string. Regular expression matchers are
# slower but more flexible, and they are specified by using a regex
# key, as can be seen below.
version: 40
# This is the URL the plugin will POST reports to.
report_url: https://nosol.annaclemens.io/report
chat:
rmt:
option:
basic: Filter RMT from chat
advanced: Enable built-in chat RMT filter
default: true
required_matchers:
- - 4KGOLD
- We have sufficient stock
- PVPBANK.COM
- Gil for free
- www.so9.com
- Fast & Convenient
- Cheap & Safety Guarantee
- 【Code|A O A U E
- igfans
- 4KGOLD.COM
- Cheapest Gil with
- pvp and bank on google
- Selling Cheap GIL
- ff14mogstation.com
- Cheap Gil 1000k
- gilsforyou
- server 1000K =
- gils_selling
- E A S Y.C O M
- bonus code
- mins delivery guarantee
- Sell cheap
- Salegm.com
- cheap Mog
- Off Code
- FF14Mog.com
- 使用する5
- 5分納品
- offers Fantasia
- regex: 'finalfantasyxiv\.com-\w+\.\w+' # phishing
- regex: 'square-enix\.com-\w+\.\w+' # phishing
party_finder:
rmt:
option:
basic: Filter RMT from Party Finder
advanced: Enable built-in Party Finder RMT filter
default: true
ignore_case: true
required_matchers:
# selling
- - sell
- s e l l
- $ell
- $ e l l
- sale
- s a l e
- price
- cheap
- minmax # name of a RMT group because they thought to stop saying "sell" lul
- regex: '\boffer'
# content
- - eden
- savage
- primal
- ultimate
- trial
- loot
- content
- regex: '\bex\b'
- regex: '\bmount\b'
- regex: e[1-9][0-2]?
global:
roleplay:
option:
basic: 'Filter RP advertisements from {}'
advanced: 'Enable built-in {} RP filter'
ignore_case: true
likelihood_threshold: 3
likely_matchers:
# mentions housing wards
- - ward
- regex: 'w.{0,2}\d'
# mentions housing plots
- - plot
- apt
- regex: 'p.{0,2}\d'
# has a discord or website
- - discord.gg
- carrd.co
- discord.io
- tinyurl.com
# mentions roleplaying
- - rp
- roleplay
- role play
# mentions fairly rp-only keywords
- - sfw # also catches nsfw
- '18+'
- '18 +'
- open
- event
- venue
- hosting
# has venue type
- - venue
- cafe
- café
- bar
- lounge
- brothel
- casino
- cabaret
- restaurant
- library
- bookstore
- book store
- hostel
- club
- bath
- frat
- sorority
- bazaar
- regex: '\bden\b'
# mentions services/activities
- - entertainment
- live
- raffle
- menu
- atmosphere
- drink
- food
- eat
- dance
- dancing
- music
- karaoke
- contest
- giveaway
- gambling
- cosplay
- bounty
- bounties
- adventur # adventuring, adventure, adventurer, etc.
- courtesan
- rp night
- maid
- cowboy
- merry
- girlfriend
- wife
- sleepover
- masquerade
# specific RP names
- - Lucky Sevens
free_company:
option:
basic: 'Filter FC recruitments from {}'
advanced: 'Enable built-in {} FC recruitment filter'
ignore_case: true
channels:
- none
- shout
- yell
likelihood_threshold: 3
likely_matchers:
# mentions free company
- - fc
- free company
- recruiting
- regex: '[<«][\w\-]+[>»]'
# contains a call to action
- - join
- apply
- /t
- dm
- pm
- whisper
- regex: '\btell'
- regex: '[ie]nquire'
# mentions benefits
- - discord
- map
- rank
- active
- weekly
- social
- friendly
- buff
- event
- house
- mansion
- level
- raid
- hunt
# has common keywords
- - family
- community
- veteran
- seasoned
- casual
- help
- player
- new player
- new member
- new people
- new face
- characters
- looking for member
- lonely
- regex: '\bold\b'
- regex: '\bvets?\b'
# has common keywords 2
- - welcom
- invite
- Ranked

View File

@ -1,28 +0,0 @@
#!/bin/sh
# remove old pack dir
rm -rf pack
# make temp dir and go to it
mkdir -p pack/temp
cd pack/temp || exit
# copy the dlls, defs, and the manifest
cp ../../bin/Release/NoSoliciting.dll ../../bin/Release/YamlDotNet.dll ./
cp ../../NoSoliciting.json ./
# make sure none of them are marked executable
chmod -x ./*
# zip them
zip ../latest.zip ./*
# move the manifest next to the zip
mv NoSoliciting.json ../
# remove everything
rm ./*
# go back up and remove the temp dir
cd ..
rmdir temp

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.CodeAnalysis.FxCopAnalyzers" version="2.9.6" targetFramework="net48" developmentDependency="true" />
<package id="Microsoft.CodeAnalysis.VersionCheckAnalyzer" version="2.9.6" targetFramework="net48" developmentDependency="true" />
<package id="Microsoft.CodeQuality.Analyzers" version="2.9.6" targetFramework="net48" developmentDependency="true" />
<package id="Microsoft.NetCore.Analyzers" version="2.9.6" targetFramework="net48" developmentDependency="true" />
<package id="Microsoft.NetFramework.Analyzers" version="2.9.6" targetFramework="net48" developmentDependency="true" />
<package id="YamlDotNet" version="8.1.2" targetFramework="net48" />
</packages>

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