From fe3fd9b1b61cdb6bdcd74b6c3ca2a791f20bb4cc Mon Sep 17 00:00:00 2001 From: TRUkae Date: Thu, 20 Nov 2025 18:48:49 +0100 Subject: [PATCH] new detectors and reactions --- .gitignore | 3 +- README.md | 6 +- detectors/basicDetector.js | 27 ++++++++ detectors/coubehDetector.js | 27 ++++++++ detectors/detector.js | 9 +-- detectors/feurDetector.js | 27 ++++++++ detectors/index.js | 17 +++++ detectors/pingDetector.js | 1 + detectors/suffixDetector.js | 51 +++++++++++++++ detectors/withPronounDetector.js | 104 +++++++++++++++++++++++++++++++ package.json | 4 +- 11 files changed, 266 insertions(+), 10 deletions(-) create mode 100644 detectors/basicDetector.js create mode 100644 detectors/coubehDetector.js create mode 100644 detectors/feurDetector.js create mode 100644 detectors/index.js create mode 100644 detectors/suffixDetector.js create mode 100644 detectors/withPronounDetector.js diff --git a/.gitignore b/.gitignore index 30bc162..c906d1e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/node_modules \ No newline at end of file +/node_modules +bun.lock \ No newline at end of file diff --git a/README.md b/README.md index 28ec762..95130ad 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ réponses troll et fun facts # dépendances bun add fr-compromise conjugation-fr discord.js + # version -0.1.1 : initialisation et création du code -0.2.1 : ajout de modules de détection \ No newline at end of file +- 0.1.1 : initialisation et création du code +- 0.2.1 : ajout de modules de détection +- 0.2.2 : rajout de nouveaux comportement (détection d'un feur par quelqu'un d'autre, ...) \ No newline at end of file diff --git a/detectors/basicDetector.js b/detectors/basicDetector.js new file mode 100644 index 0000000..951429c --- /dev/null +++ b/detectors/basicDetector.js @@ -0,0 +1,27 @@ +import Detector from "./Detector.js"; +import { cleanMessageContent } from "../utils/strings.js"; + +//check the regex for "quoi" +// trigger if it's at the end of the sentence (tu fais quoi) +// trigger if it's followed by 1 to 3 words (c'est quoi ça) +// skip if in the middle of a long sentence (je sais pas quoi faire demain) + +const answers = [ + ...Array(10).fill('feur'), + '# feur', + '||quoicoubeh|| feur' +]; + +export default class BasicDetector extends Detector { + detect(message) { + return /(?:^|\b)quoi\b ?\??(\s\S+){0,3}$/i.test(cleanMessageContent(message)); + } + + // randomize answer + createSpecificReply(message) { + if (this.detect(message)) { + return Promise.resolve(answers[Math.floor(Math.random() * answers.length)]); + } + return Promise.resolve(null); + } +} \ No newline at end of file diff --git a/detectors/coubehDetector.js b/detectors/coubehDetector.js new file mode 100644 index 0000000..1f0bccd --- /dev/null +++ b/detectors/coubehDetector.js @@ -0,0 +1,27 @@ +import Detector from "./Detector.js"; +import { cleanMessageContent } from "../utils/strings.js"; + +const answers = [ + 'Motif de ban ça', + "Non mais t'es pas bien toi ?", + 'https://tenor.com/view/kaamelott-leodagan-somme-sorte-provocateur-gif-18229391', + 'https://tenor.com/fr/view/movies-titanic-quotes-its-been-years-84years-gif-14851580', + 'https://tenor.com/fr/view/au-bucher-shouting-gif-15611785', + 'https://tenor.com/fr/view/kaamelott-r%C3%A9purgateur-h%C3%A9r%C3%A9tique-au-bucher-gif-18206884' + +]; + +export default class QuoicoubehDetector extends Detector { + triggerName = 'quoicoubeh'; + + detect(message) { + return /(?:^|\b)(quoi)?coubeh(?:\b|$)/i.test(cleanMessageContent(message)); + } + + createSpecificReply(message) { + if (this.detect(message)) { + return Promise.resolve(answers[Math.floor(Math.random() * answers.length)]); + } + return Promise.resolve(null); + } +} \ No newline at end of file diff --git a/detectors/detector.js b/detectors/detector.js index b720dcd..e56f5e6 100644 --- a/detectors/detector.js +++ b/detectors/detector.js @@ -16,13 +16,10 @@ export function createTriggersChecklist() { }; } -/** - * Base class for all detectors - * Using a Chain of Responsibility pattern - */ +// Base class for all detectors, using a Chain of Responsibility pattern export default class Detector { nextDetector = null; - triggerName = 'quoi'; // Valeur par défaut + triggerName = 'quoi'; // default value /** * @param nextDetector The detector that will be checked if this one doesn't detect anything @@ -56,7 +53,7 @@ export default class Detector { const forcedRoleId = getSetting(message.guildId, 'forcedAnswerRoleId'); - // Si l'utilisateur a le rôle forcé, on ignore le pourcentage (100%) + // if user has a forced role, skip the percentage (100%) const threshold = (forcedRoleId != null && message.member?.roles.cache.has(forcedRoleId)) ? 100 : this.getChanceToReply(message); diff --git a/detectors/feurDetector.js b/detectors/feurDetector.js new file mode 100644 index 0000000..36edf2e --- /dev/null +++ b/detectors/feurDetector.js @@ -0,0 +1,27 @@ +import Detector from "./Detector.js"; +import { cleanMessageContent } from "../utils/strings.js"; + +const answers = [ + "Toi t'es un bon", + "ça j'aime, ça", + "bien joué !", + "aller !", + 'https://tenor.com/fr/view/deez-ha-got-heem-got-em-got-him-gif-4824899', + 'https://tenor.com/fr/view/kaamelott-joueur-voil%C3%A0-faites-plaisir-gif-18227872', + 'https://tenor.com/view/kaamelott-yvain-exp%C3%A9ience-champion-cest-lexp%C3%A9rience-qui-parle-gif-17313437' +]; + +export default class FeurDetector extends Detector { + triggerName = 'feur'; + + detect(message) { + return /(?:^|\b)feur(?:\b|$)/i.test(cleanMessageContent(message)); + } + + createSpecificReply(message) { + if (this.detect(message)) { + return Promise.resolve(answers[Math.floor(Math.random() * answers.length)]); + } + return Promise.resolve(null); + } +} \ No newline at end of file diff --git a/detectors/index.js b/detectors/index.js new file mode 100644 index 0000000..ce42b91 --- /dev/null +++ b/detectors/index.js @@ -0,0 +1,17 @@ +import basicDetector from "./basicDetector.js"; +import feurDetector from "./feurDetector.js"; +import pingDetector from "./pingDetector.js"; +import quoicoubehDetector from "./quoicoubehDetector.js"; +import suffixPrefixDetector from "./suffixPrefixDetector.js"; +import withPronounDetector from "./withPronounDetector.js"; // Nom aligné + +const firstDetector = new WithPronounDetector(); // Utilisation du bon nom + +firstDetector + .setNextDetector(new suffixPrefixDetector()) + .setNextDetector(new basicDetector()) + .setNextDetector(new quoicoubehDetector()) + .setNextDetector(new feurDetector()) + .setNextDetector(new pingDetector()); + +export default firstDetector; \ No newline at end of file diff --git a/detectors/pingDetector.js b/detectors/pingDetector.js index 5f2f4d1..ee31852 100644 --- a/detectors/pingDetector.js +++ b/detectors/pingDetector.js @@ -12,6 +12,7 @@ const answers = [ export default class PingDetector extends Detector { triggerName = 'mention'; +//prevent trigger if user is doing a troll on its own async detect(message) { if (/(^|\b)(feur|quoicoubeh)(\b|$)/i.test(cleanMessageContent(message))) { return false; diff --git a/detectors/suffixDetector.js b/detectors/suffixDetector.js new file mode 100644 index 0000000..4bff140 --- /dev/null +++ b/detectors/suffixDetector.js @@ -0,0 +1,51 @@ +import Detector from "./Detector.js"; +import compromise from 'fr-compromise'; +import { cleanMessageContent } from "../utils/strings.js"; + +const suffixes = [ + ', mon gars', + ', mec', + ", je crois", + ...Array(10).fill('') +]; + +export default class SuffixPrefixDetector extends Detector { + async createSpecificReply(message) { + const reference = await message.fetchReference().catch(() => null); + // check if it's a reply to the bot + const isSelfTarget = (reference && reference.author.id === message.client.user?.id) ?? false; + + const compromiseMatch = compromise(cleanMessageContent(message).toLowerCase()).match(`[(!quoi){0,2} (#Verb|faire)? #Preposition?] quoi [#Verb]? [(#Determiner !quoi|!quoi)?]?$`); + + if (compromiseMatch == null || compromiseMatch.length === 0) { + return null; + } + + return this.conjugateResponse(compromiseMatch, isSelfTarget); + } + + conjugateResponse(compromiseMatch, isSelfTarget = false) { + const parsed = compromiseMatch.json(); + const result = parsed[0]; + + let replyPrefix = trimPrefix(compromiseMatch.groups('prefix')?.text().trim() ?? ''); + const numberOfWordsInPrefix = replyPrefix.split(' ').length; + if (numberOfWordsInPrefix > 7) { + return null; // Too long, pass to next detector + } + const replysuffixToPrefix = compromiseMatch.groups('suffix2prefix')?.text().trim() ?? ''; + const replySuffix = compromiseMatch.groups('suffix')?.text().trim() ?? ''; + + if (result == null) { + return ''; + } + + return (`${replyPrefix} ${replysuffixToPrefix} feur ${replySuffix.length > 0 ? ` ${replySuffix}` : ''}${suffixes[Math.floor(Math.random() * suffixes.length)]}`).trim().replaceAll(' ', ' ').replace(' ,', ','); + } +} + +function trimPrefix(prefix) { + return (prefix + .split(/(?=(?:ç|c)a)/).at(-1) ?? '') + .trim(); +} \ No newline at end of file diff --git a/detectors/withPronounDetector.js b/detectors/withPronounDetector.js new file mode 100644 index 0000000..be54775 --- /dev/null +++ b/detectors/withPronounDetector.js @@ -0,0 +1,104 @@ +import Detector from "./Detector.js"; +import compromise from 'fr-compromise'; +import conj from 'conjugation-fr'; +import { toInfinitive } from "../data.js"; // check path ?? +import { cleanMessageContent } from "../utils/strings.js"; + +const suffixes = [ + ', mon gars', + ', mec', + ", je crois", + '', + '', + '' +]; + +// check the pronoun of the question to answer properly +// exemple : tu fais quoi ? je fais feur + +export default class withPronounDetector extends Detector { + async createSpecificReply(message) { + const reference = await message.fetchReference().catch(() => null); + const isSelfTarget = (reference && reference.author.id === message.client.user?.id) ?? false; + + const compromiseMatch = compromise(cleanMessageContent(message).toLowerCase()).match('(#Pronoun|ça|ca|tu) (#Verb|fait) #Infinitive? quoi [!Verb{0,3}]$'); + + if (compromiseMatch == null || compromiseMatch.length === 0) { + return null; + } + + return this.conjugateResponse(compromiseMatch, isSelfTarget); + } + + conjugateResponse(compromiseMatch, isSelfTarget = false) { + const parsed = compromiseMatch.json(); + const result = parsed[0]; + + const replySuffix = compromiseMatch.groups('suffix')?.text().trim() ?? ''; + + if (result == null) { + return ''; + } + + // warning : result.terms[1] can't exist if the compromise structure change + const verbTerm = result.terms[1]; + if (!verbTerm) return ''; + + const verbInfinitive = toInfinitive[verbTerm.text]; + if (!verbInfinitive) return ''; // safety check if the verb is in the list + + let conjugated = ''; + const tenseTag = verbTerm.tags.find(t => t.endsWith('Tense')) ?? 'Present'; + + const getConjugation = (pronoun) => { + const tenseData = conj.findTense(verbInfinitive, tenseTag); + return tenseData.find(c => c.pronoun === pronoun)?.verb; + }; + + switch (result.terms[0].text.toLowerCase()) { + case 'je': + case "j'": + conjugated = `tu ${getConjugation('tu')}`; + break; + case 'tu': + case "t'": + if (isSelfTarget) { + conjugated = `je ${getConjugation('je')}`; + } else { + conjugated = `il ${getConjugation('il')}`; + } + break; + case 'il': + conjugated = `il ${getConjugation('il')}`; + break; + case 'elle': + conjugated = `elle ${getConjugation('il')}`; // 'elle' utilise la conjugaison 'il' dans cette lib souvent + break; + case 'on': + conjugated = `on ${getConjugation('il')}`; + break; + case 'ça': + conjugated = `ça ${getConjugation('il')}`; + break; + case 'nous': + conjugated = `nous ${getConjugation('nous')}`; + break; + case 'vous': + conjugated = `on ${getConjugation('il')}`; + break; + case 'ils': + conjugated = `ils ${getConjugation('ils')}`; + break; + case 'elles': + conjugated = `elles ${getConjugation('ils')}`; + break; + default: + conjugated = ''; + break; + } + + const infinitiveVerb = result.terms.find(t => t.tags.includes('Infinitive'))?.text ?? ''; + + return (`${conjugated} ${infinitiveVerb} feur ${replySuffix}${suffixes[Math.floor(Math.random() * suffixes.length)]}`).trim().replaceAll(' ', ' ').replace(' ,', ','); + } +} \ No newline at end of file diff --git a/package.json b/package.json index a2b9532..8f324ae 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { - "discord.js": "^14.25.0" + "conjugation-fr": "^0.3.4", + "discord.js": "^14.25.0", + "fr-compromise": "^0.2.8" } }