From 31576047a21b16f657a4dcdb95ca28c4a638ac2f Mon Sep 17 00:00:00 2001 From: Anna Clemens Date: Tue, 27 Apr 2021 20:16:07 -0400 Subject: [PATCH] feat: remove defs, add some loc, add context menu --- NoSoliciting.Trainer/data.csv | 18 ++ NoSoliciting/ContextMenu.cs | 67 +++++ NoSoliciting/Definitions.cs | 275 ------------------ NoSoliciting/Filter.cs | 116 +------- NoSoliciting/ILRepack.targets | 1 + NoSoliciting/Interface/Report.cs | 49 ++-- NoSoliciting/Interface/Settings.cs | 161 ++-------- NoSoliciting/Message.cs | 16 +- NoSoliciting/Ml/Models.cs | 43 +-- NoSoliciting/NoSoliciting.csproj | 19 +- NoSoliciting/Plugin.cs | 35 +-- NoSoliciting/PluginConfiguration.cs | 2 - NoSoliciting/Properties/Resources.Designer.cs | 84 ------ NoSoliciting/Properties/Resources.resx | 124 -------- NoSoliciting/Resources/en.json | 170 +++++++++++ NoSoliciting/definitions.yaml | 150 ---------- 16 files changed, 379 insertions(+), 951 deletions(-) create mode 100755 NoSoliciting/ContextMenu.cs delete mode 100644 NoSoliciting/Definitions.cs delete mode 100644 NoSoliciting/Properties/Resources.Designer.cs delete mode 100644 NoSoliciting/Properties/Resources.resx create mode 100755 NoSoliciting/Resources/en.json delete mode 100644 NoSoliciting/definitions.yaml diff --git a/NoSoliciting.Trainer/data.csv b/NoSoliciting.Trainer/data.csv index 835a362..2d6dd96 100755 --- a/NoSoliciting.Trainer/data.csv +++ b/NoSoliciting.Trainer/data.csv @@ -9,6 +9,7 @@ COMMUNITY,0,"○○ Bozja/DRS - Savages/Ultimates - Blue Mage, PvP + COMMUNITY,0,♡ Gayorzea is a friendly & welcoming discord partnered server for LGBT+ XIV players! Join today @ https://discord.gg/gayorzea ♡ COMMUNITY,0,♥ Gayorzea is a friendly & welcoming Discord partnered server for LGBT+ XIV players! Join today @ https://discord.gg/gayorzea ♥ COMMUNITY,0,"Deep Dungeons Discord for Potd/Hoh runs/loot/clears. 4500+ ppl. Mar. Event with mog prizes, now up! ---> discord.gg/pRmJzFg" +COMMUNITY,0,Deep Dungeons MAss Discord for Potd/Hoh runs/loot/clears. events 5500+ members. ---> discord.gg/deepdungeons COMMUNITY,0,"discord.gg/clubxiv, come join ""The Clubhouse"" the foremost social club on Primal: all about friendship. " COMMUNITY,0,Do you miss Rival Wings? We got you! Rival Wings runs scheduled for TONIGHT and SAT 8PM EST - discord.gg/pvprevival COMMUNITY,0,"Enjoy discussions, events, help, and more at the (official) FFXIV Gilgamesh Discord Server https://discord.gg/MzyYXPdvre " @@ -100,6 +101,7 @@ FC,0,LF smallish FC may be afk ;3 FC,0,Looking for a place to call home? Well you are in luck! Log_Horizon We are accepting all kinds of players! /Tell to join! FC,0,Looking for an FC? Fifty Percent is looking! We are trying to build a tight-knit group - Doesn't matter how you like to play. FC,0,"Looking for someone to help me build up my FC. :) " +FC,0,"Looking to add members to House-Buffs-Discord join for info. " FC,0,Mognificents is a really small fc recruiting people so we can make a beautiful community that we've all dreamed of! Send a tell! :D FC,0,Morning! Brand new Free Company looking to recruit chill people who loves doing content and getting to know new people! FC,0,"New FC (Cruxis) is looking for members! New and old, all are welcome to join. Come join the pary for an invite! [Sargatanas]" @@ -339,6 +341,7 @@ NORMAL,0,"Blue Log completion, come get yours." NORMAL,0,blue mage gear grinding! NORMAL,0,"Blue Mages - KNOW FIGHT, Have decent gear, LEVEL 70 BLUES please! Thank you! " NORMAL,0,"Bonding ceremony tonight at 8pm est. Join party if you want an invite/want the minion from attending, or trade me in Limsa" +NORMAL,0,"Chicken man eating eggs for gil, prank your friends at only 400 gil per egg!" NORMAL,0,"chill farm, no rotate" NORMAL,0,Cinder prog for the fc all are welcome no salt! :D NORMAL,0,Cleaning up to heart phase. Tank swap at 2 stacks. @@ -1759,6 +1762,8 @@ NORMAL,11,"""You said a bad word one time, but we can't tell you what word or wh NORMAL,11,\o/ frand NORMAL,11,• Looking for a group! NORMAL,11,"※ If you need assistance, please reach out to me or the Empress! ※" +NORMAL,11,"★   next stop Lakeland ( 14.5 , 24.2 ) ★" +NORMAL,11,"★   next stop Lakeland ( 25.9 , 24.3 ) ★" NORMAL,11,"☆☆Shadowbringers Hunt Train • A Rank☆☆ Starting at 《Instance》 → Lakeland ( 27.6 , 15.3 )" NORMAL,11,A cake!? NORMAL,11,a pretty good rhythm game. Just found it today and been trying to FC it on hard @@ -2209,6 +2214,7 @@ RMT_C,0,【】 【 E9S-E12S ※ Ultimates ※ PVP ※ Mount RMT_C,0,"【】 DR Savage, Eden 9-12s, Ultimates, POTD/HOH, Titles, Primals, Mentoring, FFlogs, Blu Mage etc. now#0502" RMT_C,0,"【】 DR Savage, Eden 9-12s, Ultimates, POTD/HOH, Titles, Primals, Mentoring, FFlogs, Blu Mage etc. Satori#7777" RMT_C,0,↓BUD↑[Eden's Promise Savage][Diamond Weapon EX] [Ultimates] [DR Savage] [Mounts][Primals] ≡ Discord: bud#2903 +RMT_C,0,↓BUD↑[Eden's Promise Savage][Diamond Weapon EX] [Ultimates] [Mounts][Primals] ≡ Discord: bud#2903 RMT_C,0, 一 Present#0148 ★★★★★ RMT_C,0,● MIN M AX Ø SALES ≪Eden Savage E9-12S • Ucob/UwU/TEA • Primals≫ Guaranteed result fast delivery | Discord→ yu#7494 RMT_C,0,"● MIN M AX Ø | ❶ Loot & Mounts (New Savage, Ultimates etc.) ❷ Coaching & Logs | Instant delivery. Discord→ Enma#7777" @@ -2451,6 +2457,7 @@ RP,0,[RP][SFW] SAKE & UMESHI NIGHT!!! | Kusuo Izakaya & Bath (Kobai Goten Branch RP,0,[RP][SFW] Sweet-Brier Bakery Cafe and Lounge NOW OPEN! Come chill and relax at Sarg Goblet W16 P55! RP,0,[RP][SFW]  Kusuo Izakaya & Bath  Come by for a relaxing night out with us! | Gilg - Shiro - W24 - APT 32 | discord.gg/svrMqEcN8z RP,0,"[RP][SFWish] The Paw and Tail bar is open, now with less Syrcus Tower! Gilgamesh, Lavender Beds, Ward 3, Plot 41" +RP,0,"[RP][SIREN] The Kuroi Cabaret is HIRING! We're looking for all sorts of positions, so apply today @ https://discord.gg/jAmdwcWv" RP,0,[RP]{18+] Roehaus invites you to dream One Last Dream! Mount Raffle | Bards | Bar | Company JenoLBw13p46 @ 8p-12a EST RP,0,"[RP]Calling all Pirates, Cutthroats, and sellswords tonight 5pm Central. check discord #events https://discord.gg/ChRB9b4ynz" RP,0,[RP]IshiKhawa family restaraunt [open 6-9pm CST] ( rules/info: https://ishikhawa.carrd.co ) @@ -2623,12 +2630,15 @@ RP,0,Faeries only Goth/Industrial/Metal party! Twitch dj stream party in basemen RP,0,"Faeries' Caress is open! SFW bar! Come have a drink, chat and share in our stage! ♥ Adamantoise - Lavender Beds - W15 - P 12." RP,0,Female♥ Femnine Highlander♥ LF Highlander/Roe for RP 18+♥ RP,0,for 400k/hr ill pretend to be ur gf and give u the love and care u need. 500k if u wanna get freaky. +RP,0,Free Antelope Stag Horns! @ Limsa Aetheryte Jenova. Also always looking for trans-friendly mlm RP partners for my Viera! ♥ RP,0,Frog's Log Bath & Bar is open @ Sarg Mist W2 P27! Come dressed as a frog for a free treat RP,0,"Garden of Lakshmi grand opening tonight 9PM CST! Drinks, dancing and fun! Faerie Mist W3 P11" RP,0,Gay Catboi available for purchase ;) Need the gill to pay a debt RP,0,"Girl for hire<3 Il run dungeons with you, just chat if you want, maybe (other) things send a tell for more info <3" RP,0,"Good evening loves! Come join us at Succubares Insomnium here on Sargatanas! Goblet, Ward 10, Plot 35, hope to see you tonight!" RP,0,"Good evening! Come join us at Succubares Insomnium here on Sargatanas! Goblet, Ward 7, Plot 25, hope to see you tonight!" +RP,0,"Good morning! Stop by Bluebird for all your queuing, afk, and lounging needs. Music on the roof. Gilga / Mist / W13 P8" +RP,0,Got a place that you'd like to advertise? Come check us out!   | RP Boards and more! - https://discord.gg/S7BUVKh RP,0,Got a place that you'd like to advertise? Come check us out!   | RP Boards and more! - https://discord.gg/S7BUVKh RP,0,Got a place that you'd like to advertise? Come check us out!   | RP Boards and more! - https://discord.gg/S7BUVKh RP,0,Got a place that you'd like to advertise? Come check us out!   | RP Boards and more! - https://discord.gg/S7BUVKh @@ -3097,6 +3107,7 @@ STATIC,0,Looking for a static?Read this form and join us! https://forms.gle/VAY7 STATIC,0,Looking for caster for TEA. Fresh prog. Send /tell if interested or msg Waltz#0621 STATIC,0,"Looking for members to static/prog E12s english speaking starting around 7-8pm ST, Send a tell for more info." STATIC,0,Looking for these roles for a fresh UwU prog. Raid Times: 9:00pm to 1:00(EST) Tuesday-Wednesday. Msg me. Mithos#7771 +STATIC,0,Looking for WHM/AST for weekly reclears 9s-12s: 9:00pm EST - 11:00pm EST - Tues and Wed. Know the fights. Please Whisper STATIC,0,main nin lf statik 6 pm to 10:30pmEST free everyday can also play dnc fresh on 12s FR EN STATIC,0,"MC static lf perm healer e9-11s reclears e12s P1 Lions clean up wend,fri,sun 8:30-11:30pm est if afk add on disc Devonta#8450" STATIC,0,"MCH LF Static for E12S Prog, on Line Lions - Available between 7 to 11:30 EST discord dm digi#1349 lets chat there!" @@ -3119,6 +3130,7 @@ STATIC,0,"recruiting for fresh TEA static. Sun/Mon/Tue 7pm-11pm PST. 2 healers, STATIC,0,Recruiting New and Vets! Time: The Friday 6PM PST after we get 48 people settled! https://discord.gg/nkbYXNmHBM :) STATIC,0,Recruiting New and Vets! Time: The Friday 6PM PST after we get 48 people settled! https://discord.gg/nkbYXNmHBM :) STATIC,0,"recruiting physical range for fresh TEA. fri-sat 9pm -12am. leave discord id or message, must be done with ES12" +STATIC,0,"recruiting Physical Range for TEA (7/8). fri-sat 9-12 (pm) Eastern. Must be done and over with ES12, new to TEA is fine" STATIC,0,relatively new 7/8 static LF 1 non-pld mt for the new eden tier! Message me at granger#0966 on discord if interested! STATIC,0,"Savage reclear static looking for 1 SCH/AST healer. Schedule is generally 6-10 pst. If interested, msg me @ rsioaire#7679" STATIC,0,Scheduled run on 03/04 (Thu) @ 6PM PST/9PM EST. RSVP / Info @ https://discord.gg/YEPYE75DbK (7 spots left ; 1 Tank spot left) @@ -3232,6 +3244,7 @@ TRADE,0,".:♥:. Crafter/Gatherer looking for work!  gear, raid pots/food, le TRADE,0,«The Cranky Crafter» Goblet 10/7 @ West ather: Services & Discounts! Designer home office with items on display! ◙^.^◙ TRADE,0,(Brynhildr) WTB ANY LARGE PLOT ANY LOCATION (relocation) pm me what u got TRADE,0,(Cactuar) Rank 30 Large Mist FC for sale. (Plot 32) +TRADE,0,"(Cactuar) Rank 30 Mist Large FC (plot 30) For Sale. " TRADE,0,(SELLING BURNING HAKUTAKU EYES 2M PER) TRADE,0,(Spriggan) WTB any rank 6 or higher FC (NO interest in Fc with plot) TRADE,0,[Balmung] WTB FC - PST @@ -3244,6 +3257,8 @@ TRADE,0,[Omega] WTB large house in lavender (spot 6/36) please help me to get my TRADE,0,[OPEN] V's Armory Need gear? Crafting FULL Exarch sets an affordable price. TRADE,0,[Ragnarok] [Relo] [Medium] [Shirohane] [P13] TRADE,0,[Ragnarok] [Sell-relo] [Medium] [Shiro] [P38] [Mist] [P14] +TRADE,0,[Ragnarok][Sell][Medium][Goblet][Plot 41] +TRADE,0,[Ragnarok][Sell][Medium][Goblet][Plot 41] TRADE,0,"[Rust Shop] Selling HQ  Gearsets! Full Exarchic 900K, Aesthete C+G 1.5M, All Tools 2M Gil. Fae Gob W3P10" TRADE,0,"[Rust Shop] Selling HQ Gearsets! Exarchic 800K, Aesthete C+G 1.4M, All Tools 1.8M Gil. Fae Gob W3P10" TRADE,0,"[Selling] Healer/Tank queue! 30k per daily roulette, 50K per select instance, 100K for 3 instances! 50% off sprout discount!" @@ -3255,10 +3270,12 @@ TRADE,0,"[WTB] Allegory, 2k = 175k. PST" TRADE,0,[WTB] Casual Attire Coffer 1.7M. Slide into these DM's and sell ya boy some clothes TRADE,0,[WTB][FC][older than 2015] [Zodiark] TRADE,0,[ZALERA-Only] WTS Mist Relo Plot 7/// WTB Large FC Any location. DM any questions. +TRADE,0,[zodiark][Medium][mist][Sell-Relo][ward 16][Plot 60] TRADE,0,"{Omnicrafter} Looking for work! Can make anything, exarchic gear, levekits, etc. Supply mats or not, prices may vary!" TRADE,0,"**Looking for a Large Plot, Coeurl Server** Ancient Order is looking to upgrade our house<3" TRADE,0,#1 NA WHOLESALER! NEO SET-660K! i490 SET-1.5M(w/DISCOUNT OPTIONS)! BUYING PHANTAS ANY AMOUNT 3K! JOIN PARTY!!! TRADE,0, Looking for Free Company with plot any city and any size. Workshop is NOT required. Discord @Lunar#5999 +TRADE,0,> Eurekan Seller < || Yukinko Snowflake Minion = 1.5 Mil || Form and Function Hairstyle = 600k || JOIN party if interested o7 TRADE,0,★★SELLING ANY GRADE 4 TINCTURES ★★ 2.4K/EACH. Join PF TRADE,0,"☆ Crafter/Gatherer looking for work!  gear, raid pots/food, etc. ☆ You bring matts, you get a discount! ☆ GSM Specialist!" TRADE,0,☆ SELLING: i510 Exarchic 1.0m per set ☆ SELLING: Aesthete's 1.3m per set ☆ @@ -3346,6 +3363,7 @@ TRADE,0,"Omni Crafter for Hire. If you have the mats , will craft for less than TRADE,0,"Omni-Crafter for hire! - Can craft just about anything up to ilv 510,prices are negotiable. Send tell or join if interested." TRADE,0,"Omni-Crafter for hire! Will craft anything for you, wiling to pentameld, price are negotiable - Send tell or join! :D" TRADE,0,"Omni-Crafter for hire! Will craft anything for you, willing to pentameld, prices are negotiable - Send tell or join! :D" +TRADE,0,"Omni-Crafter LFW! Will craft anything you need, from food, to BiS gear and more! Prices are negotiable - Send tell or join :D" TRADE,0,"Omni-Crafter. Let me know what you need, just provide mats. Tips are appreciated ♥" TRADE,0,Omnicrafter selling anything. Exarchic Sets for 1.4mil full or 150K per piece. Selling raid pots too. Will craft glams free if bring mats TRADE,0,Omnicrafter crafting cheap Exarchs OR any other items by request :) diff --git a/NoSoliciting/ContextMenu.cs b/NoSoliciting/ContextMenu.cs new file mode 100755 index 0000000..1327c75 --- /dev/null +++ b/NoSoliciting/ContextMenu.cs @@ -0,0 +1,67 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using CheapLoc; +using NoSoliciting.Interface; +using XivCommon.Functions; + +namespace NoSoliciting { + public class ContextMenu : IDisposable { + private Plugin Plugin { get; } + + internal ContextMenu(Plugin plugin) { + this.Plugin = plugin; + + this.Plugin.Common.Functions.ContextMenu.OpenContextMenu += this.OnOpenContextMenu; + } + + public void Dispose() { + this.Plugin.Common.Functions.ContextMenu.OpenContextMenu -= this.OnOpenContextMenu; + } + + private void OnOpenContextMenu(ContextMenuArgs args) { + if (args.ParentAddonName != "LookingForGroup") { + return; + } + + var label = Loc.Localize("ReportToNoSoliciting", "Report to NoSoliciting"); + args.AdditionalItems.Add(new ContextMenuItem(label, this.Report)); + // args.AdditionalItems.Add(new ContextMenuItem("Report to NoSoliciting", this.Report) { + // NameJapanese = "NoSolicitingへの報告", + // NameFrench = "Signaler à NoSoliciting", + // NameGerman = "An NoSoliciting melden", + // }); + } + + private void Report(ContextMenuItemSelectedArgs 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; + } + + Task.Run(async () => { + var status = await this.Plugin.Ui.Report.ReportMessageAsync(message); + switch (status) { + case ReportStatus.Successful: + this.Plugin.Interface.Framework.Gui.Toast.ShowNormal($"Party Finder hosted by {listing.Name} reported successfully."); + break; + case ReportStatus.Failure: + this.Plugin.Interface.Framework.Gui.Toast.ShowError($"Failed to report Party Finder hosted by {listing.Name}."); + break; + } + }); + } + } +} diff --git a/NoSoliciting/Definitions.cs b/NoSoliciting/Definitions.cs deleted file mode 100644 index e05b057..0000000 --- a/NoSoliciting/Definitions.cs +++ /dev/null @@ -1,275 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Dalamud.Game.Text; -using Dalamud.Plugin; -using NoSoliciting.Interface; -using NoSoliciting.Properties; -using YamlDotNet.Core; -using YamlDotNet.Core.Events; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; - -namespace NoSoliciting { - public class Definitions { - public static string? LastError { get; private set; } - public static DateTime? LastUpdate { get; set; } - - private const string Url = "https://git.sr.ht/~jkcclemens/NoSoliciting/blob/master/NoSoliciting/definitions.yaml"; - - public uint Version { get; private set; } - public Uri ReportUrl { get; private set; } - - public Dictionary Chat { get; private set; } - public Dictionary PartyFinder { get; private set; } - - public static async Task UpdateAndCache(Plugin plugin) { - #if DEBUG - return LoadDefaults(); - #endif - - Definitions? defs = null; - - var download = await Download().ConfigureAwait(true); - if (download != null) { - defs = download.Item1; - - try { - UpdateCache(plugin, download.Item2); - } catch (IOException e) { - PluginLog.Log("Could not update cache."); - PluginLog.Log(e.ToString()); - } - } - - return defs ?? await CacheOrDefault(plugin).ConfigureAwait(true); - } - - public static Definitions Load(string text) { - var de = new DeserializerBuilder() - .WithNamingConvention(UnderscoredNamingConvention.Instance) - .WithTypeConverter(new MatcherConverter()) - .IgnoreUnmatchedProperties() - .Build(); - return de.Deserialize(text); - } - - private static async Task CacheOrDefault(Plugin plugin) { - if (plugin == null) { - throw new ArgumentNullException(nameof(plugin), "Plugin cannot be null"); - } - - var pluginFolder = plugin.Interface.ConfigDirectory.ToString(); - - var cachedPath = Path.Combine(pluginFolder, "definitions.yaml"); - if (!File.Exists(cachedPath)) { - goto LoadDefaults; - } - - string text; - using (var file = File.OpenText(cachedPath)) { - text = await file.ReadToEndAsync().ConfigureAwait(true); - } - - try { - return Load(text); - } catch (YamlException e) { - PluginLog.Log($"Could not load cached definitions: {e}. Loading defaults."); - } - - LoadDefaults: - return LoadDefaults(); - } - - private static Definitions LoadDefaults() { - return Load(Resources.DefaultDefinitions); - } - - private static async Task?> Download() { - try { - using var client = new WebClient(); - var text = await client.DownloadStringTaskAsync(Url).ConfigureAwait(true); - LastError = null; - return Tuple.Create(Load(text), text); - } catch (Exception e) when (e is WebException or 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) { - var pluginFolder = plugin.Interface.ConfigDirectory.ToString(); - Directory.CreateDirectory(pluginFolder); - var cachePath = Path.Combine(pluginFolder, "definitions.yaml"); - - var b = Encoding.UTF8.GetBytes(defs); - - using var file = File.OpenWrite(cachePath); - await file.WriteAsync(b, 0, b.Length).ConfigureAwait(true); - } - - internal void Initialise(Plugin plugin) { - var defs = this.Chat.Select(e => new KeyValuePair($"chat.{e.Key}", e.Value)) - .Concat(this.PartyFinder.Select(e => new KeyValuePair($"party_finder.{e.Key}", e.Value))); - - foreach (var entry in defs) { - entry.Value.Initialise(entry.Key); - if (!plugin.Config.FilterStatus.TryGetValue(entry.Key, out _)) { - plugin.Config.FilterStatus[entry.Key] = entry.Value.Default; - } - } - - plugin.Config.Save(); - } - } - - public class Definition { - private bool _initialised; - - [YamlIgnore] - public string Id { get; private set; } - - public List> RequiredMatchers { get; private set; } = new(); - public List> LikelyMatchers { get; private set; } = new(); - public int LikelihoodThreshold { get; private set; } - public bool IgnoreCase { get; private set; } - public bool Normalise { get; private set; } = true; - public List Channels { get; private set; } = new(); - public OptionNames Option { get; private set; } - public bool Default { get; private set; } - - public void Initialise(string id) { - if (this._initialised) { - return; - } - - this._initialised = true; - - this.Id = id ?? throw new ArgumentNullException(nameof(id), "string cannot be null"); - - if (!this.IgnoreCase) { - return; - } - - var allMatchers = this.LikelyMatchers - .Concat(this.RequiredMatchers) - .SelectMany(matchers => matchers); - - foreach (var matcher in allMatchers) { - matcher.MakeIgnoreCase(); - } - } - - public bool Matches(XivChatType type, string text) { - if (text == null) { - throw new ArgumentNullException(nameof(text), "string cannot be null"); - } - - if (this.Channels.Count != 0 && !this.Channels.Contains(type)) { - return false; - } - - if (this.Normalise) { - text = NoSolUtil.Normalise(text); - } - - if (this.IgnoreCase) { - text = text.ToLowerInvariant(); - } - - // ensure all required matchers match - var allRequired = this.RequiredMatchers.All(matchers => matchers.Any(matcher => matcher.Matches(text))); - if (!allRequired) { - return false; - } - - // calculate likelihood - var likelihood = this.LikelyMatchers.Count(matchers => matchers.Any(matcher => matcher.Matches(text))); - - // matches only if likelihood is greater than or equal the threshold - return likelihood >= this.LikelihoodThreshold; - } - } - - public class Matcher { - private string? substring; - private Regex? regex; - - public Matcher(string substring) { - this.substring = substring ?? throw new ArgumentNullException(nameof(substring), "string cannot be null"); - } - - public Matcher(Regex regex) { - this.regex = regex ?? throw new ArgumentNullException(nameof(regex), "Regex cannot be null"); - } - - internal void MakeIgnoreCase() { - if (this.substring != null) { - this.substring = this.substring.ToLowerInvariant(); - } - - if (this.regex != null) { - this.regex = new Regex(this.regex.ToString(), this.regex.Options | RegexOptions.IgnoreCase); - } - } - - public bool Matches(string text) { - if (text == null) { - throw new ArgumentNullException(nameof(text), "string cannot be null"); - } - - if (this.substring != null) { - return text.Contains(this.substring); - } - - if (this.regex != null) { - return this.regex.IsMatch(text); - } - - throw new ApplicationException("Matcher created without substring or regex"); - } - } - - public class OptionNames { - public string Basic { get; private set; } - public string Advanced { get; private set; } - } - - internal sealed class MatcherConverter : IYamlTypeConverter { - public bool Accepts(Type type) { - return type == typeof(Matcher); - } - - public object ReadYaml(IParser parser, Type type) { - Matcher matcher; - - if (parser.TryConsume(out Scalar scalar)) { - matcher = new Matcher(scalar.Value); - } else if (parser.TryConsume(out MappingStart _)) { - if (parser.Consume().Value != "regex") { - throw new ArgumentException("matcher was an object but did not specify regex key"); - } - - var regex = new Regex(parser.Consume().Value, RegexOptions.Compiled); - matcher = new Matcher(regex); - - parser.Consume(); - } else { - throw new ArgumentException("invalid matcher"); - } - - return matcher; - } - - public void WriteYaml(IEmitter emitter, object value, Type type) { - throw new NotImplementedException(); - } - } -} diff --git a/NoSoliciting/Filter.cs b/NoSoliciting/Filter.cs index a6a3cbf..79b7693 100644 --- a/NoSoliciting/Filter.cs +++ b/NoSoliciting/Filter.cs @@ -1,5 +1,4 @@ -using Dalamud.Hooking; -using Dalamud.Plugin; +using Dalamud.Plugin; using System; using Dalamud.Game.Internal.Gui; using Dalamud.Game.Internal.Gui.Structs; @@ -41,11 +40,7 @@ namespace NoSoliciting { }; private Plugin Plugin { get; } - private bool _clearOnNext; - - private delegate IntPtr HandlePfSummaryDelegate(IntPtr param1, IntPtr param2, byte param3); - - private readonly Hook? _handleSummaryHook; + private int LastBatch { get; set; } = -1; private bool _disposedValue; @@ -54,11 +49,6 @@ namespace NoSoliciting { this.Plugin.Interface.Framework.Gui.Chat.OnCheckMessageHandled += this.OnChat; this.Plugin.Interface.Framework.Gui.PartyFinder.ReceiveListing += this.OnListing; - - var summaryPtr = this.Plugin.Interface.TargetModuleScanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B FA 48 8B F1 45 84 C0 74 ?? 0F B7 0A"); - - this._handleSummaryHook = new Hook(summaryPtr, new HandlePfSummaryDelegate(this.HandleSummary)); - this._handleSummaryHook.Enable(); } private void Dispose(bool disposing) { @@ -69,7 +59,6 @@ namespace NoSoliciting { if (disposing) { this.Plugin.Interface.Framework.Gui.Chat.OnCheckMessageHandled -= this.OnChat; this.Plugin.Interface.Framework.Gui.PartyFinder.ReceiveListing -= this.OnListing; - this._handleSummaryHook?.Dispose(); } this._disposedValue = true; @@ -82,25 +71,19 @@ namespace NoSoliciting { } private void OnChat(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled) { - isHandled = isHandled || this.FilterMessage(type, sender, message); + isHandled = isHandled || this.FilterMessage(type, senderId, sender, message); } private void OnListing(PartyFinderListing listing, PartyFinderListingEventArgs args) { try { - if (this._clearOnNext) { + if (this.LastBatch != args.BatchNumber) { this.Plugin.ClearPartyFinderHistory(); - this._clearOnNext = false; } - string? reason = null; - uint? version = null; - if (this.Plugin.MlReady) { - version = this.Plugin.MlFilter!.Version; - reason = this.MlListingFilterReason(listing); - } else if (this.Plugin.DefsReady) { - version = this.Plugin.Definitions!.Version; - reason = this.DefsListingFilterReason(listing); - } + this.LastBatch = args.BatchNumber; + + var version = this.Plugin.MlFilter?.Version; + var reason = this.MlListingFilterReason(listing); if (version == null) { return; @@ -109,9 +92,10 @@ namespace NoSoliciting { this.Plugin.AddPartyFinderHistory(new Message( version.Value, ChatType.None, + listing.ContentIdLower, listing.Name, listing.Description, - this.Plugin.MlReady, + true, reason )); @@ -129,19 +113,15 @@ namespace NoSoliciting { } } - private bool FilterMessage(XivChatType type, SeString sender, SeString message) { + private bool FilterMessage(XivChatType type, uint senderId, SeString sender, SeString message) { if (message == null) { throw new ArgumentNullException(nameof(message), "SeString cannot be null"); } - if (this.Plugin.MlReady) { - return this.MlFilterMessage(type, sender, message); - } - - return this.Plugin.DefsReady && this.DefsFilterMessage(type, sender, message); + return this.MlFilterMessage(type, senderId, sender, message); } - private bool MlFilterMessage(XivChatType type, SeString sender, SeString message) { + private bool MlFilterMessage(XivChatType type, uint senderId, SeString sender, SeString message) { if (this.Plugin.MlFilter == null) { return false; } @@ -177,6 +157,7 @@ namespace NoSoliciting { this.Plugin.AddMessageHistory(new Message( this.Plugin.MlFilter.Version, ChatTypeExt.FromDalamud(type), + senderId, sender, message, true, @@ -190,44 +171,6 @@ namespace NoSoliciting { return filter; } - private bool DefsFilterMessage(XivChatType type, SeString sender, SeString message) { - if (this.Plugin.Definitions == null || ChatTypeExt.FromDalamud(type).IsBattle()) { - return false; - } - - var text = message.TextValue; - - string? reason = null; - var filter = false; - - foreach (var def in this.Plugin.Definitions.Chat.Values) { - filter = filter || this.Plugin.Config.FilterStatus.TryGetValue(def.Id, out var enabled) - && enabled - && def.Matches(type, text) - && SetReason(out reason, def.Id); - } - - // check for custom filters if enabled - filter = filter || this.Plugin.Config.CustomChatFilter - && Chat.MatchesCustomFilters(text, this.Plugin.Config) - && SetReason(out reason, "custom"); - - this.Plugin.AddMessageHistory(new Message( - this.Plugin.Definitions.Version, - ChatTypeExt.FromDalamud(type), - sender, - message, - false, - reason - )); - - if (filter && this.Plugin.Config.LogFilteredChat) { - PluginLog.Log($"Filtered chat message ({reason}): {text}"); - } - - return filter; - } - private string? MlListingFilterReason(PartyFinderListing listing) { if (this.Plugin.MlFilter == null) { return null; @@ -264,37 +207,6 @@ namespace NoSoliciting { return null; } - private string? DefsListingFilterReason(PartyFinderListing listing) { - if (this.Plugin.Definitions == null) { - return null; - } - - var desc = listing.Description.TextValue; - - if (this.Plugin.Config.FilterHugeItemLevelPFs && listing.MinimumItemLevel > FilterUtil.MaxItemLevelAttainable(this.Plugin.Interface.Data)) { - return "ilvl"; - } - - foreach (var def in this.Plugin.Definitions.PartyFinder.Values) { - if (this.Plugin.Config.FilterStatus.TryGetValue(def.Id, out var enabled) && enabled && def.Matches(XivChatType.None, desc)) { - return def.Id; - } - } - - // check for custom filters if enabled - if (this.Plugin.Config.CustomPFFilter && PartyFinder.MatchesCustomFilters(desc, this.Plugin.Config)) { - return "custom"; - } - - return null; - } - - private IntPtr HandleSummary(IntPtr param1, IntPtr param2, byte param3) { - this._clearOnNext = true; - - return this._handleSummaryHook!.Original(param1, param2, param3); - } - private static bool SetReason(out string reason, string value) { reason = value; return true; diff --git a/NoSoliciting/ILRepack.targets b/NoSoliciting/ILRepack.targets index e0736c7..97d87e4 100644 --- a/NoSoliciting/ILRepack.targets +++ b/NoSoliciting/ILRepack.targets @@ -9,6 +9,7 @@ + { - string? resp = null; - try { - using var client = new WebClient(); - this.LastReportStatus = ReportStatus.InProgress; - var reportUrl = this.Plugin.MlFilter?.ReportUrl; - if (reportUrl != null) { - resp = await client.UploadStringTaskAsync(reportUrl, message.ToJson()).ConfigureAwait(true); - } - } catch (Exception) { - // ignored - } - - this.LastReportStatus = resp == "{\"message\":\"ok\"}" ? ReportStatus.Successful : ReportStatus.Failure; - PluginLog.Log(resp == null - ? "Report not sent. ML model not set." - : $"Report sent. Response: {resp}"); - }); + this.ReportMessage(message); ImGui.CloseCurrentPopup(); } @@ -308,6 +291,36 @@ namespace NoSoliciting.Interface { return clicked; } + internal void ReportMessage(Message message) { + Task.Run(async () => await this.ReportMessageAsync(message)); + } + + internal async Task ReportMessageAsync(Message message) { + string? resp = null; + try { + using var client = new WebClient(); + this.LastReportStatus = ReportStatus.InProgress; + var reportUrl = this.Plugin.MlFilter?.ReportUrl; + if (reportUrl != null) { + resp = await client.UploadStringTaskAsync(reportUrl, message.ToJson()).ConfigureAwait(true); + } + } catch (Exception) { + // ignored + } + + var status = resp == "{\"message\":\"ok\"}" ? ReportStatus.Successful : ReportStatus.Failure; + if (status == ReportStatus.Failure) { + PluginLog.LogWarning($"Failed to report message:\n{resp}"); + } + + this.LastReportStatus = status; + PluginLog.Log(resp == null + ? "Report not sent. ML model not set." + : $"Report sent. Response: {resp}"); + + return status; + } + #endregion } } diff --git a/NoSoliciting/Interface/Settings.cs b/NoSoliciting/Interface/Settings.cs index 2dedf78..0747efe 100755 --- a/NoSoliciting/Interface/Settings.cs +++ b/NoSoliciting/Interface/Settings.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Generic; using System.Numerics; +using System.Reflection; using System.Text.RegularExpressions; +using CheapLoc; using Dalamud.Interface; +using Dalamud.Plugin; using ImGuiNET; using NoSoliciting.Ml; @@ -38,26 +41,13 @@ namespace NoSoliciting.Interface { } public void Draw() { - if (!this.ShowSettings || !ImGui.Begin($"{this.Plugin.Name} settings", ref this._showSettings)) { + var windowTitle = Loc.Localize("Settings", $"{this.Plugin.Name} settings"); + if (!this.ShowSettings || !ImGui.Begin($"{windowTitle}###NoSoliciting settings", ref this._showSettings)) { return; } - var modes = new[] { - "Machine learning (default)", - "Definition matchers (obsolete)", - }; - var modeIndex = this.Plugin.Config.UseMachineLearning ? 0 : 1; - if (ImGui.Combo("Filter mode", ref modeIndex, modes, modes.Length)) { - this.Plugin.Config.UseMachineLearning = modeIndex == 0; - this.Plugin.Config.Save(); - - if (this.Plugin.Config.UseMachineLearning) { - this.Plugin.InitialiseMachineLearning(false); - } - } - var advanced = this.Plugin.Config.AdvancedMode; - if (ImGui.Checkbox("Advanced mode", ref advanced)) { + if (ImGui.Checkbox(Loc.Localize("AdvancedMode", "Advanced mode"), ref advanced)) { this.Plugin.Config.AdvancedMode = advanced; this.Plugin.Config.Save(); } @@ -68,23 +58,19 @@ namespace NoSoliciting.Interface { return; } - if (this.Plugin.Config.UseMachineLearning) { - this.DrawMachineLearningConfig(); - } else { - this.DrawDefinitionsConfig(); - } + this.DrawMachineLearningConfig(); this.DrawOtherFilters(); - if (ImGui.BeginTabItem("Other")) { + if (ImGui.BeginTabItem(Loc.Localize("OtherTab", "Other"))) { var logFilteredPfs = this.Plugin.Config.LogFilteredPfs; - if (ImGui.Checkbox("Log filtered PFs", ref logFilteredPfs)) { + if (ImGui.Checkbox(Loc.Localize("LogFilteredPfs", "Log filtered PFs"), ref logFilteredPfs)) { this.Plugin.Config.LogFilteredPfs = logFilteredPfs; this.Plugin.Config.Save(); } var logFilteredMessages = this.Plugin.Config.LogFilteredChat; - if (ImGui.Checkbox("Log filtered messages", ref logFilteredMessages)) { + if (ImGui.Checkbox(Loc.Localize("LogFilteredMessages", "Log filtered messages"), ref logFilteredMessages)) { this.Plugin.Config.LogFilteredChat = logFilteredMessages; this.Plugin.Config.Save(); } @@ -96,7 +82,7 @@ namespace NoSoliciting.Interface { ImGui.Separator(); - if (ImGui.Button("Show reporting window")) { + if (ImGui.Button(Loc.Localize("ShowReportingWindow", "Show reporting window"))) { this.Ui.Report.Open(); } @@ -112,7 +98,7 @@ namespace NoSoliciting.Interface { this.DrawBasicMachineLearningConfig(); } - if (!ImGui.BeginTabItem("Model")) { + if (!ImGui.BeginTabItem(Loc.Localize("ModelTab", "Model"))) { return; } @@ -123,7 +109,7 @@ namespace NoSoliciting.Interface { ImGui.TextUnformatted($"Last error: {lastError}"); } - if (ImGui.Button("Update model")) { + if (ImGui.Button(Loc.Localize("UpdateModel", "Update model"))) { // prevent issues when people spam the button if (ImGui.GetIO().KeyCtrl || this.Plugin.MlStatus is MlFilterStatus.Uninitialised or MlFilterStatus.Initialised) { this.Plugin.MlFilter?.Dispose(); @@ -137,7 +123,7 @@ namespace NoSoliciting.Interface { } private void DrawBasicMachineLearningConfig() { - if (!ImGui.BeginTabItem("Filters")) { + if (!ImGui.BeginTabItem(Loc.Localize("FiltersTab", "Filters"))) { return; } @@ -168,13 +154,13 @@ namespace NoSoliciting.Interface { } private void DrawAdvancedMachineLearningConfig() { - if (!ImGui.BeginTabItem("Filters")) { + if (!ImGui.BeginTabItem(Loc.Localize("FiltersTab", "Filters"))) { return; } ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(255f, 204f, 0f, 1f)); - ImGui.TextUnformatted("Do not change advanced settings unless you know what you are doing."); - ImGui.TextUnformatted("The machine learning model was trained with certain channels in mind."); + ImGui.TextUnformatted(Loc.Localize("AdvancedWarning1", "Do not change advanced settings unless you know what you are doing.")); + ImGui.TextUnformatted(Loc.Localize("AdvancedWarning2", "The machine learning model was trained with certain channels in mind.")); ImGui.PopStyleColor(); foreach (var category in MessageCategoryExt.UiOrder) { @@ -217,91 +203,16 @@ namespace NoSoliciting.Interface { #endregion - #region Definitions config - - private void DrawDefinitionsConfig() { - if (this.Plugin.Config.AdvancedMode) { - this.DrawDefsAdvancedSettings(); - } else { - this.DrawDefsBasicSettings(); - } - - this.DrawDefinitionsTab(); - } - - private void DrawDefinitionsTab() { - if (!ImGui.BeginTabItem("Definitions")) { - return; - } - - if (this.Plugin.Definitions != null) { - ImGui.TextUnformatted($"Version: {this.Plugin.Definitions.Version}"); - } - - if (Definitions.LastUpdate != null) { - ImGui.TextUnformatted($"Last update: {Definitions.LastUpdate}"); - } - - var error = Definitions.LastError; - if (error != null) { - ImGui.TextUnformatted($"Last error: {error}"); - } - - if (ImGui.Button("Update definitions")) { - this.Plugin.UpdateDefinitions(); - } - - ImGui.EndTabItem(); - } - - private void DrawDefsBasicSettings() { - if (this.Plugin.Definitions == null) { - return; - } - - if (!ImGui.BeginTabItem("Filters")) { - return; - } - - this.DrawCheckboxes(this.Plugin.Definitions.Chat.Values, true, "chat"); - - ImGui.Separator(); - - this.DrawCheckboxes(this.Plugin.Definitions.PartyFinder.Values, true, "Party Finder"); - - ImGui.EndTabItem(); - } - - private void DrawDefsAdvancedSettings() { - if (this.Plugin.Definitions == null) { - return; - } - - if (ImGui.BeginTabItem("Chat")) { - this.DrawCheckboxes(this.Plugin.Definitions.Chat.Values, false, "chat"); - - ImGui.EndTabItem(); - } - - if (ImGui.BeginTabItem("Party Finder")) { - this.DrawCheckboxes(this.Plugin.Definitions.PartyFinder.Values, false, "Party Finder"); - - ImGui.EndTabItem(); - } - } - - #endregion - #region Other config private void DrawOtherFilters() { - if (!ImGui.BeginTabItem("Other filters")) { + if (!ImGui.BeginTabItem(Loc.Localize("OtherFiltersTab", "Other filters"))) { return; } - if (ImGui.CollapsingHeader("Chat filters")) { + if (ImGui.CollapsingHeader(Loc.Localize("ChatFilters", "Chat filters"))) { var customChat = this.Plugin.Config.CustomChatFilter; - if (ImGui.Checkbox("Enable custom chat filters", ref customChat)) { + if (ImGui.Checkbox(Loc.Localize("EnableCustomChatFilters", "Enable custom chat filters"), ref customChat)) { this.Plugin.Config.CustomChatFilter = customChat; this.Plugin.Config.Save(); } @@ -313,22 +224,22 @@ namespace NoSoliciting.Interface { } } - if (ImGui.CollapsingHeader("Party Finder filters")) { + if (ImGui.CollapsingHeader(Loc.Localize("PartyFinderFilters", "Party Finder filters"))) { var filterHugeItemLevelPFs = this.Plugin.Config.FilterHugeItemLevelPFs; // ReSharper disable once InvertIf - if (ImGui.Checkbox("Filter PFs with item level above maximum", ref filterHugeItemLevelPFs)) { + if (ImGui.Checkbox(Loc.Localize("FilterIlvlPfs", "Filter PFs with item level above maximum"), ref filterHugeItemLevelPFs)) { this.Plugin.Config.FilterHugeItemLevelPFs = filterHugeItemLevelPFs; this.Plugin.Config.Save(); } var considerPrivate = this.Plugin.Config.ConsiderPrivatePfs; - if (ImGui.Checkbox("Apply filters to private Party Finder listings", ref considerPrivate)) { + if (ImGui.Checkbox(Loc.Localize("FilterPrivatePfs", "Apply filters to private Party Finder listings"), ref considerPrivate)) { this.Plugin.Config.ConsiderPrivatePfs = considerPrivate; this.Plugin.Config.Save(); } var customPf = this.Plugin.Config.CustomPFFilter; - if (ImGui.Checkbox("Enable custom Party Finder filters", ref customPf)) { + if (ImGui.Checkbox(Loc.Localize("EnableCustomPartyFinderFilters", "Enabled custom Party Finder filters"), ref customPf)) { this.Plugin.Config.CustomPFFilter = customPf; this.Plugin.Config.Save(); } @@ -346,7 +257,7 @@ namespace NoSoliciting.Interface { private void DrawCustom(string name, ref List substrings, ref List regexes) { ImGui.Columns(2); - ImGui.TextUnformatted("Substrings to filter"); + ImGui.TextUnformatted(Loc.Localize("SubstringsToFilter", "Substrings to filter")); if (ImGui.BeginChild($"##{name}-substrings", new Vector2(0, 175))) { for (var i = 0; i < substrings.Count; i++) { var input = substrings[i]; @@ -375,7 +286,7 @@ namespace NoSoliciting.Interface { ImGui.NextColumn(); - ImGui.TextUnformatted("Regular expressions to filter"); + ImGui.TextUnformatted(Loc.Localize("RegularExpressionsToFilter", "Regular expressions to filter")); if (ImGui.BeginChild($"##{name}-regexes", new Vector2(0, 175))) { for (var i = 0; i < regexes.Count; i++) { var input = regexes[i]; @@ -411,29 +322,13 @@ namespace NoSoliciting.Interface { ImGui.Columns(1); // ReSharper disable once InvertIf - if (ImGui.Button($"Save filters##{name}-save")) { + var saveLoc = Loc.Localize("SaveFilters", "Save filters"); + if (ImGui.Button($"{saveLoc}##{name}-save")) { this.Plugin.Config.Save(); this.Plugin.Config.CompileRegexes(); } } #endregion - - #region Utility - - private void DrawCheckboxes(IEnumerable defs, bool basic, string labelFillIn) { - foreach (var def in defs) { - this.Plugin.Config.FilterStatus.TryGetValue(def.Id, out var enabled); - var label = basic ? def.Option.Basic : def.Option.Advanced; - label = label.Replace("{}", labelFillIn); - // ReSharper disable once InvertIf - if (ImGui.Checkbox(label, ref enabled)) { - this.Plugin.Config.FilterStatus[def.Id] = enabled; - this.Plugin.Config.Save(); - } - } - } - - #endregion } } diff --git a/NoSoliciting/Message.cs b/NoSoliciting/Message.cs index 39b2d77..6920b53 100644 --- a/NoSoliciting/Message.cs +++ b/NoSoliciting/Message.cs @@ -14,8 +14,11 @@ using NoSoliciting.Ml; #endif namespace NoSoliciting { + [Serializable] public class Message { public Guid Id { get; } + [JsonIgnore] + public uint ActorId { get; } public uint DefinitionsVersion { get; } public DateTime Timestamp { get; } public ChatType ChatType { get; } @@ -24,27 +27,18 @@ namespace NoSoliciting { public bool Ml { get; } public string? FilterReason { get; } - public Message(uint defsVersion, ChatType type, SeString sender, SeString content, bool ml, string? reason) { + public Message(uint defsVersion, ChatType type, uint actorId, SeString sender, SeString content, bool ml, string? reason) { this.Id = Guid.NewGuid(); this.DefinitionsVersion = defsVersion; this.Timestamp = DateTime.Now; this.ChatType = type; + this.ActorId = actorId; this.Sender = sender; this.Content = content; this.Ml = ml; this.FilterReason = reason; } - public Message(uint defsVersion, ChatType type, string sender, string content, bool ml, string? reason) : this( - defsVersion, - type, - new SeString(new Payload[] {new TextPayload(sender)}), - new SeString(new Payload[] {new TextPayload(content)}), - ml, - reason - ) { - } - [Serializable] [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))] private class JsonMessage { diff --git a/NoSoliciting/Ml/Models.cs b/NoSoliciting/Ml/Models.cs index 742b3d3..1849f9b 100644 --- a/NoSoliciting/Ml/Models.cs +++ b/NoSoliciting/Ml/Models.cs @@ -1,4 +1,6 @@ using System; +using CheapLoc; +using Dalamud; namespace NoSoliciting.Ml { public enum MessageCategory { @@ -42,7 +44,6 @@ namespace NoSoliciting.Ml { }; #if DEBUG - public static string ToModelName(this MessageCategory category) => category switch { MessageCategory.Trade => "TRADE", MessageCategory.FreeCompany => "FC", @@ -74,30 +75,30 @@ namespace NoSoliciting.Ml { #endif public static string Name(this MessageCategory category) => category switch { - MessageCategory.Trade => "Trade ads", - MessageCategory.FreeCompany => "Free Company ads", - MessageCategory.Normal => "Normal messages", - MessageCategory.Phishing => "Phishing messages", - MessageCategory.RmtContent => "RMT (content)", - MessageCategory.RmtGil => "RMT (gil)", - MessageCategory.Roleplaying => "Roleplaying ads", - MessageCategory.Static => "Static recruitment", - MessageCategory.Community => "Community ads", - MessageCategory.StaticSub => "Static substitutes", + MessageCategory.Trade => Loc.Localize("TradeCategory", "Trade ads"), + MessageCategory.FreeCompany => Loc.Localize("FreeCompanyCategory", "Free Company ads"), + MessageCategory.Normal => Loc.Localize("NormalCategory", "Normal messages"), + MessageCategory.Phishing => Loc.Localize("PhishingCategory", "Phishing messages"), + MessageCategory.RmtContent => Loc.Localize("RmtContentCategory", "RMT (content)"), + MessageCategory.RmtGil => Loc.Localize("RmtGilCategory", "RMT (gil)"), + MessageCategory.Roleplaying => Loc.Localize("RoleplayingCategory", "Roleplaying ads"), + MessageCategory.Static => Loc.Localize("StaticCategory", "Static recruitment"), + MessageCategory.Community => Loc.Localize("CommunityCategory", "Community ads"), + MessageCategory.StaticSub => Loc.Localize("StaticSubCategory", "Static substitutes"), _ => throw new ArgumentException("Invalid category", nameof(category)), }; public static string Description(this MessageCategory category) => category switch { - MessageCategory.Trade => "Messages advertising trading items or services for gil, such as omnicrafters looking for work or people selling rare items off the market board", - MessageCategory.FreeCompany => "Advertisements for Free Companies", - MessageCategory.Normal => "Normal messages that should not be filtered", - MessageCategory.Phishing => "Messages trying to trick you into revealing your account details in order to steal your account", - MessageCategory.RmtContent => "Real-money trade involving content (also known as content sellers)", - MessageCategory.RmtGil => "Real-money trade involving gil or items (also known as RMT bots)", - MessageCategory.Roleplaying => "Advertisements for personal RP, RP communities, venues, or anything else related to roleplaying", - MessageCategory.Static => "Statics looking for members or players looking for a static", - MessageCategory.Community => "Advertisements for general-purpose communities, generally Discord servers", - MessageCategory.StaticSub => "Statics looking for fill-ins of missing members for clears", + MessageCategory.Trade => Loc.Localize("TradeDescription", "Messages advertising trading items or services for gil, such as omnicrafters looking for work or people selling rare items off the market board"), + MessageCategory.FreeCompany => Loc.Localize("FreeCompanyDescription", "Advertisements for Free Companies"), + MessageCategory.Normal => Loc.Localize("NormalDescription", "Normal messages that should not be filtered"), + MessageCategory.Phishing => Loc.Localize("PhishingDescription", "Messages trying to trick you into revealing your account details in order to steal your account"), + MessageCategory.RmtContent => Loc.Localize("RmtContentDescription", "Real-money trade involving content (also known as content sellers)"), + MessageCategory.RmtGil => Loc.Localize("RmtGilDescription", "Real-money trade involving gil or items (also known as RMT bots)"), + MessageCategory.Roleplaying => Loc.Localize("RoleplayingDescription", "Advertisements for personal RP, RP communities, venues, or anything else related to roleplaying"), + MessageCategory.Static => Loc.Localize("StaticDescription", "Statics looking for members or players looking for a static"), + MessageCategory.Community => Loc.Localize("CommunityDescription", "Advertisements for general-purpose communities, generally Discord servers"), + MessageCategory.StaticSub => Loc.Localize("StaticSubDescription", "Statics looking for fill-ins of missing members for clears"), _ => throw new ArgumentException("Invalid category", nameof(category)), }; } diff --git a/NoSoliciting/NoSoliciting.csproj b/NoSoliciting/NoSoliciting.csproj index af33ca0..4438fe2 100755 --- a/NoSoliciting/NoSoliciting.csproj +++ b/NoSoliciting/NoSoliciting.csproj @@ -9,6 +9,10 @@ x64 + + $(AppData)\XIVLauncher\addon\Hooks\dev\CheapLoc.dll + False + $(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll False @@ -33,6 +37,9 @@ $(AppData)\XIVLauncher\addon\Hooks\dev\Newtonsoft.Json.dll False + + ..\..\XivCommon\XivCommon\bin\Release\net48\XivCommon.dll + @@ -44,18 +51,8 @@ - - True - True - Resources.resx - - - - - ResXFileCodeGenerator - Resources.Designer.cs - + diff --git a/NoSoliciting/Plugin.cs b/NoSoliciting/Plugin.cs index 2983ee1..088787b 100644 --- a/NoSoliciting/Plugin.cs +++ b/NoSoliciting/Plugin.cs @@ -4,8 +4,11 @@ using System.Collections.Generic; using System.IO; using System.Reflection; using System.Threading.Tasks; +using CheapLoc; +using Dalamud; using NoSoliciting.Interface; using NoSoliciting.Ml; +using XivCommon; namespace NoSoliciting { public class Plugin : IDalamudPlugin { @@ -17,13 +20,12 @@ namespace NoSoliciting { public DalamudPluginInterface Interface { get; private set; } = null!; public PluginConfiguration Config { get; private set; } = null!; + public XivCommonBase Common { get; private set; } = null!; public PluginUi Ui { get; private set; } = null!; public Commands Commands { get; private set; } = null!; - public Definitions? Definitions { get; private set; } + private ContextMenu ContextMenu { get; set; } = null!; public MlFilterStatus MlStatus { get; set; } = MlFilterStatus.Uninitialised; public MlFilter? MlFilter { get; set; } - public bool MlReady => this.Config.UseMachineLearning && this.MlFilter != null; - public bool DefsReady => !this.Config.UseMachineLearning && this.Definitions != null; private readonly List _messageHistory = new(); public IEnumerable MessageHistory => this._messageHistory; @@ -42,19 +44,20 @@ namespace NoSoliciting { this.Interface = pluginInterface; + Loc.Setup(Resourcer.Resource.AsString("Resources/en.json"), Assembly.GetAssembly(typeof(Plugin))); + this.Config = this.Interface.GetPluginConfig() as PluginConfiguration ?? new PluginConfiguration(); this.Config.Initialise(this.Interface); + this.Common = new XivCommonBase(this.Interface, Hooks.PartyFinder | Hooks.ContextMenu); + this.Ui = new PluginUi(this); this.Commands = new Commands(this); - - this.UpdateDefinitions(); + this.ContextMenu = new ContextMenu(this); this.Filter = new Filter(this); - if (this.Config.UseMachineLearning) { - this.InitialiseMachineLearning(false); - } + this.InitialiseMachineLearning(false); // pre-compute the max ilvl to prevent stutter try { @@ -70,10 +73,14 @@ namespace NoSoliciting { } if (disposing) { + Loc.ExportLocalizable(); + this.Filter.Dispose(); this.MlFilter?.Dispose(); + this.ContextMenu.Dispose(); this.Commands.Dispose(); this.Ui.Dispose(); + this.Common.Dispose(); } this._disposedValue = true; @@ -96,18 +103,6 @@ namespace NoSoliciting { }); } - internal void UpdateDefinitions() { - Task.Run(async () => { - var defs = await Definitions.UpdateAndCache(this).ConfigureAwait(true); - // this shouldn't be possible, but what do I know - if (defs != null) { - defs.Initialise(this); - this.Definitions = defs; - Definitions.LastUpdate = DateTime.Now; - } - }); - } - public void AddMessageHistory(Message message) { this._messageHistory.Insert(0, message); diff --git a/NoSoliciting/PluginConfiguration.cs b/NoSoliciting/PluginConfiguration.cs index 53d0ae3..94f53ca 100644 --- a/NoSoliciting/PluginConfiguration.cs +++ b/NoSoliciting/PluginConfiguration.cs @@ -51,8 +51,6 @@ namespace NoSoliciting { public bool FilterHugeItemLevelPFs { get; set; } - public bool UseMachineLearning { get; set; } = true; - public HashSet BasicMlFilters { get; set; } = new() { MessageCategory.RmtGil, MessageCategory.RmtContent, diff --git a/NoSoliciting/Properties/Resources.Designer.cs b/NoSoliciting/Properties/Resources.Designer.cs deleted file mode 100644 index 87aee20..0000000 --- a/NoSoliciting/Properties/Resources.Designer.cs +++ /dev/null @@ -1,84 +0,0 @@ -//------------------------------------------------------------------------------ -// -// 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. -// -//------------------------------------------------------------------------------ - -namespace NoSoliciting.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [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; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// 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]";. - /// - internal static string DefaultDefinitions { - get { - return ResourceManager.GetString("DefaultDefinitions", resourceCulture); - } - } - } -} diff --git a/NoSoliciting/Properties/Resources.resx b/NoSoliciting/Properties/Resources.resx deleted file mode 100644 index 7bcd3ee..0000000 --- a/NoSoliciting/Properties/Resources.resx +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - ..\definitions.yaml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 - - diff --git a/NoSoliciting/Resources/en.json b/NoSoliciting/Resources/en.json new file mode 100755 index 0000000..adaf9c2 --- /dev/null +++ b/NoSoliciting/Resources/en.json @@ -0,0 +1,170 @@ +{ + "ReportToNoSoliciting": { + "message": "Report to NoSoliciting", + "description": "ContextMenu.OnOpenContextMenu" + }, + "TradeCategory": { + "message": "Trade ads", + "description": "MessageCategoryExt.Name" + }, + "FreeCompanyCategory": { + "message": "Free Company ads", + "description": "MessageCategoryExt.Name" + }, + "NormalCategory": { + "message": "Normal messages", + "description": "MessageCategoryExt.Name" + }, + "PhishingCategory": { + "message": "Phishing messages", + "description": "MessageCategoryExt.Name" + }, + "RmtContentCategory": { + "message": "RMT (content)", + "description": "MessageCategoryExt.Name" + }, + "RmtGilCategory": { + "message": "RMT (gil)", + "description": "MessageCategoryExt.Name" + }, + "RoleplayingCategory": { + "message": "Roleplaying ads", + "description": "MessageCategoryExt.Name" + }, + "StaticCategory": { + "message": "Static recruitment", + "description": "MessageCategoryExt.Name" + }, + "CommunityCategory": { + "message": "Community ads", + "description": "MessageCategoryExt.Name" + }, + "StaticSubCategory": { + "message": "Static substitutes", + "description": "MessageCategoryExt.Name" + }, + "TradeDescription": { + "message": "Messages advertising trading items or services for gil, such as omnicrafters looking for work or people selling rare items off the market board", + "description": "MessageCategoryExt.Description" + }, + "FreeCompanyDescription": { + "message": "Advertisements for Free Companies", + "description": "MessageCategoryExt.Description" + }, + "NormalDescription": { + "message": "Normal messages that should not be filtered", + "description": "MessageCategoryExt.Description" + }, + "PhishingDescription": { + "message": "Messages trying to trick you into revealing your account details in order to steal your account", + "description": "MessageCategoryExt.Description" + }, + "RmtContentDescription": { + "message": "Real-money trade involving content (also known as content sellers)", + "description": "MessageCategoryExt.Description" + }, + "RmtGilDescription": { + "message": "Real-money trade involving gil or items (also known as RMT bots)", + "description": "MessageCategoryExt.Description" + }, + "RoleplayingDescription": { + "message": "Advertisements for personal RP, RP communities, venues, or anything else related to roleplaying", + "description": "MessageCategoryExt.Description" + }, + "StaticDescription": { + "message": "Statics looking for members or players looking for a static", + "description": "MessageCategoryExt.Description" + }, + "CommunityDescription": { + "message": "Advertisements for general-purpose communities, generally Discord servers", + "description": "MessageCategoryExt.Description" + }, + "StaticSubDescription": { + "message": "Statics looking for fill-ins of missing members for clears", + "description": "MessageCategoryExt.Description" + }, + " settings": { + "message": null, + "description": "Settings.Draw" + }, + "AdvancedMode": { + "message": "Advanced mode", + "description": "Settings.Draw" + }, + "OtherTab": { + "message": "Other", + "description": "Settings.Draw" + }, + "LogFilteredPfs": { + "message": "Log filtered PFs", + "description": "Settings.Draw" + }, + "LogFilteredMessages": { + "message": "Log filtered messages", + "description": "Settings.Draw" + }, + "ShowReportingWindow": { + "message": "Show reporting window", + "description": "Settings.Draw" + }, + "ModelTab": { + "message": "Model", + "description": "Settings.DrawMachineLearningConfig" + }, + "UpdateModel": { + "message": "Update model", + "description": "Settings.DrawMachineLearningConfig" + }, + "FiltersTab": { + "message": "Filters", + "description": "Settings.DrawBasicMachineLearningConfig" + }, + "AdvancedWarning1": { + "message": "Do not change advanced settings unless you know what you are doing.", + "description": "Settings.DrawAdvancedMachineLearningConfig" + }, + "AdvancedWarning2": { + "message": "The machine learning model was trained with certain channels in mind.", + "description": "Settings.DrawAdvancedMachineLearningConfig" + }, + "OtherFiltersTab": { + "message": "Other filters", + "description": "Settings.DrawOtherFilters" + }, + "ChatFilters": { + "message": "Chat filters", + "description": "Settings.DrawOtherFilters" + }, + "EnableCustomChatFilters": { + "message": "Enable custom chat filters", + "description": "Settings.DrawOtherFilters" + }, + "PartyFinderFilters": { + "message": "Party Finder filters", + "description": "Settings.DrawOtherFilters" + }, + "FilterIlvlPfs": { + "message": "Filter PFs with item level above maximum", + "description": "Settings.DrawOtherFilters" + }, + "FilterPrivatePfs": { + "message": "Apply filters to private Party Finder listings", + "description": "Settings.DrawOtherFilters" + }, + "EnableCustomPartyFinderFilters": { + "message": "Enabled custom Party Finder filters", + "description": "Settings.DrawOtherFilters" + }, + "SubstringsToFilter": { + "message": "Substrings to filter", + "description": "Settings.DrawCustom" + }, + "RegularExpressionsToFilter": { + "message": "Regular expressions to filter", + "description": "Settings.DrawCustom" + }, + "SaveFilters": { + "message": "Save filters", + "description": "Settings.DrawCustom" + } +} diff --git a/NoSoliciting/definitions.yaml b/NoSoliciting/definitions.yaml deleted file mode 100644 index b203963..0000000 --- a/NoSoliciting/definitions.yaml +++ /dev/null @@ -1,150 +0,0 @@ -# THIS FILE IS DEPRECATED AND WILL NO LONGER RECEIVE UPDATES. Please -# see the file called data.csv in NoSoliciting.Trainer for how to -# update the new machine learning model that has replaced this file. - -# This file defines the filters that NoSoliciting will use for -# built-in filters. - -# The version should be incremented for each commit including changes -# to this file. - -# There are two main sections: chat and party_finder. The chat and -# party_finder sections are for their respective areas (the chat log -# and the Party Finder window). - -# Each subsection is a separate built-in filter that can be toggled on -# and off. The option shown in the UI is defined in the subsection. - -# Subsections can have ignore_case (defaults to false) and normalise -# (defaults to true) set. ignore_case will ignore casing for matching -# against the matchers, and normalise will normalise text prior to -# matching. Text normalisation consists of turning FFXIV-specific -# unicode symbols into normal ASCII characters and running a NFKD -# unicode decomposition on the result. - -# Subsections also may filter based on channels with the channels key. -# A list of channels may be specified, and the message will be ignored -# if not in one of the specified channels. For the Party Finder, the -# channel is always None. An empty list (or missing channels key) will -# ignore the channel. - -# Each subsection may specify whether it is enabled by default with -# the default key. This should be used sparingly. This defaults to -# false. - -# The real meat of the file is the matchers. There are two types of -# matchers: required and likely. Both types have categories of strings -# or regular expressions that should match. For required matchers, at -# least one string or regex should match in *all* categories. For -# likely matchers, at least one string or regex should match in the -# value of likelihood_threshold (or greater) categories. - -# If both required and likely matchers are specified, they both must -# match. This means that all the categories of the required matchers -# must find a match, *and* that at least likelihood_threshold matchers -# must find a match in likely_matchers. - -# Substring matchers are faster than regular expressions and are -# specified just by using a string. Regular expression matchers are -# slower but more flexible, and they are specified by using a regex -# key, as can be seen below. - -version: 68 - -# This is the URL the plugin will POST reports to. -report_url: https://nosol.annaclemens.io/report - -chat: - rmt: - option: - basic: Filter RMT from chat - advanced: Enable built-in chat RMT filter - default: true - required_matchers: - - - 4KGOLD - - We have sufficient stock - - PVPBANK.COM - - Gil for free - - www.so9.com - - Fast & Convenient - - Cheap & Safety Guarantee - - 【Code|A O A U E - - igfans - - 4KGOLD.COM - - Cheapest Gil with - - pvp and bank on google - - Selling Cheap GIL - - ff14mogstation.com - - Cheap Gil 1000k - - gilsforyou - - server 1000K = - - gils_selling - - E A S Y.C O M - - bonus code - - mins delivery guarantee - - Sell cheap - - Salegm.com - - cheap Mog - - Off Code - - FF14Mog.com - - ff14mog.com - - 使用する5%オ - - 5分納品 - - offers Fantasia - - 5GOLD.COM - - Buy Cheap gils - - regex: '【 .+ 、. = .+ 】' - - regex: 'finalfantasyxiv\.com-\w+\.\w+' # phishing - - regex: '\.com-\w+\.\w+/' # phishing - - regex: '(?i)giveaway of \d+.?m.*discord.gg/.' # phishing - -party_finder: - rmt: - option: - basic: Filter RMT from Party Finder - advanced: Enable built-in Party Finder RMT filter - default: true - ignore_case: true - required_matchers: - # selling - - - sell - - s e l l - - $ell - - $ e l l - - sale - - s a l e - - price - - cheap - - gil only - # rmt groups - - minmax - - lalakuza - - legacy - - sk7 - - restocker - - 'fast&easy' - - fast and easy help - - '[helping]' - - lawyer - # more keywords - - deliver - - best team - - satisfaction - - coaching - - reliable - - regex: '\s#1' - - regex: '\bi\d{3}.+loot\b' - - regex: 'm\s*i\s*n\s*m\s*a\s*x' - - regex: 'pre.?order' - - regex: '\boffer' - # content - - - eden - - savage - - primal - - ultimate - - trial - - loot - - content - - ucob - - regex: '\bex\b' - - regex: e[1-9][0-2]?