feat: remove defs, add some loc, add context menu
This commit is contained in:
parent
c23a47ff1c
commit
11176694b8
|
@ -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! <LogH> 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 <Fang> 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,<Savage ※ Ultimates ※ BLU ※ Primals ※ Coaching> 一 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,<Midgar> 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 :)
|
||||
|
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,275 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Plugin;
|
||||
using NoSoliciting.Interface;
|
||||
using NoSoliciting.Properties;
|
||||
using YamlDotNet.Core;
|
||||
using YamlDotNet.Core.Events;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace NoSoliciting {
|
||||
public class Definitions {
|
||||
public static string? LastError { get; private set; }
|
||||
public static DateTime? LastUpdate { get; set; }
|
||||
|
||||
private const string Url = "https://git.sr.ht/~jkcclemens/NoSoliciting/blob/master/NoSoliciting/definitions.yaml";
|
||||
|
||||
public uint Version { get; private set; }
|
||||
public Uri ReportUrl { get; private set; }
|
||||
|
||||
public Dictionary<string, Definition> Chat { get; private set; }
|
||||
public Dictionary<string, Definition> PartyFinder { get; private set; }
|
||||
|
||||
public static async Task<Definitions> UpdateAndCache(Plugin plugin) {
|
||||
#if DEBUG
|
||||
return LoadDefaults();
|
||||
#endif
|
||||
|
||||
Definitions? defs = null;
|
||||
|
||||
var download = await Download().ConfigureAwait(true);
|
||||
if (download != null) {
|
||||
defs = download.Item1;
|
||||
|
||||
try {
|
||||
UpdateCache(plugin, download.Item2);
|
||||
} catch (IOException e) {
|
||||
PluginLog.Log("Could not update cache.");
|
||||
PluginLog.Log(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return defs ?? await CacheOrDefault(plugin).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
public static Definitions Load(string text) {
|
||||
var de = new DeserializerBuilder()
|
||||
.WithNamingConvention(UnderscoredNamingConvention.Instance)
|
||||
.WithTypeConverter(new MatcherConverter())
|
||||
.IgnoreUnmatchedProperties()
|
||||
.Build();
|
||||
return de.Deserialize<Definitions>(text);
|
||||
}
|
||||
|
||||
private static async Task<Definitions> CacheOrDefault(Plugin plugin) {
|
||||
if (plugin == null) {
|
||||
throw new ArgumentNullException(nameof(plugin), "Plugin cannot be null");
|
||||
}
|
||||
|
||||
var pluginFolder = 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<Tuple<Definitions, string>?> Download() {
|
||||
try {
|
||||
using var client = new WebClient();
|
||||
var text = await client.DownloadStringTaskAsync(Url).ConfigureAwait(true);
|
||||
LastError = null;
|
||||
return Tuple.Create(Load(text), text);
|
||||
} catch (Exception e) when (e is WebException 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<string, Definition>($"chat.{e.Key}", e.Value))
|
||||
.Concat(this.PartyFinder.Select(e => new KeyValuePair<string, Definition>($"party_finder.{e.Key}", e.Value)));
|
||||
|
||||
foreach (var entry in defs) {
|
||||
entry.Value.Initialise(entry.Key);
|
||||
if (!plugin.Config.FilterStatus.TryGetValue(entry.Key, out _)) {
|
||||
plugin.Config.FilterStatus[entry.Key] = entry.Value.Default;
|
||||
}
|
||||
}
|
||||
|
||||
plugin.Config.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public class Definition {
|
||||
private bool _initialised;
|
||||
|
||||
[YamlIgnore]
|
||||
public string Id { get; private set; }
|
||||
|
||||
public List<List<Matcher>> RequiredMatchers { get; private set; } = new();
|
||||
public List<List<Matcher>> LikelyMatchers { get; private set; } = new();
|
||||
public int LikelihoodThreshold { get; private set; }
|
||||
public bool IgnoreCase { get; private set; }
|
||||
public bool Normalise { get; private set; } = true;
|
||||
public List<XivChatType> Channels { get; private set; } = new();
|
||||
public OptionNames Option { get; private set; }
|
||||
public bool Default { get; private set; }
|
||||
|
||||
public void Initialise(string id) {
|
||||
if (this._initialised) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._initialised = true;
|
||||
|
||||
this.Id = id ?? throw new ArgumentNullException(nameof(id), "string cannot be null");
|
||||
|
||||
if (!this.IgnoreCase) {
|
||||
return;
|
||||
}
|
||||
|
||||
var allMatchers = this.LikelyMatchers
|
||||
.Concat(this.RequiredMatchers)
|
||||
.SelectMany(matchers => matchers);
|
||||
|
||||
foreach (var matcher in allMatchers) {
|
||||
matcher.MakeIgnoreCase();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Matches(XivChatType type, string text) {
|
||||
if (text == null) {
|
||||
throw new ArgumentNullException(nameof(text), "string cannot be null");
|
||||
}
|
||||
|
||||
if (this.Channels.Count != 0 && !this.Channels.Contains(type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.Normalise) {
|
||||
text = NoSolUtil.Normalise(text);
|
||||
}
|
||||
|
||||
if (this.IgnoreCase) {
|
||||
text = text.ToLowerInvariant();
|
||||
}
|
||||
|
||||
// ensure all required matchers match
|
||||
var allRequired = this.RequiredMatchers.All(matchers => matchers.Any(matcher => matcher.Matches(text)));
|
||||
if (!allRequired) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// calculate likelihood
|
||||
var likelihood = this.LikelyMatchers.Count(matchers => matchers.Any(matcher => matcher.Matches(text)));
|
||||
|
||||
// matches only if likelihood is greater than or equal the threshold
|
||||
return likelihood >= this.LikelihoodThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
public class Matcher {
|
||||
private string? substring;
|
||||
private Regex? regex;
|
||||
|
||||
public Matcher(string substring) {
|
||||
this.substring = substring ?? throw new ArgumentNullException(nameof(substring), "string cannot be null");
|
||||
}
|
||||
|
||||
public Matcher(Regex regex) {
|
||||
this.regex = regex ?? throw new ArgumentNullException(nameof(regex), "Regex cannot be null");
|
||||
}
|
||||
|
||||
internal void MakeIgnoreCase() {
|
||||
if (this.substring != null) {
|
||||
this.substring = this.substring.ToLowerInvariant();
|
||||
}
|
||||
|
||||
if (this.regex != null) {
|
||||
this.regex = new Regex(this.regex.ToString(), this.regex.Options | RegexOptions.IgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Matches(string text) {
|
||||
if (text == null) {
|
||||
throw new ArgumentNullException(nameof(text), "string cannot be null");
|
||||
}
|
||||
|
||||
if (this.substring != null) {
|
||||
return text.Contains(this.substring);
|
||||
}
|
||||
|
||||
if (this.regex != null) {
|
||||
return this.regex.IsMatch(text);
|
||||
}
|
||||
|
||||
throw new ApplicationException("Matcher created without substring or regex");
|
||||
}
|
||||
}
|
||||
|
||||
public class OptionNames {
|
||||
public string Basic { get; private set; }
|
||||
public string Advanced { get; private set; }
|
||||
}
|
||||
|
||||
internal sealed class MatcherConverter : IYamlTypeConverter {
|
||||
public bool Accepts(Type type) {
|
||||
return type == typeof(Matcher);
|
||||
}
|
||||
|
||||
public object ReadYaml(IParser parser, Type type) {
|
||||
Matcher matcher;
|
||||
|
||||
if (parser.TryConsume(out Scalar scalar)) {
|
||||
matcher = new Matcher(scalar.Value);
|
||||
} else if (parser.TryConsume(out MappingStart _)) {
|
||||
if (parser.Consume<Scalar>().Value != "regex") {
|
||||
throw new ArgumentException("matcher was an object but did not specify regex key");
|
||||
}
|
||||
|
||||
var regex = new Regex(parser.Consume<Scalar>().Value, RegexOptions.Compiled);
|
||||
matcher = new Matcher(regex);
|
||||
|
||||
parser.Consume<MappingEnd>();
|
||||
} else {
|
||||
throw new ArgumentException("invalid matcher");
|
||||
}
|
||||
|
||||
return matcher;
|
||||
}
|
||||
|
||||
public void WriteYaml(IEmitter emitter, object value, Type type) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<HandlePfSummaryDelegate>? _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<HandlePfSummaryDelegate>(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;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<InputAssemblies Include="$(OutputPath)\M*.dll"/>
|
||||
<InputAssemblies Include="$(OutputPath)\S*.dll"/>
|
||||
<InputAssemblies Include="$(OutputPath)\Y*.dll"/>
|
||||
<InputAssemblies Include="$(OutputPath)\XivCommon.dll"/>
|
||||
</ItemGroup>
|
||||
<ILRepack
|
||||
Parallel="true"
|
||||
|
|
|
@ -232,24 +232,7 @@ namespace NoSoliciting.Interface {
|
|||
ImGui.PopStyleColor();
|
||||
} else {
|
||||
if (ImGui.Button("Report")) {
|
||||
Task.Run(async () => {
|
||||
string? resp = null;
|
||||
try {
|
||||
using var client = new WebClient();
|
||||
this.LastReportStatus = ReportStatus.InProgress;
|
||||
var reportUrl = this.Plugin.MlFilter?.ReportUrl;
|
||||
if (reportUrl != null) {
|
||||
resp = await client.UploadStringTaskAsync(reportUrl, message.ToJson()).ConfigureAwait(true);
|
||||
}
|
||||
} 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<ReportStatus> 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<string> substrings, ref List<string> 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<Definition> defs, bool basic, string labelFillIn) {
|
||||
foreach (var def in defs) {
|
||||
this.Plugin.Config.FilterStatus.TryGetValue(def.Id, out var enabled);
|
||||
var label = basic ? def.Option.Basic : def.Option.Advanced;
|
||||
label = label.Replace("{}", labelFillIn);
|
||||
// ReSharper disable once InvertIf
|
||||
if (ImGui.Checkbox(label, ref enabled)) {
|
||||
this.Plugin.Config.FilterStatus[def.Id] = enabled;
|
||||
this.Plugin.Config.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="CheapLoc">
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\CheapLoc.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Dalamud">
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
|
@ -33,6 +37,9 @@
|
|||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="XivCommon, Version=1.4.0.0, Culture=neutral, PublicKeyToken=null">
|
||||
<HintPath>..\..\XivCommon\XivCommon\bin\Release\net48\XivCommon.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DalamudPackager" Version="1.2.1"/>
|
||||
|
@ -44,18 +51,8 @@
|
|||
<PackageReference Include="YamlDotNet" Version="11.1.1"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="..\NoSoliciting.MessageClassifier\bin\Release\net48\NoSoliciting.MessageClassifier.exe"/>
|
||||
<EmbeddedResource Include="Resources\en.json"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NoSoliciting.Interface\NoSoliciting.Interface.csproj"/>
|
||||
|
|
|
@ -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<Message> _messageHistory = new();
|
||||
public IEnumerable<Message> 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);
|
||||
|
||||
|
|
|
@ -51,8 +51,6 @@ namespace NoSoliciting {
|
|||
|
||||
public bool FilterHugeItemLevelPFs { get; set; }
|
||||
|
||||
public bool UseMachineLearning { get; set; } = true;
|
||||
|
||||
public HashSet<MessageCategory> BasicMlFilters { get; set; } = new() {
|
||||
MessageCategory.RmtGil,
|
||||
MessageCategory.RmtContent,
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace NoSoliciting.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NoSoliciting.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to # This file defines the filters that NoSoliciting will use for
|
||||
///# built-in filters.
|
||||
///
|
||||
///# The version should be incremented for each commit including changes
|
||||
///# to this file.
|
||||
///
|
||||
///# There are three main sections: chat, party_finder, and global. The
|
||||
///# chat and party_finder sections are for their respective areas (the
|
||||
///# chat log and the Party Finder window), and the global section
|
||||
///# applies to both.
|
||||
///
|
||||
///# Each subsection is a separate built-in filter that can be toggled on
|
||||
///# and off. The option shown in the UI is defined [rest of string was truncated]";.
|
||||
/// </summary>
|
||||
internal static string DefaultDefinitions {
|
||||
get {
|
||||
return ResourceManager.GetString("DefaultDefinitions", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="DefaultDefinitions" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\definitions.yaml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
|
||||
</data>
|
||||
</root>
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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]?
|
Loading…
Reference in New Issue