new detectors and reactions

This commit is contained in:
Lou 2025-11-20 18:48:49 +01:00
parent 73b906fd9b
commit fe3fd9b1b6
11 changed files with 266 additions and 10 deletions

3
.gitignore vendored
View file

@ -1 +1,2 @@
/node_modules
/node_modules
bun.lock

View file

@ -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
- 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, ...)

View file

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

View file

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

View file

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

27
detectors/feurDetector.js Normal file
View file

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

17
detectors/index.js Normal file
View file

@ -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;

View file

@ -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;

View file

@ -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(`[<prefix>(!quoi){0,2} (#Verb|faire)? #Preposition?] quoi [<suffix2prefix>#Verb]? [<suffix>(#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();
}

View file

@ -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 [<suffix>!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(' ,', ',');
}
}

View file

@ -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"
}
}