From 117acd2aff3bcd097815b85641e385fc9b46601d Mon Sep 17 00:00:00 2001 From: Pokled Date: Wed, 4 Nov 2026 02:27:54 +0000 Subject: [PATCH] =?UTF-8?q?fix(admin):=20OctoGuard,=20Mod=C3=A8les=20et=20?= =?UTF-8?q?Vue=20d'ensemble=20conformes=20=C3=A0=20la=20maquette?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La page Modèles affichait la liste des règles du profil de base au lieu des modèles de communauté de la maquette validée. Refaite : - Six cartes illustrées (Base Nodyx, Communauté jeunesse, Jeu compétitif, Entraide technique, Créatifs et artistes, Stricte et officielle), badge, « Voir le détail ». Les cinq modèles sont de vrais profils (lib/octoguard/templates.json), validés par le cœur en fr et en en. - « Voir le détail » : ce qui change, calculé par le serveur (/profile/preview), règle par règle ; « Appliquer le modèle » passe par /profile/import. Un modèle ajuste les règles de base et ajoute les siennes, n'écrase jamais la liste de mots de l'admin ; ses règles fermes arrivent en observation ; les règles personnalisées sont retirées avant l'envoi et affichées « gardées ». - Base Nodyx : les huit règles du socle et leur état, réinstallation d'un clic. Importer et Exporter dans l'en-tête, comme la maquette. - Vue d'ensemble : quatre chiffres sur une ligne dont la Justesse, quatre raccourcis (Modèles prêts ajouté), et l'état d'OctoGuard sous son nom dans la colonne (« Éteint · 7 règles prêtes »). --- nodyx-frontend/src/lib/locales/en.json | 57 +- nodyx-frontend/src/lib/locales/fr.json | 57 +- nodyx-frontend/src/lib/octoguard/rules.ts | 2 +- .../src/lib/octoguard/templates.json | 562 ++++++++++++++++++ nodyx-frontend/src/lib/octoguard/templates.ts | 33 + .../src/routes/admin/+layout.svelte | 8 +- .../routes/admin/octoguard/+layout.server.ts | 14 + .../src/routes/admin/octoguard/+page.svelte | 16 +- .../src/routes/admin/octoguard/og.css | 4 + .../admin/octoguard/templates/+page.server.ts | 9 +- .../admin/octoguard/templates/+page.svelte | 296 +++++---- 11 files changed, 919 insertions(+), 139 deletions(-) create mode 100644 nodyx-frontend/src/lib/octoguard/templates.json create mode 100644 nodyx-frontend/src/lib/octoguard/templates.ts create mode 100644 nodyx-frontend/src/routes/admin/octoguard/+layout.server.ts diff --git a/nodyx-frontend/src/lib/locales/en.json b/nodyx-frontend/src/lib/locales/en.json index fc27ea6f..37dbd924 100644 --- a/nodyx-frontend/src/lib/locales/en.json +++ b/nodyx-frontend/src/lib/locales/en.json @@ -5531,7 +5531,7 @@ "og2.err_domains_empty": "Enter at least one site.", "og2.err_domain": "“{{d}}” is not a valid site address (example: discord.gg).", "og2.words_too_many": "Beyond {{max}} words, the engine would ignore the rest. Remove a few.", - "og2.nav_templates": "Templates", + "og2.nav_templates": "Ready-made templates", "og2.saved": "Saved.", "og2.prev": "Previous", "og2.next": "Next", @@ -5571,7 +5571,7 @@ "og2.mute_lift": "Lift", "og2.mute_lift_yes": "Yes, lift", "og2.mute_lift_err": "The mute could not be lifted. Please try again.", - "og2.tpl_desc": "The base profile shipped with Nodyx, and sharing your rules between communities.", + "og2.tpl_desc": "A template is a rule set designed for a kind of community. Before applying, you see exactly what changes. Nothing is ever deleted.", "og2.tpl_base_title": "Nodyx base profile", "og2.tpl_base_desc": "Version {{v}}. Installed rules follow profile updates, except the ones you customised.", "og2.tpl_installed_count": "{{n}} of {{total}} installed", @@ -5582,7 +5582,7 @@ "og2.tpl_base_note": "A reinstalled firm rule comes back in observation: you judge before it acts.", "og2.tpl_export_title": "Export my rules", "og2.tpl_export_desc": "A file to keep or share with another Nodyx community. It contains no member data.", - "og2.tpl_export_btn": "Download the file", + "og2.tpl_export_btn": "Export my rules", "og2.tpl_export_err": "Export failed. Please try again.", "og2.tpl_import_title": "Import a profile", "og2.tpl_import_desc": "Pick a file exported by a Nodyx community. You see what changes before applying.", @@ -5774,5 +5774,54 @@ "og2.mv_s8_observe": "An observing rule does nothing visible: the member does not know it exists.", "og2.mv_s8_report": "At the Report level, the message goes through normally; only the team gets the report.", "og2.mv_s8_dm_tag": "Private messages", - "og2.mv_s8_dm": "OctoGuard does not monitor private messages." + "og2.mv_s8_dm": "OctoGuard does not monitor private messages.", + "og2.tpl_import_btn": "Import", + "og2.tpl_base_name": "Nodyx base", + "og2.tpl_base_desc2": "The base shipped with Nodyx: links, invites, mentions, flooding, all caps, new accounts, corrupted text, banned words. Gentle to start.", + "og2.tpl_rules_n": "{{n}} rules", + "og2.tpl_version": "Version {{v}}", + "og2.tpl_see": "See details", + "og2.tpl_youth_name": "Youth community", + "og2.tpl_youth_tag": "Caring", + "og2.tpl_youth_desc": "For a young audience: insults and adult content refused, links from accounts under 7 days old refused, mass mentions and all caps warned.", + "og2.tpl_youth_meta": "arrives in observation", + "og2.tpl_gaming_name": "Competitive gaming", + "og2.tpl_gaming_tag": "Reactive", + "og2.tpl_gaming_desc": "Fast exchanges: strict flooding rule with a 10-minute mute after 3 warnings, invites to other servers refused, all caps tolerated.", + "og2.tpl_gaming_meta": "strong anti-flood", + "og2.tpl_help_name": "Technical help", + "og2.tpl_help_tag": "Forum", + "og2.tpl_help_desc": "For help forums: documentation links allowed (up to 8 per message), link shorteners reported, mass mentions warned.", + "og2.tpl_help_meta": "links allowed", + "og2.tpl_creative_name": "Creators and artists", + "og2.tpl_creative_tag": "Open", + "og2.tpl_creative_desc": "Portfolios and shared work: links allowed (up to 12 per message), all caps tolerated, resale scam words reported to the team.", + "og2.tpl_creative_meta": "free sharing", + "og2.tpl_strict_name": "Strict and official", + "og2.tpl_strict_tag": "Firm", + "og2.tpl_strict_desc": "Brand or institution community: bursts of links, invites, corrupted text and new-account links refused, mentions and all caps warned, 24-hour mute after 2 warnings.", + "og2.tpl_strict_meta": "all arrives in observation", + "og2.tpl_note": "A template's firm rules arrive in observation: you judge their catches before they act. Your customised rules are never touched.", + "og2.tpl_changes": "{{name}}: what changes", + "og2.tpl_base_help": "The eight base rules. A removed rule is reinstalled in one click; a rule you changed stays as it is.", + "og2.tpl_preview_help": "Preview computed by the server before applying. Nothing is deleted, and your customised rules are not touched.", + "og2.tpl_preview_err": "The preview could not be computed. Please try again.", + "og2.tpl_apply": "Apply the template ({{n}})", + "og2.tpl_nothing": "Nothing to change", + "og2.tpl_state_add": "Added", + "og2.tpl_state_update": "Changed", + "og2.tpl_state_unchanged": "Unchanged", + "og2.tpl_state_kept": "Kept (customised)", + "og2.tpl_state_refused": "Refused", + "og2.tpl_state_missing": "Removed", + "og2.tpl_state_installed": "Installed", + "og2.tpl_state_customized": "Customised", + "og2.side_on": "Active · {{n}} rule(s)", + "og2.side_off": "Off · {{n}} rule(s) ready", + "og2.tile_templates": "Six profiles for your community.", + "og2.tile_templates_go": "Choose", + "og2.list_more": " and {{n}} more", + "og2.st_precision": "Accuracy", + "og2.st_precision_sub": "{{u}} false positive(s) undone out of {{n}}", + "og2.st_precision_none": "No action this week yet" } diff --git a/nodyx-frontend/src/lib/locales/fr.json b/nodyx-frontend/src/lib/locales/fr.json index 77d87a69..bcfce4b7 100644 --- a/nodyx-frontend/src/lib/locales/fr.json +++ b/nodyx-frontend/src/lib/locales/fr.json @@ -5531,7 +5531,7 @@ "og2.err_domains_empty": "Indiquez au moins un site.", "og2.err_domain": "« {{d}} » n'est pas une adresse de site valide (exemple : discord.gg).", "og2.words_too_many": "Au-delà de {{max}} mots, le moteur ignorerait les suivants. Retirez-en quelques-uns.", - "og2.nav_templates": "Modèles", + "og2.nav_templates": "Modèles prêts", "og2.saved": "Enregistré.", "og2.prev": "Précédent", "og2.next": "Suivant", @@ -5571,7 +5571,7 @@ "og2.mute_lift": "Lever", "og2.mute_lift_yes": "Oui, lever", "og2.mute_lift_err": "La sourdine n'a pas pu être levée. Réessayez.", - "og2.tpl_desc": "Le profil de base livré avec Nodyx, et le partage de vos règles d'une communauté à l'autre.", + "og2.tpl_desc": "Un modèle est un ensemble de règles pensé pour un type de communauté. Avant d'appliquer, vous voyez exactement ce qui change. Rien n'est jamais supprimé.", "og2.tpl_base_title": "Profil de base de Nodyx", "og2.tpl_base_desc": "Version {{v}}. Les règles installées suivent les mises à jour du profil, sauf celles que vous avez personnalisées.", "og2.tpl_installed_count": "{{n}} sur {{total}} installées", @@ -5582,7 +5582,7 @@ "og2.tpl_base_note": "Une règle réinstallée revient en observation si elle est ferme : vous jugez avant qu'elle agisse.", "og2.tpl_export_title": "Exporter mes règles", "og2.tpl_export_desc": "Un fichier à garder ou à partager avec une autre communauté Nodyx. Il ne contient aucune donnée de membre.", - "og2.tpl_export_btn": "Télécharger le fichier", + "og2.tpl_export_btn": "Exporter mes règles", "og2.tpl_export_err": "L'export a échoué. Réessayez.", "og2.tpl_import_title": "Importer un profil", "og2.tpl_import_desc": "Choisissez un fichier exporté par une communauté Nodyx. Vous voyez ce qui change avant d'appliquer.", @@ -5774,5 +5774,54 @@ "og2.mv_s8_observe": "Une règle qui observe ne fait rien de visible : le membre ne sait pas qu'elle existe.", "og2.mv_s8_report": "Au cran Signaler, le message passe normalement ; seule l'équipe reçoit le signalement.", "og2.mv_s8_dm_tag": "Messages privés", - "og2.mv_s8_dm": "OctoGuard ne surveille pas les messages privés." + "og2.mv_s8_dm": "OctoGuard ne surveille pas les messages privés.", + "og2.tpl_import_btn": "Importer", + "og2.tpl_base_name": "Base Nodyx", + "og2.tpl_base_desc2": "Le socle livré avec Nodyx : liens, invitations, mentions, inondation, majuscules, comptes neufs, texte corrompu, mots interdits. Doux au départ.", + "og2.tpl_rules_n": "{{n}} règles", + "og2.tpl_version": "Version {{v}}", + "og2.tpl_see": "Voir le détail", + "og2.tpl_youth_name": "Communauté jeunesse", + "og2.tpl_youth_tag": "Bienveillant", + "og2.tpl_youth_desc": "Pour un public jeune : insultes et contenu adulte refusés, liens des comptes de moins de 7 jours refusés, mentions en masse et capitales averties.", + "og2.tpl_youth_meta": "arrive en observation", + "og2.tpl_gaming_name": "Jeu compétitif", + "og2.tpl_gaming_tag": "Réactif", + "og2.tpl_gaming_desc": "Échanges rapides : inondation stricte avec sourdine de 10 minutes après 3 avertissements, invitations vers d'autres serveurs refusées, capitales tolérées.", + "og2.tpl_gaming_meta": "anti-inondation fort", + "og2.tpl_help_name": "Entraide technique", + "og2.tpl_help_tag": "Forum", + "og2.tpl_help_desc": "Pour l'entraide : liens de documentation libres (jusqu'à 8 par message), raccourcisseurs de liens signalés, mentions en masse averties.", + "og2.tpl_help_meta": "liens libres", + "og2.tpl_creative_name": "Créatifs et artistes", + "og2.tpl_creative_tag": "Ouvert", + "og2.tpl_creative_desc": "Portfolios et partages d'œuvres : liens libres (jusqu'à 12 par message), capitales tolérées, mots d'arnaque à la revente signalés à l'équipe.", + "og2.tpl_creative_meta": "partage libre", + "og2.tpl_strict_name": "Stricte et officielle", + "og2.tpl_strict_tag": "Ferme", + "og2.tpl_strict_desc": "Communauté de marque ou d'institution : liens en rafale, invitations, texte corrompu et liens des comptes neufs refusés, mentions et capitales averties, sourdine de 24 h après 2 avertissements.", + "og2.tpl_strict_meta": "tout arrive en observation", + "og2.tpl_note": "Les règles fermes d'un modèle arrivent en observation : vous jugez leurs prises avant qu'elles agissent. Vos règles personnalisées ne sont jamais touchées.", + "og2.tpl_changes": "{{name}} : ce qui change", + "og2.tpl_base_help": "Les huit règles du socle. Une règle retirée se réinstalle d'un clic ; une règle que vous avez modifiée reste telle quelle.", + "og2.tpl_preview_help": "Aperçu calculé par le serveur avant d'appliquer. Rien n'est supprimé, et vos règles personnalisées ne sont pas touchées.", + "og2.tpl_preview_err": "L'aperçu n'a pas pu être calculé. Réessayez.", + "og2.tpl_apply": "Appliquer le modèle ({{n}})", + "og2.tpl_nothing": "Rien à changer", + "og2.tpl_state_add": "Ajoutée", + "og2.tpl_state_update": "Modifiée", + "og2.tpl_state_unchanged": "Inchangée", + "og2.tpl_state_kept": "Gardée (personnalisée)", + "og2.tpl_state_refused": "Refusée", + "og2.tpl_state_missing": "Retirée", + "og2.tpl_state_installed": "Installée", + "og2.tpl_state_customized": "Personnalisée", + "og2.side_on": "Actif · {{n}} règle(s)", + "og2.side_off": "Éteint · {{n}} règle(s) prête(s)", + "og2.tile_templates": "Six profils selon votre communauté.", + "og2.tile_templates_go": "Choisir", + "og2.list_more": " et {{n}} autre(s)", + "og2.st_precision": "Justesse", + "og2.st_precision_sub": "{{u}} faux positif(s) annulé(s) sur {{n}}", + "og2.st_precision_none": "Pas encore d'action cette semaine" } diff --git a/nodyx-frontend/src/lib/octoguard/rules.ts b/nodyx-frontend/src/lib/octoguard/rules.ts index ad553c37..ed262d6f 100644 --- a/nodyx-frontend/src/lib/octoguard/rules.ts +++ b/nodyx-frontend/src/lib/octoguard/rules.ts @@ -114,7 +114,7 @@ export function describe(r: Rule, t: T): string { case 'zalgo': return t('og2.desc_zalgo', { n: p.max_combining ?? 3 }) case 'link_domain': { const all: string[] = p.domains ?? [] - const list = all.slice(0, 4).join(', ') + (all.length > 4 ? ', …' : '') + const list = all.slice(0, 4).join(', ') + (all.length > 4 ? t('og2.list_more', { n: all.length - 4 }) : '') return t(p.mode === 'whitelist' ? 'og2.desc_domains_allow' : 'og2.desc_domains_block', { list }) } default: return t('og2.desc_regex') diff --git a/nodyx-frontend/src/lib/octoguard/templates.json b/nodyx-frontend/src/lib/octoguard/templates.json new file mode 100644 index 00000000..817f336c --- /dev/null +++ b/nodyx-frontend/src/lib/octoguard/templates.json @@ -0,0 +1,562 @@ +{ + "_why": "Les modèles de communauté de la page Modèles. Données, pas interface. Un modèle ajuste les règles du profil de base (même preset_key) et ajoute ses propres règles ; il n'écrase jamais la liste de mots de l'admin. Les règles fermes arrivent en observation (dry_run).", + "templates": [ + { + "id": "youth", + "tone": "report", + "art": "youth", + "rules": [ + { + "name": { + "fr": "Spam de liens", + "en": "Link spam" + }, + "type": "link_spam", + "params": { + "max_links": 2 + }, + "action": "report_only", + "scopes": [ + "chat", + "forum" + ], + "dry_run": false, + "enabled": true, + "preset_key": "base.link_spam" + }, + { + "name": { + "fr": "Liens des comptes tout neufs", + "en": "Links from brand-new accounts" + }, + "type": "link_spam", + "params": { + "max_links": 0 + }, + "action": "delete", + "scopes": [ + "chat", + "forum", + "profile" + ], + "dry_run": true, + "enabled": true, + "preset_key": "base.new_account_links", + "conditions": { + "account_max_age_days": 7 + } + }, + { + "name": { + "fr": "Mentions en masse", + "en": "Mass mentions" + }, + "type": "mention_spam", + "params": { + "max_mentions": 4 + }, + "action": "warn", + "scopes": [ + "chat", + "forum" + ], + "dry_run": true, + "enabled": true, + "preset_key": "base.mention_spam", + "escalation": { + "warns_threshold": 3, + "window_days": 7, + "action": "mute", + "duration": { + "value": 1, + "unit": "h" + } + } + }, + { + "name": { + "fr": "Majuscules", + "en": "All caps" + }, + "type": "caps", + "params": { + "min_length": 15, + "threshold_percent": 70 + }, + "action": "warn", + "scopes": [ + "chat", + "forum" + ], + "dry_run": true, + "enabled": true, + "preset_key": "base.caps" + }, + { + "name": { + "fr": "Mots interdits : lot jeunesse", + "en": "Banned words: youth set" + }, + "type": "keywords", + "params": { + "words_by_lang": { + "fr": [ + "connard", + "connasse", + "enculé", + "salope", + "pute", + "bâtard", + "abruti", + "bouffon", + "pétasse", + "fdp", + "ntm", + "enfoiré*", + "nudes", + "sexe", + "baise", + "*porno*", + "*hentai*", + "*onlyfans*" + ], + "en": [ + "asshole", + "bitch", + "bastard", + "dickhead", + "prick", + "twat", + "wanker", + "moron", + "stfu", + "fuck*", + "nudes", + "sex", + "*porn*", + "*hentai*", + "*onlyfans*" + ] + }, + "exceptions": [] + }, + "action": "delete", + "scopes": [ + "chat", + "forum", + "profile", + "poll", + "wiki" + ], + "dry_run": true, + "enabled": true, + "preset_key": null + } + ] + }, + { + "id": "gaming", + "tone": "warn", + "art": "gaming", + "rules": [ + { + "name": { + "fr": "Inondation", + "en": "Flooding" + }, + "type": "flood", + "params": { + "max_repeats": 3, + "repeat_window_s": 30, + "max_messages": 6, + "burst_window_s": 8 + }, + "action": "warn", + "scopes": [ + "chat" + ], + "dry_run": true, + "enabled": true, + "preset_key": "base.flood", + "escalation": { + "warns_threshold": 3, + "window_days": 1, + "action": "mute", + "duration": { + "value": 10, + "unit": "m" + } + } + }, + { + "name": { + "fr": "Invitations vers d’autres serveurs", + "en": "Invites to other servers" + }, + "type": "link_domain", + "params": { + "mode": "blacklist", + "domains": [ + "discord.gg", + "discord.com/invite", + "discordapp.com/invite", + "t.me", + "chat.whatsapp.com" + ] + }, + "action": "delete", + "scopes": [ + "chat", + "forum", + "profile" + ], + "dry_run": true, + "enabled": true, + "preset_key": "base.invites" + }, + { + "name": { + "fr": "Mentions en masse", + "en": "Mass mentions" + }, + "type": "mention_spam", + "params": { + "max_mentions": 6 + }, + "action": "warn", + "scopes": [ + "chat", + "forum" + ], + "dry_run": true, + "enabled": true, + "preset_key": "base.mention_spam" + }, + { + "name": { + "fr": "Majuscules", + "en": "All caps" + }, + "type": "caps", + "params": { + "min_length": 20, + "threshold_percent": 85 + }, + "action": "report_only", + "scopes": [ + "chat", + "forum" + ], + "dry_run": false, + "enabled": true, + "preset_key": "base.caps" + } + ] + }, + { + "id": "help", + "tone": "observe", + "art": "help", + "rules": [ + { + "name": { + "fr": "Spam de liens", + "en": "Link spam" + }, + "type": "link_spam", + "params": { + "max_links": 8 + }, + "action": "report_only", + "scopes": [ + "chat", + "forum" + ], + "dry_run": false, + "enabled": true, + "preset_key": "base.link_spam" + }, + { + "name": { + "fr": "Mentions en masse", + "en": "Mass mentions" + }, + "type": "mention_spam", + "params": { + "max_mentions": 4 + }, + "action": "warn", + "scopes": [ + "chat", + "forum" + ], + "dry_run": true, + "enabled": true, + "preset_key": "base.mention_spam" + }, + { + "name": { + "fr": "Majuscules", + "en": "All caps" + }, + "type": "caps", + "params": { + "min_length": 20, + "threshold_percent": 80 + }, + "action": "report_only", + "scopes": [ + "chat", + "forum" + ], + "dry_run": false, + "enabled": true, + "preset_key": "base.caps" + }, + { + "name": { + "fr": "Raccourcisseurs de liens", + "en": "Link shorteners" + }, + "type": "link_domain", + "params": { + "mode": "blacklist", + "domains": [ + "bit.ly", + "tinyurl.com", + "t.co", + "is.gd", + "cutt.ly", + "shorturl.at" + ] + }, + "action": "report_only", + "scopes": [ + "chat", + "forum" + ], + "dry_run": false, + "enabled": true, + "preset_key": null + } + ] + }, + { + "id": "creative", + "tone": "hold", + "art": "creative", + "rules": [ + { + "name": { + "fr": "Spam de liens", + "en": "Link spam" + }, + "type": "link_spam", + "params": { + "max_links": 12 + }, + "action": "report_only", + "scopes": [ + "chat", + "forum" + ], + "dry_run": false, + "enabled": true, + "preset_key": "base.link_spam" + }, + { + "name": { + "fr": "Majuscules", + "en": "All caps" + }, + "type": "caps", + "params": { + "min_length": 25, + "threshold_percent": 90 + }, + "action": "report_only", + "scopes": [ + "chat", + "forum" + ], + "dry_run": true, + "enabled": true, + "preset_key": "base.caps" + }, + { + "name": { + "fr": "Arnaques à la revente", + "en": "Resale scams" + }, + "type": "keywords", + "params": { + "words_by_lang": { + "fr": [ + "*arnaque*", + "*escroc*", + "*giveaway*", + "*airdrop*" + ], + "en": [ + "*scam*", + "*scammer*", + "*giveaway*", + "*airdrop*" + ] + }, + "exceptions": [] + }, + "action": "report_only", + "scopes": [ + "chat", + "forum", + "profile" + ], + "dry_run": false, + "enabled": true, + "preset_key": null + } + ] + }, + { + "id": "strict", + "tone": "delete", + "art": "strict", + "rules": [ + { + "name": { + "fr": "Spam de liens", + "en": "Link spam" + }, + "type": "link_spam", + "params": { + "max_links": 2 + }, + "action": "delete", + "scopes": [ + "chat", + "forum" + ], + "dry_run": true, + "enabled": true, + "preset_key": "base.link_spam" + }, + { + "name": { + "fr": "Liens des comptes tout neufs", + "en": "Links from brand-new accounts" + }, + "type": "link_spam", + "params": { + "max_links": 0 + }, + "action": "delete", + "scopes": [ + "chat", + "forum", + "profile" + ], + "dry_run": true, + "enabled": true, + "preset_key": "base.new_account_links", + "conditions": { + "account_max_age_days": 7 + } + }, + { + "name": { + "fr": "Mentions en masse", + "en": "Mass mentions" + }, + "type": "mention_spam", + "params": { + "max_mentions": 3 + }, + "action": "warn", + "scopes": [ + "chat", + "forum" + ], + "dry_run": true, + "enabled": true, + "preset_key": "base.mention_spam", + "escalation": { + "warns_threshold": 2, + "window_days": 7, + "action": "mute", + "duration": { + "value": 24, + "unit": "h" + } + } + }, + { + "name": { + "fr": "Majuscules", + "en": "All caps" + }, + "type": "caps", + "params": { + "min_length": 12, + "threshold_percent": 60 + }, + "action": "warn", + "scopes": [ + "chat", + "forum" + ], + "dry_run": true, + "enabled": true, + "preset_key": "base.caps" + }, + { + "name": { + "fr": "Texte corrompu", + "en": "Corrupted text" + }, + "type": "zalgo", + "params": { + "max_combining": 2 + }, + "action": "delete", + "scopes": [ + "chat", + "forum", + "profile", + "poll", + "wiki" + ], + "dry_run": true, + "enabled": true, + "preset_key": "base.zalgo" + }, + { + "name": { + "fr": "Invitations vers d’autres serveurs", + "en": "Invites to other servers" + }, + "type": "link_domain", + "params": { + "mode": "blacklist", + "domains": [ + "discord.gg", + "discord.com/invite", + "discordapp.com/invite", + "t.me", + "chat.whatsapp.com" + ] + }, + "action": "delete", + "scopes": [ + "chat", + "forum", + "profile" + ], + "dry_run": true, + "enabled": true, + "preset_key": "base.invites" + } + ] + } + ] +} \ No newline at end of file diff --git a/nodyx-frontend/src/lib/octoguard/templates.ts b/nodyx-frontend/src/lib/octoguard/templates.ts new file mode 100644 index 00000000..5ff874fb --- /dev/null +++ b/nodyx-frontend/src/lib/octoguard/templates.ts @@ -0,0 +1,33 @@ +// Les modèles de communauté (page Modèles) : des profils OctoGuard complets, +// appliqués par le cœur (POST /profile/preview puis /profile/import). +import DATA from './templates.json' + +export type TemplateRule = { + name: { fr: string; en: string } + type: string + params: Record + action: string + scopes: string[] + dry_run: boolean + enabled: boolean + preset_key: string | null + escalation?: Record + conditions?: Record +} +export type Template = { id: string; tone: string; art: string; rules: TemplateRule[] } +export const TEMPLATES = DATA.templates as Template[] + +const pick = (lang: string) => (String(lang).startsWith('fr') ? 'fr' : 'en') as 'fr' | 'en' + +/** Une règle telle que le cœur l'attend, dans la langue de l'instance. */ +export function resolveRule(r: TemplateRule, lang: string) { + const l = pick(lang) + const { words_by_lang, ...rest } = r.params as { words_by_lang?: Record } + const params = words_by_lang ? { ...rest, words: words_by_lang[l] ?? words_by_lang.en } : rest + return { ...r, name: r.name[l], params, immunized_role_types: ['owner', 'admin', 'moderator'], preset_key: r.preset_key ?? null } +} + +/** Le profil complet d'un modèle, au format d'échange d'OctoGuard. */ +export function buildProfile(t: Template, lang: string, title: string) { + return { format: 'nodyx-octoguard-profile' as const, version: 1 as const, name: title.slice(0, 100), rules: t.rules.map((r) => resolveRule(r, lang)) } +} diff --git a/nodyx-frontend/src/routes/admin/+layout.svelte b/nodyx-frontend/src/routes/admin/+layout.svelte index d0185830..be9060ff 100644 --- a/nodyx-frontend/src/routes/admin/+layout.svelte +++ b/nodyx-frontend/src/routes/admin/+layout.svelte @@ -171,7 +171,12 @@
OctoGuard
-
{tFn('og2.side_sub')}
+ {#if (page.data as any).ogStatus} + {@const st = (page.data as any).ogStatus} +
{tFn(st.enabled ? 'og2.side_on' : 'og2.side_off', { n: st.active })}
+ {:else} +
{tFn('og2.side_sub')}
+ {/if}
+ + + + {#if exportErr}{/if} + {#if data.failed}{/if} -
-
-
-

{tFn('og2.tpl_base_title')}

-

{tFn('og2.tpl_base_desc', { v: data.base?.version ?? 1 })}

+
+ +
+
+
+
{tFn('og2.tpl_base_name')}{installed === presets.length ? tFn('og2.tpl_installed') : tFn('og2.tpl_installed_count', { n: installed, total: presets.length })}
+

{tFn('og2.tpl_base_desc2')}

+
{tFn('og2.tpl_rules_n', { n: presets.length })} · {tFn('og2.tpl_version', { v: data.base?.version ?? 1 })}
- {tFn('og2.tpl_installed_count', { n: presets.filter((p) => p.installed).length, total: presets.length })} -
- {#if data.failed} - - {:else} -
- {#each presets as p (p.key)} -
- -
-
{p.name[lang]}
-
{describe(asRule(p), tFn)}
-
- {#if !p.installed} - - {:else if p.customized} - {tFn('og2.customized')} - {:else} - {tFn('og2.tpl_installed')} - {/if} -
- {/each} -
- {#if presetErr}{/if} -

{tFn('og2.tpl_base_note')}

- {/if} -
- -
+

{tFn('og2.tpl_note')}

+ +{#if modal} +
+ +
+{/if} + +