├── favicon.ico ├── .gitlab-ci.yml ├── combine-scripts.py ├── .vscode └── tasks.json ├── LICENSE ├── index.html ├── styles.min.css ├── styles.css ├── README.md ├── script.min.js ├── chatdata.js ├── chatbot.js └── badWords-fr.js /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XenocodeRCE/chatMD/main/favicon.ico -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | pages: 2 | script: 3 | - mkdir .public 4 | - cp -r * .public 5 | - mv .public public 6 | - find public -type f -regex '.*\.\(htm\|html\|txt\|text\|js\|css\)$' -exec gzip -f -k {} \; 7 | - find public -type f -regex '.*\.\(htm\|html\|txt\|text\|js\|css\)$' -exec brotli -f -k {} \; 8 | artifacts: 9 | paths: 10 | - public 11 | only: 12 | - main -------------------------------------------------------------------------------- /combine-scripts.py: -------------------------------------------------------------------------------- 1 | # Chemin des fichiers source 2 | chatbot_js_file = "chatbot.js" 3 | chatdata_js_file = "chatdata.js" 4 | 5 | # Chemin du fichier de sortie 6 | output_file = "script.min.js" 7 | 8 | # Lire le contenu des fichiers source 9 | with open(chatbot_js_file, "r") as chatbot_file, open(chatdata_js_file, "r") as chatdata_file: 10 | chatbot_content = chatbot_file.read() 11 | chatdata_content = chatdata_file.read() 12 | 13 | # Concaténer le contenu des fichiers source 14 | combined_content = chatbot_content + chatdata_content 15 | 16 | # Écrire le contenu minifié dans le fichier de sortie 17 | with open(output_file, "w") as output: 18 | output.write(combined_content) 19 | 20 | print(f"Le fichier {output_file} a été créé avec succès.") -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Minify js & css", 8 | "dependsOn": ["Combine Scripts", "Minify CSS", "Minify JS"], 9 | "group": { 10 | "kind": "build", 11 | "isDefault": true 12 | } 13 | }, 14 | { 15 | "label": "Combine Scripts", 16 | "type": "shell", 17 | "command": "python combine-scripts.py" 18 | }, 19 | { 20 | "label": "Minify CSS", 21 | "type": "shell", 22 | "command": "minifyall ${workspaceFolder}/styles.css -s .min" 23 | }, 24 | { 25 | "label": "Minify JS", 26 | "type": "shell", 27 | "command": "node-minify --compressor terser --input ${workspaceFolder}/script.min.js --output 'script.min.js'" 28 | } 29 | 30 | ] 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Eyssette 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ChatMD 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

Chatbot

17 |
18 |
19 | 20 |
21 |
22 |
23 |
31 |
32 | 33 |
34 |
35 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /styles.min.css: -------------------------------------------------------------------------------- 1 | h1,#controls{text-align:center;margin:1em;}main{width:80%;max-width:800px;margin:auto;padding:10px;border-radius:15px;border-color:rgb(228,228,228);background:linear-gradient(90deg,rgb(239,242,247) 0%,7.60286%,rgb(237,240,249) 15.2057%,20.7513%,rgb(235,239,248) 26.297%,27.6386%,rgb(235,239,248) 28.9803%,38.2826%,rgb(231,237,249) 47.585%,48.1216%,rgb(230,236,250) 48.6583%,53.1306%,rgb(228,236,249) 57.6029%,61.5385%,rgb(227,234,250) 65.4741%,68.7835%,rgb(222,234,250) 72.093%,75.7603%,rgb(219,230,248) 79.4275%,82.8265%,rgb(216,229,248) 86.2254%,87.8354%,rgb(213,228,249) 89.4454%,91.8605%,rgb(210,226,249) 94.2755%,95.4383%,rgb(209,225,248) 96.6011%,98.3005%,rgb(208,224,247) 100%);}#chat{min-height:350px;font-family:"Inter",sans-serif,Avenir,Helvetica,Arial;font-size:.9rem;position:relative;overflow:hidden;padding:10px;border-radius:5px;font-size:1em;}#controls{margin-top:2em;}#input-container{background-color:rgb(255,255,255);margin:auto;display:flex;border:1px solid rgba(0,0,0,.1);border-radius:5px;margin-top:.8em;margin-bottom:.8em;box-shadow:rgba(149,157,165,.2) 0 1px 12px;overflow-y:auto;max-height:200px;position:relative;}#user-input{outline:none;overflow-wrap:break-word;line-break:auto;width:100%;text-align:left;padding:5px;height:1em;}.placeholder{color:gray;}[contenteditable="true"]:empty:before{content:attr(placeholder);color:grey;font-style:italic;}.message{margin-top:.5em;margin-bottom:.5em;clear:both;margin-top:10px;margin-bottom:10px;padding:10px;width:80%;border-radius:10px;}.user-message{box-shadow:rgba(0,0,0,.12) 0 .3px .9px,rgba(0,0,0,.16) 0 1.6px 3.6px;color:rgb(255,255,255);background-color:rgb(0,132,255);float:right;}.user-message p{margin:0 5px;}.bot-message{float:left;margin-top:10px;margin-bottom:10px;}.bot-message>:first-child:before{content:"🗣️";margin-right:10px;font-size:2em;}footer{width:60%;max-width:600px;margin:auto;font-size:.9em;text-align:center;margin-top:3em;border-top:1px solid #bbb;padding-top:1em;}footer a{text-decoration:none;}footer a:hover{text-decoration:underline;}#chat img{display:block;margin:1em;}#chat li,#chat li p{margin-top:.25em;margin-bottom:.25em;}@media screen and (max-width:500px){body{font-size:1.05em!important}main{width:initial!important;max-width:initial!important}#chat{padding:5px!important}.message{padding:5px!important}.bot-message{width:90%!important}ul{padding-left:30px!important}ul ul{padding-left:25px!important}} -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | h1, 2 | #controls { 3 | text-align: center; 4 | margin: 1em; 5 | } 6 | 7 | main { 8 | width: 80%; 9 | max-width: 800px; 10 | margin: auto; 11 | padding: 10px; 12 | border-radius: 15px; 13 | border-color: rgb(228, 228, 228); 14 | background: linear-gradient( 15 | 90deg, 16 | rgb(239, 242, 247) 0%, 17 | 7.60286%, 18 | rgb(237, 240, 249) 15.2057%, 19 | 20.7513%, 20 | rgb(235, 239, 248) 26.297%, 21 | 27.6386%, 22 | rgb(235, 239, 248) 28.9803%, 23 | 38.2826%, 24 | rgb(231, 237, 249) 47.585%, 25 | 48.1216%, 26 | rgb(230, 236, 250) 48.6583%, 27 | 53.1306%, 28 | rgb(228, 236, 249) 57.6029%, 29 | 61.5385%, 30 | rgb(227, 234, 250) 65.4741%, 31 | 68.7835%, 32 | rgb(222, 234, 250) 72.093%, 33 | 75.7603%, 34 | rgb(219, 230, 248) 79.4275%, 35 | 82.8265%, 36 | rgb(216, 229, 248) 86.2254%, 37 | 87.8354%, 38 | rgb(213, 228, 249) 89.4454%, 39 | 91.8605%, 40 | rgb(210, 226, 249) 94.2755%, 41 | 95.4383%, 42 | rgb(209, 225, 248) 96.6011%, 43 | 98.3005%, 44 | rgb(208, 224, 247) 100% 45 | ); 46 | } 47 | 48 | #chat { 49 | min-height: 350px; 50 | font-family: "Inter", sans-serif, Avenir, Helvetica, Arial; 51 | font-size: 0.9rem; 52 | position: relative; 53 | overflow: hidden; 54 | padding: 10px; 55 | border-radius: 5px; 56 | font-size: 1em; 57 | } 58 | 59 | #controls { 60 | margin-top: 2em; 61 | } 62 | 63 | #input-container { 64 | background-color: rgb(255, 255, 255); 65 | margin: auto; 66 | display: flex; 67 | border: 1px solid rgba(0, 0, 0, 0.1); 68 | border-radius: 5px; 69 | margin-top: 0.8em; 70 | margin-bottom: 0.8em; 71 | box-shadow: rgba(149, 157, 165, 0.2) 0px 1px 12px; 72 | overflow-y: auto; 73 | max-height: 200px; 74 | position: relative; 75 | } 76 | 77 | #user-input { 78 | outline: none; 79 | overflow-wrap: break-word; 80 | line-break: auto; 81 | width: 100%; 82 | text-align: left; 83 | padding: 5px; 84 | height: 1em; 85 | } 86 | 87 | .placeholder { 88 | color: gray; 89 | } 90 | 91 | [contenteditable="true"]:empty:before { 92 | content: attr(placeholder); 93 | color: grey; 94 | font-style: italic; 95 | } 96 | 97 | .message { 98 | margin-top: 0.5em; 99 | margin-bottom: 0.5em; 100 | clear: both; 101 | margin-top: 10px; 102 | margin-bottom: 10px; 103 | padding: 10px; 104 | width: 80%; 105 | border-radius: 10px; 106 | } 107 | 108 | .user-message { 109 | box-shadow: rgba(0, 0, 0, 0.12) 0px 0.3px 0.9px, 110 | rgba(0, 0, 0, 0.16) 0px 1.6px 3.6px; 111 | color: rgb(255, 255, 255); 112 | background-color: rgb(0, 132, 255); 113 | float: right; 114 | } 115 | 116 | .user-message p { 117 | margin: 0px 5px; 118 | } 119 | 120 | .bot-message { 121 | float: left; 122 | margin-top: 10px; 123 | margin-bottom: 10px; 124 | } 125 | 126 | .bot-message > :first-child:before { 127 | content: "🗣️"; 128 | margin-right: 10px; 129 | font-size: 2em; 130 | } 131 | 132 | footer { 133 | width: 60%; 134 | max-width: 600px; 135 | margin: auto; 136 | font-size: 0.9em; 137 | text-align: center; 138 | margin-top: 3em; 139 | border-top: 1px solid #bbb; 140 | padding-top: 1em; 141 | } 142 | 143 | footer a { 144 | text-decoration: none; 145 | } 146 | footer a:hover { 147 | text-decoration: underline; 148 | } 149 | 150 | #chat img { 151 | display: block; 152 | margin: 1em; 153 | } 154 | 155 | #chat li, 156 | #chat li p { 157 | margin-top: 0.25em; 158 | margin-bottom: 0.25em; 159 | } 160 | 161 | @media screen and (max-width: 500px) { 162 | body { 163 | font-size: 1.05em !important; 164 | } 165 | main { 166 | width: initial !important; 167 | max-width: initial !important; 168 | } 169 | #chat { 170 | padding: 5px !important; 171 | } 172 | .message { 173 | padding: 5px !important; 174 | } 175 | .bot-message { 176 | width: 90% !important; 177 | } 178 | ul { 179 | padding-left: 30px !important; 180 | } 181 | ul ul { 182 | padding-left: 25px !important; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatMD 2 | 3 | ChatMD est un chatbot, que vous pouvez configurer par vous-même en Markdown : 4 | 5 | - Créez un fichier en [markdown]((https://www.markdowntutorial.com/fr/)) et mettez-le en ligne : sur [CodiMD](https://codimd.apps.education.fr/), ou sur une [forge](https://forge.aeif.fr/) 6 | - Respectez la syntaxe de ChatMD pour définir votre chatbot 7 | 8 | Votre chatbot est alors prêt et visible à l'adresse suivante : [https://eyssette.forge.aeif.fr/chatMD/#URL](https://eyssette.forge.aeif.fr/chatMD/#URL) (Mettez l'url de votre fichier à la place de URL) 9 | 10 | 11 | ## Exemples 12 | 13 | Voici un modèle que vous pouvez récupérer pour construire votre chatbot : [modèle à récupérer](https://codimd.apps.education.fr/mBGbHStJSVOSrlGfGb981A?both) 14 | 15 | Voici quelques exemple de chatbot créés avec ChatMD : 16 | 17 | - [Méthode de la dissertation en philosophie](https://eyssette.forge.aeif.fr/chatMD/#https://eyssette.forge.aeif.fr/chatbot/dissertation-philosophie.md) 18 | - [Utilisation d'un microscope](https://eyssette.forge.aeif.fr/chatMD/#https://codimd.apps.education.fr/xGNHIJSeTVCk6FHas-_71g) : un chatbot créé à partir du travail de Guillaume Berthelot et de Jérémy Navoizat ([voir la source](https://codimd.apps.education.fr/xGNHIJSeTVCk6FHas-_71g)) 19 | 20 | 21 | ## Syntaxe 22 | 23 | La syntaxe pour écrire un chatbot avec chatMD est la suivante : 24 | 25 | - On définit le titre du chatbot dans un titre de niveau 1 26 | - Le message initial est à mettre dans un bloc de citation après le titre du chatbot 27 | - Les titres de niveau 2 servent à identifier les réponses possibles du chatbot 28 | - Sous chaque titre de niveau 2 : 29 | - On indique avec une liste non ordonnée les mots clés ou expressions qui vont déclencher la réponse. On peut éventuellement s'en passer si on guide l'utilisateur avec un choix d'options (voir ci-dessous). 30 | - On écrit une réponse en Markdown. 31 | - [Optionnel] On indique avec une liste ordonnée les options possibles. Chaque élément de la liste doit être un lien en Mardown de la forme suivante : \`[intitulé de l'option](identifiant de l'option, qui doit correspondre à l'un des titres de niveau 2)\`. 32 | 33 | ## Options de configuration plus avancées 34 | 35 | On peut ajouter un en-tête yaml à son fichier Markdown. 36 | Par exemple : 37 | 38 | ```yaml 39 | clavier: false 40 | rechercheContenu: true 41 | gestionsGrosMots: true 42 | style: a{color:red} 43 | maths: true 44 | titresRéponses: ["### ", "#### "] 45 | ``` 46 | 47 | - `clavier: false` désactive le champ d'entrée clavier si on souhaite simplement guider l'utilisateur avec les options proposées en fin de chaque réponse. 48 | - `rechercheContenu: true` permet d'ajouter une recherche de comparaison de l'entrée de l'utilisateur avec le contenu de chaque réponse 49 | - `gestionGrosMots: true` permet de détecter les gros mots envoyés par l'utilisateur et de formuler une réponse adéquate si l'utilisateur en utilise 50 | - `style: a{color:red}` permet d'ajouter des styles CSS personnalisés. 51 | - `maths: true` permet d'écrire des formules mathématiques en Latex avec la syntaxe `$Latex$` ou `$$Latex$$` 52 | - `titresRéponses: "### "` permet de changer l'identifiant des réponses du chatbot si on veut pouvoir structurer les réponses du chatbot dans son document 53 | - `titresRéponses: ["### ", "#### "]` permet de changer les identifiants possibles des réponses du chatbot si on veut pouvoir structurer les réponses du chatbot dans son document 54 | 55 | ## Crédits 56 | 57 | Chat MD est un outil libre et gratuit sous licence MIT. 58 | 59 | Il utilise d'autres logiciels libres : 60 | - [js-yaml](https://github.com/nodeca/js-yaml) pour la gestion des en-têtes yaml 61 | - [typed.js](https://github.com/mattboldt/typed.js) pour l'effet "machine à écrire" 62 | - [showdown](https://github.com/showdownjs/showdown) pour la conversion du markdown en html 63 | - [leo-profanity](https://github.com/jojoee/leo-profanity) et [french-badwords-list](https://github.com/darwiin/french-badwords-list/) pour la gestion des gros mots -------------------------------------------------------------------------------- /script.min.js: -------------------------------------------------------------------------------- 1 | function createChatBot(e){const t=e.pop();let n=e.pop();document.getElementById("chatbot-name").textContent=t;const s=document.getElementById("chat"),o=document.getElementById("user-input"),r=document.getElementById("send-button");let a=null,i=Math.floor(Math.random()*defaultMessage.length),u=[];var l=new showdown.Converter({emoji:!0,parseImgDimensions:!0});function c(e,t){new Typed(t,{strings:[e],typeSpeed:1,startDelay:100,onBegin:()=>{o.focus()}})}function d(e,t){const n=document.createElement("div");n.classList.add("message"),n.classList.add(t?"user-message":"bot-message");let o=(r=e,l.makeHtml(r));var r;!0===yamlMaths?setTimeout((()=>{o=function(e){let t=(e=e.replace(/\$\$(.*?)\$\$/g,"\[$1\]").replace(/\$(.*?)\$/g,"\($1\)")).match(new RegExp(/\\[.*?\\]|\\(.*?\\)/g));if(t)for(let n of t){const t=!!n.includes("\[");let s=n.replace("\[","").replace("\]","");s=s.replace("\(","").replace("\)",""),stringWithLatex=katex.renderToString(s,{displayMode:t}),e=e.replace(n,stringWithLatex)}return e}(o),t?n.innerHTML=o:c(o,n),s.appendChild(n)}),100):(t?n.innerHTML=o:c(o,n),s.appendChild(n))}function p(e,t){if(0===e.length)return t.length;if(0===t.length)return e.length;const n=[];for(let e=0;e<=t.length;e++)n[e]=[e];for(let t=0;t<=e.length;t++)n[0][t]=t;for(let s=1;s<=t.length;s++)for(let o=1;o<=e.length;o++){const r=e[o-1]===t[s-1]?0:1;n[s][o]=Math.min(n[s-1][o]+1,n[s][o-1]+1,n[s-1][o-1]+r)}return n[t.length][e.length]}function m(e,t,n){const s=e.split(" ");for(const e of s){if(p(e,t)t[e]||e))}(s),s=s.split(/\s|'/).filter((e=>e.length>=5))||[];const o=[];for(const a of s){o.push({word:a,weight:5});const i=[0,0,0,0,.4,.6,.8],u=.2,l=10;function r(t,s){let o=i[s-1];o=0===t?o+u:o;const r=a.substring(t,t+s);return n&&e[n][0].toLowerCase().includes(r)&&(o+=l),{token:r,weight:o}}const c=a.length;if(c>=5)for(let d=0;d<=c-5;d++)o.push(r(d,5));if(c>=6)for(let p=0;p<=c-6;p++)o.push(r(p,6));if(c>=7)for(let m=0;m<=c-7;m++)o.push(r(m,7))}return o}(t,n),o={};for(const{token:e,weight:t}of s)e&&(o[e]=(o[e]||0)+t);return o}let v=[];if(yamlSearchInContent)for(let t=0;t'+o+"\n"}n+="",e+=n}else a=null;return e}r.addEventListener("click",(()=>{const t=o.innerText;""!==t.trim()&&(d(t,!0),setTimeout((()=>{!function(t){if(!0===yamldetectBadWords&&filterBadWords&&filterBadWords.check(t)){const e=Math.floor(Math.random()*badWordsMessage.length);return void d(badWordsMessage[e],!1)}let n,s,o,r=null,l=0,c=0,p=t.toLowerCase();if(a&&(s=a.map((e=>e[0].toLowerCase())),o=s.indexOf(p)),a&&o>-1)h(a[o][1]);else{for(let t=0;t4&&m(p,e,3)&&i++}0==a&&i>c&&(a++,c=i),a>l&&(r=o,l=a,n=t)}if(r&&l>.55){let t=Array.isArray(r)?r.join("\n\n"):r;t=y(t,e[n][3]),d(t,!1)}else{for(;u.includes(i);)i=Math.floor(Math.random()*defaultMessage.length);u.length>4&&u.shift(),u.push(i),d(defaultMessage[i],!1)}}}(t),window.scrollTo(0,document.body.scrollHeight)}),100),o.innerText="")})),o.addEventListener("keypress",(e=>{"Enter"===e.key&&(e.preventDefault(),r.click(),window.scrollTo(0,document.body.scrollHeight))})),o.focus(),o.addEventListener("focus",(function(){this.classList.remove("placeholder")})),o.addEventListener("blur",(function(){this.classList.add("placeholder")})),s.addEventListener("click",(function(e){const t=e.target;if("A"===t.tagName){const n=window.location.href,s=t.getAttribute("href");if(s.startsWith(n)&&window.open(s),s.startsWith("#")){e.preventDefault(),d(t.innerText,!0);h(s.substring(1)),window.scrollTo(0,document.body.scrollHeight)}}})),n=y(n[0].join("\n"),n[1]),d(n,!1),n=n.replace(/.*?\<\/span>/,"")}const defaultMessage=["Désolé, je ne comprends pas votre question.","Pardonnez-moi, mais je ne saisis pas votre demande.","Excusez-moi, je ne parviens pas à comprendre ce que vous demandez.","Je suis navré, mais je ne parviens pas à saisir votre question.","Malheureusement, je ne suis pas en mesure de comprendre votre question.","Je suis désolé, mais je ne saisis pas votre question.","Pardonnez-moi, mais je ne saisis pas le sens de votre question.","Je m'excuse, mais je ne parviens pas à saisir votre demande. Pouvez-vous reformuler votre question, s'il vous plaît ?","Je ne suis pas sûr de comprendre ce que vous demandez. Pouvez-vous expliquer davantage ?","Je ne peux pas répondre à votre question telle qu'elle est formulée. Pouvez-vous la poser différemment ?","Votre question ne semble pas correspondre à mes capacités actuelles. Pourriez-vous la reformuler autrement ?","Je n'ai malheureusement pas compris votre requête.","Je suis désolé, je ne suis pas capable de répondre.","Malheureusement, je ne peux pas répondre à votre question.","Malheureusement je n'arrive pas à comprendre votre requête.","Excusez-moi, je ne comprends pas votre requête.","Excusez-moi, je n'arrive pas à répondre à votre question.","Je ne parviens pas à répondre à votre requête. Veuillez m'excuser."],badWordsMessage=["Même si je ne suis qu'un chatbot, merci de vous adresser à moi avec un langage approprié","Je préférerais que nous restions courtois dans notre communication.","Les insultes ne sont pas nécessaires. Comment puis-je vous aider autrement ?","Essayons de garder une conversation respectueuse.","Je préfère une conversation respectueuse et productive.","Je vous encourage à reformuler votre question ou commentaire de manière respectueuse.","Les mots offensants ne sont pas nécessaires ici. Comment puis-je vous aider de manière constructive ?","Restons courtois dans nos échanges, s'il vous plaît.","Injures et grossièretés ne mènent nulle part. Comment puis-je vous assister ?","Je suis ouvert à la discussion, mais veuillez garder un langage respectueux.","Essayons de communiquer de manière civilisée !"];let md="\n---\ngestionGrosMots: true\n---\n# ChatMD\n\n> Bonjour, je suis ChatMD, un chatbot, que vous pouvez configurer par vous-même en Markdown :\n> \n> - Créez un fichier en Markdown et mettez-le en ligne : sur CodiMD, ou sur une forge\n> - Respectez la syntaxe de ChatMD pour définir votre chatbot\n> \n> Votre chatbot est alors prêt et visible à l'adresse suivante : [https://eyssette.forge.aeif.fr/chatMD/#URL](https://eyssette.forge.aeif.fr/chatMD/#URL) (Mettez l'url de votre fichier à la place de URL)\n> \n> 1. [Qu'est-ce que le Markdown ?](Markdown)\n> 2. [CodiMD, une forge : qu'est-ce que c'est ?](CodiMD et forge)\n> 3. [Quelle syntaxe faut-il respecter pour ChatMD ?](Syntaxe)\n> 4. [Tu peux me donner des exemples !](Exemples)\n> 5. [Quelles sont les options de configuration plus avancées ?](Options de configuration)\n> 6. [À quoi ça sert ?](À quoi ça sert ?)\n\n## Markdown\n- markdown\nLe Markdown est un format de balisage très léger qui permet d'écrire rapidement du texte formaté.\n\nPour découvrir le Markdown, vous pouvez suivre ce [tutoriel](https://www.markdowntutorial.com/fr/).\n\n## CodiMD et forge\n- codimd\n- codi\n- forge\n- en ligne\n\n[CodiMD](https://codimd.apps.education.fr/) est un outil pour écrire du Markdown en ligne et il est disponible avec vos identifiants académiques sur le [portail Apps Edu](https://portail.apps.education.fr/).\n\nUne forge est un outil plus complet qui permet d'héberger des fichiers texte et de les transformer en site web, en carte mentale, ou encore ici en chatbot ! ChatMD est présent sur la [Forge des Communs Numériques Éducatifs](https://forge.aeif.fr/) et vous pouvez aussi mettre vos fichiers sur cette forge.\n\n## Syntaxe\n- syntaxe\n- règles\n- comment\n\nLa syntaxe pour écrire un chatbot avec chatMD est la suivante, mais c'est peut-être plus simple de [voir des exemples](#Exemples) ou bien de [récupérer un modèle](https://codimd.apps.education.fr/mBGbHStJSVOSrlGfGb981A?both).\n\n- On définit le titre du chatbot dans un titre de niveau 1\n- Le message initial est à mettre dans un bloc de citation après le titre du chatbot\n- Les titres de niveau 2 servent à identifier les réponses possibles du chatbot\n- Sous chaque titre de niveau 2 : \n\t- On indique avec une liste non ordonnée les mots clés ou expressions qui vont déclencher la réponse. On peut éventuellement s'en passer si on guide l'utilisateur avec un choix d'options (voir ci-dessous).\n\t- On écrit une réponse en Markdown.\n\t- [Optionnel] On indique avec une liste ordonnée les options possibles. Chaque élément de la liste doit être un lien en Mardown de la forme suivante : `[intitulé de l'option](identifiant de l'option, qui doit correspondre à l'un des titres de niveau 2)`.\n\n1. [Voir aussi les options de configuration plus avancées](Options de configuration)\n\n## Options de configuration\n- yaml\n- en-tête\n- en-tête yaml\n- options\n- avancé\n\nOn peut ajouter un en-tête yaml à son fichier Markdown :\n\n- `clavier: false` désactive le champ d'entrée clavier si on souhaite simplement guider l'utilisateur avec les options proposées en fin de chaque réponse.\n- `rechercheContenu: true` permet d'ajouter une recherche de comparaison de l'entrée de l'utilisateur avec le contenu de chaque réponse\n- `style: a{color:red}` permet d'ajouter des styles CSS personnalisés.\n- `gestionGrosMots: true` permet de détecter les gros mots envoyés par l'utilisateur et de formuler une réponse adéquate si l'utilisateur en utilise\n- `maths: true` permet d'écrire des formules mathématiques en Latex avec la syntaxe `$Latex$` ou `$$Latex$$`\n- `titresRéponses: [\"### \", \"#### \"]` permet de changer les identifiants possibles des réponses du chatbot si on veut pouvoir structurer les réponses du chatbot dans son document\n\n## Exemples\n- exemple\n- donner un exemple\n- concret\n- concrètement\n- modèle\n- template\n\nVoici un modèle que vous pouvez récupérer pour construire votre chatbot : [modèle à récupérer](https://codimd.apps.education.fr/mBGbHStJSVOSrlGfGb981A?both)\n\nVoici quelques exemple de chatbot créés avec ChatMD : \n\n- [Méthode de la dissertation en philosophie](https://eyssette.forge.aeif.fr/chatMD/#https://eyssette.forge.aeif.fr/chatbot/dissertation-philosophie.md)\n- [Utilisation d'un microscope](https://eyssette.forge.aeif.fr/chatMD/#https://codimd.apps.education.fr/xGNHIJSeTVCk6FHas-_71g) : un chatbot créé à partir du travail de Guillaume Berthelot et de Jérémy Navoizat [voir la source](https://codimd.apps.education.fr/xGNHIJSeTVCk6FHas-_71g?both)\n\n## À quoi ça sert ?\n- à quoi ça sert ?\n- pourquoi\n- sert\n- intérêt\n- servir\n- objectif\n- but\n- en faire\n- tutoriel\n- histoire\n- méthod\n- révision\n- utilité\n- utile\n\nOn peut imaginer plusieurs usages de chatMD :\n- Tutoriel pour un outil informatique\n- Histoire dont vous êtes le héros\n- Guide méthodologique\n- Soutien pour la révision d'un cours\n- …\n\nOn peut faire travailler des élèves ensemble sur un CodiMD, ou bien travailler collaborativement entre collègues, en tant que prof ou dans le cadre d'une formation.\n\nSi vous avez trouvé des idées intéressantes, n'hésitez pas à les partager avec moi. Vous pouvez me contacter sur [Mastodon](https://scholar.social/@eyssette).\n\n## Merci\n- merci\n- remercier\n- remercie\n- félicitations\n- félicit\n- bravo\n- super\n- excellent\n- génial\n- wow\n- chouette\n- sympa\n\nMerci ! Si vous aimez ce travail, vous aimerez peut-être aussi les autres outils ou sites que je propose sur [mon site perso](https://eyssette.github.io).\n\n\n";const shortcuts=[["dissertation-philo","https://raw.githubusercontent.com/eyssette/chatbot/main/dissertation-philosophie.md"]];let chatData,filterBadWords,yamlStyle="",yamlUserInput=!0,yamlSearchInContent=!1,yamldetectBadWords=!1,yamlMaths=!1;function getMarkdownContent(){let e=window.location.hash.substring(1);""!==e?(e.startsWith("https://github.com")&&(e=e.replace("https://github.com","https://raw.githubusercontent.com"),e=e.replace("/blob/","/")),e.startsWith("https://codimd")&&-1===e.indexOf("download")&&(e=e.replace("?edit","").replace("?both","").replace("?view","")+"/download"),shortcut=shortcuts.find((t=>t[0]==e)),shortcut&&(e=shortcut[1]),fetch(e).then((e=>e.text())).then((e=>{md=e,chatData=parseMarkdown(md),createChatBot(chatData)})).catch((e=>console.error(e)))):createChatBot(parseMarkdown(md))}function loadScript(e){return new Promise(((t,n)=>{const s=document.createElement("script");s.src=e,s.onload=t,s.onerror=n,document.head.appendChild(s)}))}function loadCSS(e){return new Promise(((t,n)=>{const s=document.createElement("link");s.href=e,s.rel="stylesheet",s.onload=t,s.onerror=n,document.head.appendChild(s)}))}function startsWithAnyOf(e,t){for(const n of t)if(e.startsWith(n))return n}function parseMarkdown(e){let t=["## "];if(e.split("---").length>2)try{yamlData=jsyaml.load(e.split("---")[1]);for(const e in yamlData){if("maths"==e&&(yamlMaths=yamlData[e],!0===yamlMaths&&Promise.all([loadScript("https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"),loadCSS("https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css")])),"titresRéponses"!=e&&"responsesTitles"!=e||(t=yamlData[e],"string"==typeof t&&(t=[t])),"style"==e){yamlStyle=yamlData[e];const t=document.createElement("style");t.innerHTML=yamlStyle,document.body.appendChild(t)}if(("userInput"==e||"clavier"==e||"keyboard"==e)&&(yamlUserInput=yamlData[e],!1===yamlUserInput)){document.getElementById("controls").style.display="none"}"searchInContent"!=e&&"rechercheContenu"!=e||(yamlSearchInContent=yamlData[e]),"gestionGrosMots"!=e&&"detectBadWords"!=e||(yamldetectBadWords=yamlData[e],!0===yamldetectBadWords&&Promise.all([loadScript("https://cdn.jsdelivr.net/npm/leo-profanity"),loadScript("badWords-fr.js")]).then((()=>{filterBadWords=LeoProfanity,filterBadWords.add(badWordsFR)})).catch((e=>{console.error("Une erreur s'est produite lors du chargement des scripts :",e)})))}}catch(e){}const n=e.split("\n");let s=[],o=[""],r=!1,a=null,i=[],u=[],l=null;const c=/^\d{1,3}\.\s\[/;let d=!1,p=[],m=[];for(let e of n)if(e.startsWith("# "))o[0]=e.replace("# ","").trim();else if(e.startsWith(">")&&!r)if(e=e.replace(/^>\s?/,"").trim(),e.match(c)){const t=e.replace(/^\d+\.\s/,"").trim(),n=t.replace(/^\[.*?\]\(/,"").replace(/\)$/,""),s=t.replace(/\]\(.*/,"").replace(/^\[/,"");m.push([s,n])}else p.push(e);else if(startsWithAnyOf(e,t))r=!0,a&&s.push([a,i,u,l]),a=e.replace(startsWithAnyOf(e,t),"").trim(),i=[],l=null,d=!1,u=[];else if(e.startsWith("- ")&&!d)i.push(e.replace("- ","").trim());else if(e.match(c)){d=!1,l||(l=[]);const t=e.replace(/^\d+\.\s/,"").trim(),n=t.replace(/^\[.*?\]\(/,"").replace(/\)$/,""),s=t.replace(/\]\(.*/,"").replace(/^\[/,"");l.push([s,n])}else e.length>0&&(u.push(e+"\n"),d=!0);s.push([a,i,u.join("\n"),l]);const h=[p,m];return s.push(h),s.push(o),s}getMarkdownContent(); -------------------------------------------------------------------------------- /chatdata.js: -------------------------------------------------------------------------------- 1 | const defaultMessage = [ 2 | "Désolé, je ne comprends pas votre question.", 3 | "Pardonnez-moi, mais je ne saisis pas votre demande.", 4 | "Excusez-moi, je ne parviens pas à comprendre ce que vous demandez.", 5 | "Je suis navré, mais je ne parviens pas à saisir votre question.", 6 | "Malheureusement, je ne suis pas en mesure de comprendre votre question.", 7 | "Je suis désolé, mais je ne saisis pas votre question.", 8 | "Pardonnez-moi, mais je ne saisis pas le sens de votre question.", 9 | "Je m'excuse, mais je ne parviens pas à saisir votre demande. Pouvez-vous reformuler votre question, s'il vous plaît ?", 10 | "Je ne suis pas sûr de comprendre ce que vous demandez. Pouvez-vous expliquer davantage ?", 11 | "Je ne peux pas répondre à votre question telle qu'elle est formulée. Pouvez-vous la poser différemment ?", 12 | "Votre question ne semble pas correspondre à mes capacités actuelles. Pourriez-vous la reformuler autrement ?", 13 | "Je n'ai malheureusement pas compris votre requête.", 14 | "Je suis désolé, je ne suis pas capable de répondre.", 15 | "Malheureusement, je ne peux pas répondre à votre question.", 16 | "Malheureusement je n'arrive pas à comprendre votre requête.", 17 | "Excusez-moi, je ne comprends pas votre requête.", 18 | "Excusez-moi, je n'arrive pas à répondre à votre question.", 19 | "Je ne parviens pas à répondre à votre requête. Veuillez m'excuser.", 20 | ]; 21 | 22 | const badWordsMessage = [ 23 | "Même si je ne suis qu'un chatbot, merci de vous adresser à moi avec un langage approprié", 24 | "Je préférerais que nous restions courtois dans notre communication.", 25 | "Les insultes ne sont pas nécessaires. Comment puis-je vous aider autrement ?", 26 | "Essayons de garder une conversation respectueuse.", 27 | "Je préfère une conversation respectueuse et productive.", 28 | "Je vous encourage à reformuler votre question ou commentaire de manière respectueuse.", 29 | "Les mots offensants ne sont pas nécessaires ici. Comment puis-je vous aider de manière constructive ?", 30 | "Restons courtois dans nos échanges, s'il vous plaît.", 31 | "Injures et grossièretés ne mènent nulle part. Comment puis-je vous assister ?", 32 | "Je suis ouvert à la discussion, mais veuillez garder un langage respectueux.", 33 | "Essayons de communiquer de manière civilisée !", 34 | ]; 35 | 36 | let md = ` 37 | --- 38 | gestionGrosMots: true 39 | --- 40 | # ChatMD 41 | 42 | > Bonjour, je suis ChatMD, un chatbot, que vous pouvez configurer par vous-même en Markdown : 43 | > 44 | > - Créez un fichier en Markdown et mettez-le en ligne : sur CodiMD, ou sur une forge 45 | > - Respectez la syntaxe de ChatMD pour définir votre chatbot 46 | > 47 | > Votre chatbot est alors prêt et visible à l'adresse suivante : [https://eyssette.forge.aeif.fr/chatMD/#URL](https://eyssette.forge.aeif.fr/chatMD/#URL) (Mettez l'url de votre fichier à la place de URL) 48 | > 49 | > 1. [Qu'est-ce que le Markdown ?](Markdown) 50 | > 2. [CodiMD, une forge : qu'est-ce que c'est ?](CodiMD et forge) 51 | > 3. [Quelle syntaxe faut-il respecter pour ChatMD ?](Syntaxe) 52 | > 4. [Tu peux me donner des exemples !](Exemples) 53 | > 5. [Quelles sont les options de configuration plus avancées ?](Options de configuration) 54 | > 6. [À quoi ça sert ?](À quoi ça sert ?) 55 | 56 | ## Markdown 57 | - markdown 58 | Le Markdown est un format de balisage très léger qui permet d'écrire rapidement du texte formaté. 59 | 60 | Pour découvrir le Markdown, vous pouvez suivre ce [tutoriel](https://www.markdowntutorial.com/fr/). 61 | 62 | ## CodiMD et forge 63 | - codimd 64 | - codi 65 | - forge 66 | - en ligne 67 | 68 | [CodiMD](https://codimd.apps.education.fr/) est un outil pour écrire du Markdown en ligne et il est disponible avec vos identifiants académiques sur le [portail Apps Edu](https://portail.apps.education.fr/). 69 | 70 | Une forge est un outil plus complet qui permet d'héberger des fichiers texte et de les transformer en site web, en carte mentale, ou encore ici en chatbot ! ChatMD est présent sur la [Forge des Communs Numériques Éducatifs](https://forge.aeif.fr/) et vous pouvez aussi mettre vos fichiers sur cette forge. 71 | 72 | ## Syntaxe 73 | - syntaxe 74 | - règles 75 | - comment 76 | 77 | La syntaxe pour écrire un chatbot avec chatMD est la suivante, mais c'est peut-être plus simple de [voir des exemples](#Exemples) ou bien de [récupérer un modèle](https://codimd.apps.education.fr/mBGbHStJSVOSrlGfGb981A?both). 78 | 79 | - On définit le titre du chatbot dans un titre de niveau 1 80 | - Le message initial est à mettre dans un bloc de citation après le titre du chatbot 81 | - Les titres de niveau 2 servent à identifier les réponses possibles du chatbot 82 | - Sous chaque titre de niveau 2 : 83 | - On indique avec une liste non ordonnée les mots clés ou expressions qui vont déclencher la réponse. On peut éventuellement s'en passer si on guide l'utilisateur avec un choix d'options (voir ci-dessous). 84 | - On écrit une réponse en Markdown. 85 | - [Optionnel] On indique avec une liste ordonnée les options possibles. Chaque élément de la liste doit être un lien en Mardown de la forme suivante : \`[intitulé de l'option](identifiant de l'option, qui doit correspondre à l'un des titres de niveau 2)\`. 86 | 87 | 1. [Voir aussi les options de configuration plus avancées](Options de configuration) 88 | 89 | ## Options de configuration 90 | - yaml 91 | - en-tête 92 | - en-tête yaml 93 | - options 94 | - avancé 95 | 96 | On peut ajouter un en-tête yaml à son fichier Markdown : 97 | 98 | - \`clavier: false\` désactive le champ d'entrée clavier si on souhaite simplement guider l'utilisateur avec les options proposées en fin de chaque réponse. 99 | - \`rechercheContenu: true\` permet d'ajouter une recherche de comparaison de l'entrée de l'utilisateur avec le contenu de chaque réponse 100 | - \`style: a{color:red}\` permet d'ajouter des styles CSS personnalisés. 101 | - \`gestionGrosMots: true\` permet de détecter les gros mots envoyés par l'utilisateur et de formuler une réponse adéquate si l'utilisateur en utilise 102 | - \`maths: true\` permet d'écrire des formules mathématiques en Latex avec la syntaxe \`$Latex$\` ou \`$$Latex$$\` 103 | - \`titresRéponses: ["### ", "#### "]\` permet de changer les identifiants possibles des réponses du chatbot si on veut pouvoir structurer les réponses du chatbot dans son document 104 | 105 | ## Exemples 106 | - exemple 107 | - donner un exemple 108 | - concret 109 | - concrètement 110 | - modèle 111 | - template 112 | 113 | Voici un modèle que vous pouvez récupérer pour construire votre chatbot : [modèle à récupérer](https://codimd.apps.education.fr/mBGbHStJSVOSrlGfGb981A?both) 114 | 115 | Voici quelques exemple de chatbot créés avec ChatMD : 116 | 117 | - [Méthode de la dissertation en philosophie](https://eyssette.forge.aeif.fr/chatMD/#https://eyssette.forge.aeif.fr/chatbot/dissertation-philosophie.md) 118 | - [Utilisation d'un microscope](https://eyssette.forge.aeif.fr/chatMD/#https://codimd.apps.education.fr/xGNHIJSeTVCk6FHas-_71g) : un chatbot créé à partir du travail de Guillaume Berthelot et de Jérémy Navoizat [voir la source](https://codimd.apps.education.fr/xGNHIJSeTVCk6FHas-_71g?both) 119 | 120 | ## À quoi ça sert ? 121 | - à quoi ça sert ? 122 | - pourquoi 123 | - sert 124 | - intérêt 125 | - servir 126 | - objectif 127 | - but 128 | - en faire 129 | - tutoriel 130 | - histoire 131 | - méthod 132 | - révision 133 | - utilité 134 | - utile 135 | 136 | On peut imaginer plusieurs usages de chatMD : 137 | - Tutoriel pour un outil informatique 138 | - Histoire dont vous êtes le héros 139 | - Guide méthodologique 140 | - Soutien pour la révision d'un cours 141 | - … 142 | 143 | On peut faire travailler des élèves ensemble sur un CodiMD, ou bien travailler collaborativement entre collègues, en tant que prof ou dans le cadre d'une formation. 144 | 145 | Si vous avez trouvé des idées intéressantes, n'hésitez pas à les partager avec moi. Vous pouvez me contacter sur [Mastodon](https://scholar.social/@eyssette). 146 | 147 | ## Merci 148 | - merci 149 | - remercier 150 | - remercie 151 | - félicitations 152 | - félicit 153 | - bravo 154 | - super 155 | - excellent 156 | - génial 157 | - wow 158 | - chouette 159 | - sympa 160 | 161 | Merci ! Si vous aimez ce travail, vous aimerez peut-être aussi les autres outils ou sites que je propose sur [mon site perso](https://eyssette.github.io). 162 | 163 | 164 | `; 165 | 166 | const shortcuts = [ 167 | ["dissertation-philo","https://raw.githubusercontent.com/eyssette/chatbot/main/dissertation-philosophie.md"] 168 | ]; 169 | 170 | let yamlStyle = ""; 171 | let yamlUserInput = true; 172 | let yamlSearchInContent = false; 173 | let yamldetectBadWords = false; 174 | let yamlMaths = false; 175 | 176 | let chatData; 177 | let filterBadWords; 178 | 179 | function getMarkdownContent() { 180 | // Récupération du markdown externe 181 | let urlMD = window.location.hash.substring(1); // Récupère l'URL du hashtag sans le # 182 | if (urlMD !== "") { 183 | // Gestion des fichiers hébergés sur github 184 | if (urlMD.startsWith("https://github.com")) { 185 | urlMD = urlMD.replace( 186 | "https://github.com", 187 | "https://raw.githubusercontent.com" 188 | ); 189 | urlMD = urlMD.replace("/blob/", "/"); 190 | } 191 | // Gestion des fichiers hébergés sur codiMD 192 | if ( 193 | urlMD.startsWith("https://codimd") && 194 | urlMD.indexOf("download") === -1 195 | ) { 196 | urlMD = 197 | urlMD.replace("?edit", "").replace("?both", "").replace("?view", "") + 198 | "/download"; 199 | } 200 | // Vérification de la présence d'un raccourci 201 | shortcut = shortcuts.find(element => element[0]==urlMD) 202 | if (shortcut) { 203 | urlMD = shortcut[1] 204 | } 205 | // Récupération du contenu du fichier 206 | fetch(urlMD) 207 | .then((response) => response.text()) 208 | .then((data) => { 209 | md = data; 210 | chatData = parseMarkdown(md); 211 | /* console.log(chatData); */ 212 | createChatBot(chatData); 213 | }) 214 | .catch((error) => console.error(error)); 215 | } else { 216 | createChatBot(parseMarkdown(md)); 217 | } 218 | } 219 | 220 | getMarkdownContent(); 221 | 222 | function loadScript(src) { 223 | // Fonction pour charger des scripts 224 | return new Promise((resolve, reject) => { 225 | const script = document.createElement("script"); 226 | script.src = src; 227 | script.onload = resolve; 228 | script.onerror = reject; 229 | document.head.appendChild(script); 230 | }); 231 | } 232 | function loadCSS(src) { 233 | // Fonction pour charger des CSS 234 | return new Promise((resolve, reject) => { 235 | const styleElement = document.createElement("link"); 236 | styleElement.href = src; 237 | styleElement.rel = "stylesheet"; 238 | styleElement.onload = resolve; 239 | styleElement.onerror = reject; 240 | document.head.appendChild(styleElement); 241 | }); 242 | } 243 | 244 | function startsWithAnyOf(string,array) { 245 | // Vérifie si une variable texte commence par un élément d'un tableau 246 | for (const element of array) { 247 | if (string.startsWith(element)) { 248 | return element 249 | break; 250 | } 251 | } 252 | } 253 | 254 | function parseMarkdown(markdownContent) { 255 | let responsesTitles = ["## "]; // Par défaut les titres des réponses sont définis par des titres en markdown niveau 2 256 | if (markdownContent.split("---").length > 2) { 257 | try { 258 | yamlData = jsyaml.load(markdownContent.split("---")[1]); 259 | for (const property in yamlData) { 260 | if (property == "maths") { 261 | yamlMaths = yamlData[property]; 262 | if (yamlMaths === true) { 263 | Promise.all([ 264 | loadScript( 265 | "https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js" 266 | ), 267 | loadCSS( 268 | "https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css" 269 | ), 270 | ]); 271 | } 272 | } 273 | if (property == "titresRéponses" || property == "responsesTitles") { 274 | responsesTitles = yamlData[property]; 275 | if (typeof responsesTitles === 'string') { 276 | // Cas où le yaml pour les titres des réponses ne contient pas un tableau, mais un seul élément 277 | responsesTitles = [responsesTitles]; 278 | } 279 | } 280 | if (property == "style") { 281 | yamlStyle = yamlData[property]; 282 | const styleElement = document.createElement("style"); 283 | styleElement.innerHTML = yamlStyle; 284 | document.body.appendChild(styleElement); 285 | } 286 | if ( 287 | property == "userInput" || 288 | property == "clavier" || 289 | property == "keyboard" 290 | ) { 291 | yamlUserInput = yamlData[property]; 292 | if (yamlUserInput === false) { 293 | const controls = document.getElementById("controls"); 294 | controls.style.display = "none"; 295 | } 296 | } 297 | if (property == "searchInContent" || property == "rechercheContenu") { 298 | yamlSearchInContent = yamlData[property]; 299 | } 300 | if (property == "gestionGrosMots" || property == "detectBadWords") { 301 | yamldetectBadWords = yamlData[property]; 302 | if (yamldetectBadWords === true) { 303 | Promise.all([ 304 | loadScript("https://cdn.jsdelivr.net/npm/leo-profanity"), 305 | loadScript("badWords-fr.js"), 306 | ]) 307 | .then(() => { 308 | // Les deux scripts sont chargés et prêts à être utilisés 309 | filterBadWords = LeoProfanity; 310 | filterBadWords.add(badWordsFR); 311 | }) 312 | .catch((error) => { 313 | console.error( 314 | "Une erreur s'est produite lors du chargement des scripts :", 315 | error 316 | ); 317 | }); 318 | } 319 | } 320 | } 321 | } catch (e) {} 322 | } 323 | const lines = markdownContent.split("\n"); 324 | let chatbotData = []; 325 | let chatbotTitle = [""]; 326 | 327 | let initialMessageComputed = false; 328 | let currentH2Title = null; 329 | let currentLiItems = []; 330 | let content = []; 331 | let lastOrderedList = null; 332 | const regexOrderedList = /^\d{1,3}\.\s\[/; 333 | let listParsed = false; 334 | let initialMessageContent = []; 335 | let initialMessageOptions = []; 336 | 337 | for (let line of lines) { 338 | // On parcourt le contenu du fichier ligne par ligne 339 | if (line.startsWith("# ")) { 340 | // Récupération du titre du chatbot 341 | chatbotTitle[0] = line.replace("# ", "").trim(); 342 | } else if (line.startsWith(">") && !initialMessageComputed) { 343 | // Récupération du message initial du chatbot, défini par un bloc citation 344 | line = line.replace(/^>\s?/, "").trim(); 345 | if (line.match(regexOrderedList)) { 346 | // Récupération des options dans le message initial, s'il y en a 347 | const listContent = line.replace(/^\d+\.\s/, "").trim(); 348 | const link = listContent.replace(/^\[.*?\]\(/, "").replace(/\)$/, ""); 349 | const text = listContent.replace(/\]\(.*/, "").replace(/^\[/, ""); 350 | initialMessageOptions.push([text, link]); 351 | } else { 352 | initialMessageContent.push(line); 353 | } 354 | } else if (startsWithAnyOf(line,responsesTitles)) { 355 | // Gestion des identifiants de réponse, et début de traitement du contenu de chaque réponse 356 | initialMessageComputed = true; 357 | if (currentH2Title) { 358 | chatbotData.push([ 359 | currentH2Title, 360 | currentLiItems, 361 | content, 362 | lastOrderedList, 363 | ]); 364 | } 365 | currentH2Title = line.replace(startsWithAnyOf(line,responsesTitles), "").trim(); // Titre h2 366 | currentLiItems = []; 367 | lastOrderedList = null; 368 | listParsed = false; 369 | content = []; 370 | } else if (line.startsWith("- ") && !listParsed) { 371 | // Gestion des listes 372 | currentLiItems.push(line.replace("- ", "").trim()); 373 | } else if (line.match(regexOrderedList)) { 374 | // Cas des listes ordonnées 375 | listParsed = false; 376 | if (!lastOrderedList) { 377 | lastOrderedList = []; 378 | } 379 | const listContent = line.replace(/^\d+\.\s/, "").trim(); 380 | const link = listContent.replace(/^\[.*?\]\(/, "").replace(/\)$/, ""); 381 | const text = listContent.replace(/\]\(.*/, "").replace(/^\[/, ""); 382 | lastOrderedList.push([text, link]); 383 | /* lastOrderedList.push(listContent); */ 384 | } else if (line.length > 0) { 385 | // Gestion du reste du contenu 386 | content.push(line + "\n"); 387 | listParsed = true; 388 | } 389 | } 390 | chatbotData.push([ 391 | currentH2Title, 392 | currentLiItems, 393 | content.join("\n"), 394 | lastOrderedList, 395 | ]); 396 | 397 | const initialMessage = [initialMessageContent, initialMessageOptions]; 398 | chatbotData.push(initialMessage); 399 | chatbotData.push(chatbotTitle); 400 | 401 | return chatbotData; 402 | } 403 | -------------------------------------------------------------------------------- /chatbot.js: -------------------------------------------------------------------------------- 1 | function createChatBot(chatData) { 2 | const chatbotName = chatData.pop(); 3 | let initialMessage = chatData.pop(); 4 | document.getElementById("chatbot-name").textContent = chatbotName; 5 | 6 | const chatContainer = document.getElementById("chat"); 7 | const userInput = document.getElementById("user-input"); 8 | const sendButton = document.getElementById("send-button"); 9 | let optionsLastResponse = null; 10 | let randomDefaultMessageIndex = Math.floor( 11 | Math.random() * defaultMessage.length 12 | ); 13 | let randomDefaultMessageIndexLastChoice = []; 14 | 15 | // Gestion du markdown dans les réponses du chatbot 16 | var converter = new showdown.Converter({ 17 | emoji: true, 18 | parseImgDimensions: true, 19 | }); 20 | function markdownToHTML(text) { 21 | const html = converter.makeHtml(text); 22 | return html; 23 | } 24 | 25 | // Effet machine à écrire 26 | function typeWriter(content, element) { 27 | var typed = new Typed(element, { 28 | strings: [content], 29 | typeSpeed: 1, 30 | startDelay: 100, 31 | onBegin: () => {userInput.focus();}, 32 | }); 33 | } 34 | 35 | function convertLatexExpressions(string) { 36 | string = string 37 | .replace(/\$\$(.*?)\$\$/g, "\[$1\]") 38 | .replace(/\$(.*?)\$/g, "\($1\)"); 39 | let expressionsLatex = string.match( 40 | new RegExp(/\\[.*?\\]|\\(.*?\\)/g) 41 | ); 42 | if (expressionsLatex) { 43 | // On n'utilise Katex que s'il y a des expressions en Latex dans le Markdown 44 | for (let expressionLatex of expressionsLatex) { 45 | // On vérifie si le mode d'affichage de l'expression (inline ou block) 46 | const inlineMaths = expressionLatex.includes("\[") ? true : false; 47 | // On récupère la formule mathématique 48 | let mathInExpressionLatex = expressionLatex 49 | .replace("\[", "") 50 | .replace("\]", ""); 51 | mathInExpressionLatex = mathInExpressionLatex 52 | .replace("\(", "") 53 | .replace("\)", ""); 54 | // On convertit la formule mathématique en HTML avec Katex 55 | stringWithLatex = katex.renderToString(mathInExpressionLatex, { 56 | displayMode: inlineMaths, 57 | }); 58 | string = string.replace(expressionLatex, stringWithLatex); 59 | } 60 | } 61 | return string; 62 | } 63 | 64 | // Création du message par le bot ou l'utilisateur 65 | function createChatMessage(message, isUser) { 66 | const chatMessage = document.createElement("div"); 67 | chatMessage.classList.add("message"); 68 | chatMessage.classList.add(isUser ? "user-message" : "bot-message"); 69 | let html = markdownToHTML(message); 70 | if (yamlMaths === true) { 71 | // S'il y a des maths, on doit gérer le Latex avant d'afficher le message 72 | setTimeout(() => { 73 | html = convertLatexExpressions(html); 74 | // Effet machine à écrire : seulement quand c'est le chatbot qui répond, sinon affichage direct 75 | if (isUser) { 76 | chatMessage.innerHTML = html; 77 | } else { 78 | typeWriter(html, chatMessage); 79 | } 80 | chatContainer.appendChild(chatMessage); 81 | }, 100); 82 | } else { 83 | // Effet machine à écrire : seulement quand c'est le chatbot qui répond, sinon affichage direct 84 | if (isUser) { 85 | chatMessage.innerHTML = html; 86 | } else { 87 | typeWriter(html, chatMessage); 88 | } 89 | chatContainer.appendChild(chatMessage); 90 | } 91 | } 92 | 93 | function levenshteinDistance(a, b) { 94 | /* Fonction pour calculer une similarité plutôt que d'en rester à une identité stricte */ 95 | if (a.length === 0) return b.length; 96 | if (b.length === 0) return a.length; 97 | 98 | const matrix = []; 99 | for (let i = 0; i <= b.length; i++) { 100 | matrix[i] = [i]; 101 | } 102 | 103 | for (let j = 0; j <= a.length; j++) { 104 | matrix[0][j] = j; 105 | } 106 | 107 | for (let i = 1; i <= b.length; i++) { 108 | for (let j = 1; j <= a.length; j++) { 109 | const cost = a[j - 1] === b[i - 1] ? 0 : 1; 110 | matrix[i][j] = Math.min( 111 | matrix[i - 1][j] + 1, 112 | matrix[i][j - 1] + 1, 113 | matrix[i - 1][j - 1] + cost 114 | ); 115 | } 116 | } 117 | 118 | return matrix[b.length][a.length]; 119 | } 120 | 121 | function hasLevenshteinDistanceLessThan(string, keyWord, distance) { 122 | // Teste la présence d'un mot dans une chaîne de caractère qui a une distance de Levenshstein inférieure à une distance donnée 123 | 124 | const words = string.split(" "); 125 | // On parcourt les mots 126 | 127 | for (const word of words) { 128 | // On calcule la distance de Levenshtein entre le mot et le mot cible 129 | const distanceLevenshtein = levenshteinDistance(word, keyWord); 130 | 131 | // Si la distance est inférieure à la distance donnée, on renvoie vrai 132 | if (distanceLevenshtein < distance) { 133 | return true; 134 | } 135 | } 136 | 137 | // Si on n'a pas trouvé de mot avec une distance inférieure à la distance donnée, on renvoie faux 138 | return false; 139 | } 140 | 141 | const LEVENSHTEIN_THRESHOLD = 3; // Seuil de similarité 142 | const MATCH_SCORE_IDENTITY = 5; // Pour régler le fait de privilégier l'identité d'un mot à la simple similarité 143 | const BESTMATCH_THRESHOLD = 0.55 // Seuil pour que le bestMatch soit pertinent 144 | 145 | function responseToSelectedOption(optionLink) { 146 | // Gestion de la réponse à envoyer si on sélectionne une des options proposées 147 | if (optionLink != "") { 148 | for (let i = 0; i < chatData.length; i++) { 149 | const title = chatData[i][0]; 150 | if (optionLink == title) { 151 | let response = chatData[i][2]; 152 | const options = chatData[i][3]; 153 | response = Array.isArray(response) ? response.join("\n\n") : response; 154 | optionsLastResponse = options; 155 | response = gestionOptions(response, options); 156 | createChatMessage(response, false); 157 | } 158 | } 159 | } else { 160 | createChatMessage(initialMessage, false); 161 | } 162 | } 163 | 164 | function removeAccents(str) { 165 | const accentMap = { 166 | à: 'a', 167 | â: 'a', 168 | é: 'e', 169 | è: 'e', 170 | ê: 'e', 171 | ë: 'e', 172 | î: 'i', 173 | ï: 'i', 174 | ô: 'o', 175 | ö: 'o', 176 | û: 'u', 177 | ü: 'u', 178 | ÿ: 'y', 179 | ç: 'c', 180 | À: 'A', 181 | Â: 'A', 182 | É: 'E', 183 | È: 'E', 184 | Ê: 'E', 185 | Ë: 'E', 186 | Î: 'I', 187 | Ï: 'I', 188 | Ô: 'O', 189 | Ö: 'O', 190 | Û: 'U', 191 | Ü: 'U', 192 | Ÿ: 'Y', 193 | Ç: 'C', 194 | }; 195 | 196 | return str.replace(/[àâéèêëîïôöûüÿçÀÂÉÈÊËÎÏÔÖÛÜŸÇ]/g, (match) => accentMap[match] || match); 197 | } 198 | 199 | function tokenize(text, indexChatBotResponse) { 200 | // Fonction pour diviser une chaîne de caractères en tokens, éventuellement en prenant en compte l'index de la réponse du Chatbot (pour prendre en compte différement les tokens présents dans le titre de la réponse) 201 | 202 | // On garde d'abord seulement les mots d'au moins 5 caractères et on remplace les lettres accentuées par l'équivalent sans accent 203 | let words = text.toLowerCase() 204 | words = words.replace(/,|\.|\:|\?|\!|\(|\)|\[|\||\/\]/g,""); 205 | words = removeAccents(words); 206 | words = words.split(/\s|'/).filter(word => word.length >= 5) || []; 207 | const tokens = []; 208 | 209 | // On va créer des tokens avec à chaque fois un poids associé 210 | for (const word of words) { 211 | // Premier type de token : le mot en entier ; poids le plus important 212 | tokens.push({word, weight: 5}); 213 | 214 | // Ensuite on intègre des tokens de 5, 6 et 7 caractères consécutifs pour détecter des racines communes 215 | // Plus le token est long, plus le poids du token est important 216 | const weights = [0, 0, 0, 0, 0.4, 0.6, 0.8]; 217 | // Si le token correspond au début du mot, le poids est plus important 218 | const bonusStart = 0.2; 219 | // Si le token est présent dans le titre, le poids est très important 220 | const bonusInTitle = 10; 221 | 222 | function weightedToken(index, tokenDimension) { 223 | let weight = weights[tokenDimension-1]; // Poids en fonction de la taille du token 224 | weight = index === 0 ? weight+bonusStart : weight; // Bonus si le token est en début du mot 225 | const token = word.substring(index, index + tokenDimension); 226 | if (indexChatBotResponse) { 227 | const titleResponse = chatData[indexChatBotResponse][0].toLowerCase(); 228 | // Bonus si le token est dans le titre 229 | if (titleResponse.includes(token)) { 230 | weight = weight + bonusInTitle; 231 | } 232 | } 233 | return {token, weight: weight} 234 | } 235 | 236 | const wordLength = word.length; 237 | 238 | if (wordLength >= 5) { 239 | for (let i = 0; i <= wordLength - 5; i++) { 240 | tokens.push(weightedToken(i,5)); 241 | } 242 | } 243 | if (wordLength >= 6) { 244 | for (let i = 0; i <= wordLength - 6; i++) { 245 | tokens.push(weightedToken(i,6)); 246 | } 247 | } 248 | if (wordLength >= 7) { 249 | for (let i = 0; i <= wordLength - 7; i++) { 250 | tokens.push(weightedToken(i,7)); 251 | } 252 | } 253 | } 254 | return tokens; 255 | } 256 | 257 | function createVector(text, indexChatBotResponse) { 258 | // Fonction pour créer un vecteur pour chaque texte en prenant en compte le poids de chaque token et éventuellement l'index de la réponse du chatbot 259 | const index = indexChatBotResponse ? indexChatBotResponse : false 260 | const tokens = tokenize(text, indexChatBotResponse); 261 | const vec = {}; 262 | for (const {token, weight} of tokens) { 263 | if (token) { 264 | vec[token] = (vec[token] || 0) + weight; 265 | } 266 | } 267 | return vec; 268 | } 269 | 270 | let vectorChatBotResponses = []; 271 | // On précalcule les vecteurs des réponses du chatbot 272 | if (yamlSearchInContent) { 273 | for (let i = 0; i < chatData.length; i++) { 274 | const responses = chatData[i][2]; 275 | let response = Array.isArray(responses) ? responses.join(" ").toLowerCase() : responses.toLowerCase(); 276 | response = chatData[i][0] + ' ' + response 277 | const vectorResponse = createVector(response, i) 278 | vectorChatBotResponses.push(vectorResponse) 279 | } 280 | } 281 | 282 | function cosineSimilarity(str, vector) { 283 | // Calcul de similarité entre une chaîne de caractère (ce sera le message de l'utilisateur) et une autre chaîne de caractère déjà transformée en vecteur (c'est le vecteur de la réponse du chatbot) 284 | 285 | // Calcule le produit scalaire de deux vecteurs 286 | function dotProduct(vec1, vec2) { 287 | const commonWords = new Set([...Object.keys(vec1), ...Object.keys(vec2)]); 288 | let dot = 0; 289 | for (const word of commonWords) { 290 | dot += (vec1[word] || 0) * (vec2[word] || 0); 291 | } 292 | return dot; 293 | } 294 | 295 | // Calcule la magnitude d'un vecteur 296 | function magnitude(vec) { 297 | let sum = 0; 298 | for (const word in vec) { 299 | sum += vec[word] ** 2; 300 | } 301 | return Math.sqrt(sum); 302 | } 303 | 304 | // Crée les vecteurs pour la chaîne de caractère (qui correspondra au message de l'utilisateur) 305 | const vectorString = createVector(str); 306 | 307 | // Calcule la similarité cosinus 308 | const dot = dotProduct(vectorString, vector); 309 | const mag1 = magnitude(vectorString); 310 | const mag2 = magnitude(vector); 311 | 312 | if (mag1 === 0 || mag2 === 0) { 313 | return 0; // Évitez la division par zéro 314 | } else { 315 | return dot / (mag1 * mag2); 316 | } 317 | } 318 | 319 | function chatbotResponse(userInputText) { 320 | // Choix de la réponse que le chatbot va envoyer 321 | 322 | if(yamldetectBadWords === true && filterBadWords) { 323 | if (filterBadWords.check(userInputText)) { 324 | const randomBadWordsMessageIndex = Math.floor( 325 | Math.random() * badWordsMessage.length 326 | ); 327 | createChatMessage(badWordsMessage[randomBadWordsMessageIndex],false); 328 | return; 329 | } 330 | } 331 | 332 | let bestMatch = null; 333 | let bestMatchScore = 0; 334 | let bestDistanceScore = 0; 335 | let userInputTextToLowerCase = userInputText.toLowerCase(); 336 | let indexBestMatch; 337 | 338 | let optionsLastResponseKeysToLowerCase; 339 | let indexLastResponseKeyMatch; 340 | if (optionsLastResponse) { 341 | // On va comparer le message de l'utilisateur aux dernières options proposées s'il y en a une 342 | optionsLastResponseKeysToLowerCase = optionsLastResponse.map( 343 | (element) => { 344 | return element[0].toLowerCase(); 345 | } 346 | ); 347 | indexLastResponseKeyMatch = optionsLastResponseKeysToLowerCase.indexOf( 348 | userInputTextToLowerCase 349 | ); 350 | } 351 | 352 | if (optionsLastResponse && indexLastResponseKeyMatch > -1) { 353 | // Si le message de l'utilisateur correspond à une des options proposées, on renvoie directement vers cette option 354 | const optionLink = optionsLastResponse[indexLastResponseKeyMatch][1]; 355 | responseToSelectedOption(optionLink); 356 | } else { 357 | /* Sinon, on cherche la meilleure réponse possible en testant l'identité ou la similarité entre les mots ou expressions clés de chaque réponse possible et le message envoyé */ 358 | for (let i = 0; i < chatData.length; i++) { 359 | const keywords = chatData[i][1]; 360 | const responses = chatData[i][2]; 361 | let matchScore = 0; 362 | let distanceScore = 0; 363 | let distance = 0; 364 | if (yamlSearchInContent) { 365 | const cosSim = cosineSimilarity(userInputTextToLowerCase,vectorChatBotResponses[i]); 366 | matchScore = matchScore + cosSim + 0.5; 367 | } 368 | for (let keyword of keywords) { 369 | let keywordToLowerCase = keyword.toLowerCase(); 370 | if (userInputTextToLowerCase.includes(keywordToLowerCase)) { 371 | // Test de l'identité stricte 372 | // En cas d'identité stricte, on monte le score d'une valeur plus importante que 1 (définie par MATCH_SCORE_IDENTITY) 373 | matchScore = matchScore + MATCH_SCORE_IDENTITY; 374 | // On privilégie les correspondances sur les keywords plus longs 375 | matchScore = matchScore + keywordToLowerCase.length/10; 376 | } else if (userInputTextToLowerCase.length > 4) { 377 | // Sinon : test de la similarité (seulement si le message de l'utilisateur n'est pas très court) 378 | if (hasLevenshteinDistanceLessThan(userInputTextToLowerCase, keyword, LEVENSHTEIN_THRESHOLD)) { 379 | distanceScore++; 380 | } 381 | } 382 | } 383 | if (matchScore == 0) { 384 | // En cas de simple similarité : on monte quand même le score, mais d'une unité seulement 385 | if (distanceScore > bestDistanceScore) { 386 | matchScore++; 387 | bestDistanceScore = distanceScore; 388 | } 389 | } 390 | if (matchScore > bestMatchScore) { 391 | bestMatch = responses; 392 | bestMatchScore = matchScore; 393 | indexBestMatch = i; 394 | } 395 | } 396 | if (bestMatch && bestMatchScore > BESTMATCH_THRESHOLD) { 397 | // On envoie le meilleur choix s'il en existe un 398 | let selectedResponse = Array.isArray(bestMatch) 399 | ? bestMatch.join("\n\n") 400 | : bestMatch; 401 | const options = chatData[indexBestMatch][3]; 402 | selectedResponse = gestionOptions(selectedResponse, options); 403 | createChatMessage(selectedResponse, false); 404 | } else { 405 | // En cas de correspondance non trouvée, on envoie un message par défaut (sélectionné au hasard dans la liste définie par defaultMessage) 406 | // On fait en sorte que le message par défaut envoyé ne soit pas le même que les derniers messages par défaut envoyés 407 | while ( 408 | randomDefaultMessageIndexLastChoice.includes( 409 | randomDefaultMessageIndex 410 | ) 411 | ) { 412 | randomDefaultMessageIndex = Math.floor( 413 | Math.random() * defaultMessage.length 414 | ); 415 | } 416 | if (randomDefaultMessageIndexLastChoice.length > 4) { 417 | randomDefaultMessageIndexLastChoice.shift(); 418 | } 419 | randomDefaultMessageIndexLastChoice.push(randomDefaultMessageIndex); 420 | createChatMessage(defaultMessage[randomDefaultMessageIndex], false); 421 | } 422 | } 423 | } 424 | 425 | function gestionOptions(response, options) { 426 | if (options) { 427 | optionsLastResponse = options; 428 | // Gestion du cas où il y a un choix possible entre différentes options après la réponse du chatbot 429 | let messageOptions = "\n
    "; 430 | for (let i = 0; i < options.length; i++) { 431 | const option = options[i]; 432 | const optionText = option[0]; 433 | const optionLink = option[1]; 434 | messageOptions = 435 | messageOptions + 436 | '
  • ' + 439 | optionText + 440 | "
  • \n"; 441 | } 442 | messageOptions = 443 | messageOptions + "
" 444 | response = response + messageOptions; 445 | } else { 446 | optionsLastResponse = null; 447 | } 448 | return response; 449 | } 450 | 451 | // Gestion des événéments js 452 | sendButton.addEventListener("click", () => { 453 | const userInputText = userInput.innerText; 454 | if (userInputText.trim() !== "") { 455 | createChatMessage(userInputText, true); 456 | setTimeout(() => { 457 | chatbotResponse(userInputText); 458 | window.scrollTo(0, document.body.scrollHeight); 459 | }, 100); 460 | userInput.innerText = ""; 461 | } 462 | }); 463 | 464 | userInput.addEventListener("keypress", (event) => { 465 | if (event.key === "Enter") { 466 | event.preventDefault(); 467 | sendButton.click(); 468 | window.scrollTo(0, document.body.scrollHeight); 469 | } 470 | }); 471 | 472 | userInput.focus(); 473 | 474 | userInput.addEventListener("focus", function () { 475 | this.classList.remove("placeholder"); 476 | }); 477 | 478 | userInput.addEventListener("blur", function () { 479 | this.classList.add("placeholder"); 480 | }); 481 | 482 | chatContainer.addEventListener("click", function (event) { 483 | const target = event.target; 484 | if (target.tagName === "A") { 485 | // Gestion du cas où on clique sur un lien 486 | const currentUrl = window.location.href; 487 | const link = target.getAttribute("href"); 488 | if (link.startsWith(currentUrl)) { 489 | // Si le lien est vers un autre chatbot (avec la même url d'origine), alors on ouvre le chatbot dans un autre onglet 490 | window.open(link); 491 | } 492 | if (link.startsWith("#")) { 493 | // Si le lien est vers une option, alors on envoie le message correspondant à cette option 494 | event.preventDefault(); 495 | createChatMessage(target.innerText, true); 496 | const optionLink = link.substring(1); 497 | responseToSelectedOption(optionLink); 498 | window.scrollTo(0, document.body.scrollHeight); 499 | } 500 | } 501 | }); 502 | 503 | // Envoi du message d'accueil du chatbot 504 | initialMessage = gestionOptions( 505 | initialMessage[0].join("\n"), 506 | initialMessage[1] 507 | ); 508 | createChatMessage(initialMessage, false); 509 | initialMessage = initialMessage.replace( 510 | /.*?\<\/span>/, 511 | "" 512 | ); // S'il y a un élément dans le message initial qui ne doit apparaître que la première fois qu'il est affiché, alors on supprime cet élément pour les prochaines fois 513 | } 514 | -------------------------------------------------------------------------------- /badWords-fr.js: -------------------------------------------------------------------------------- 1 | // https://github.com/darwiin/french-badwords-list/ 2 | // The MIT License (MIT) 3 | // Copyright (c) 2013 Maurice Butler 4 | 5 | const badWordsFR =["!mb3c!l3","!mbec!l3","!mbéc!l3","!mbec!le","!mbéc!le","@brut!","@brut1","@bruti","@nd0u!ll3","@nd0u!lle","@nd0u1ll3","@nd0u1lle","@nd0uill3","@nd0uille","@ndou!ll3","@ndou!lle","@ndou1ll3","@ndou1lle","@ndouill3","@ndouille","@v0rt0n","@vorton","1mb3c1l3","1mbec1l3","1mbéc1l3","1mbec1le","1mbéc1le","35p!ng0!n","35p!ngo!n","35p1ng01n","35p1ngo1n","35ping0in","35pingoin","3mm@nch3","3mm@nche","3mm@nché","3mm3rd3r","3mm3rd3u53","3mm3rd3ur","3mm3rd3us3","3mmanch3","3mmanche","3mmanché","3mp@f3","3mp@fe","3mp@fé","3mp@p@0ut3","3mp@p@0ute","3mp@p@0uté","3mp@p@out3","3mp@p@oute","3mp@p@outé","3mpaf3","3mpafe","3mpafé","3mpapa0ut3","3mpapa0ute","3mpapa0uté","3mpapaout3","3mpapaoute","3mpapaouté","3ncul3","3ncul3r","3ncul3ur","3ncule","3nculé","3nf0!r3","3nf0!re","3nf0!ré","3nf01r3","3nf01re","3nf01ré","3nf0ir3","3nf0ire","3nf0iré","3nflur3","3nfo!r3","3nfo!re","3nfo!ré","3nfo1r3","3nfo1re","3nfo1ré","3nfoir3","3nfoire","3nfoiré","3nv@53l!n3ur","3nv@53l1n3ur","3nv@53lin3ur","3nv@s3l!n3ur","3nv@s3l1n3ur","3nv@s3lin3ur","3nva53l!n3ur","3nva53l1n3ur","3nva53lin3ur","3nvas3l!n3ur","3nvas3l1n3ur","3nvas3lin3ur","3p@!5","3p@!s","3p@15","3p@1s","3p@i5","3p@is","3pa!5","3pa!s","3pa15","3pa1s","3pai5","3pais","3sp!ng0!n","3sp!ngo!n","3sp1ng01n","3sp1ngo1n","3sping0in","3spingoin","3tr0n","3tron","5@g0u!n","5@g0u1n","5@g0uin","5@gou!n","5@gou1n","5@gouin","5@l@ud","5@l0p","5@l0p@rd","5@l0p3","5@l0pe","5@l3","5@le","5@lop","5@lop@rd","5@lop3","5@lope","5@tr0u!ll3","5@tr0u!lle","5@tr0u1ll3","5@tr0u1lle","5@tr0uill3","5@tr0uille","5@trou!ll3","5@trou!lle","5@trou1ll3","5@trou1lle","5@trouill3","5@trouille","50tt!53ux","50tt!5eux","50tt153ux","50tt15eux","50tti53ux","50tti5eux","50u5-m3rd3","50u5-merde","53nt-l@-p!553","53nt-l@-p1553","53nt-l@-pi553","53nt-la-p!553","53nt-la-p1553","53nt-la-pi553","5ag0u!n","5ag0u1n","5ag0uin","5agou!n","5agou1n","5agouin","5al0p","5al0p3","5al0pard","5al0pe","5al3","5alaud","5ale","5alop","5alop3","5alopard","5alope","5atr0u!ll3","5atr0u!lle","5atr0u1ll3","5atr0u1lle","5atr0uill3","5atr0uille","5atrou!ll3","5atrou!lle","5atrou1ll3","5atrou1lle","5atrouill3","5atrouille","5chb3b","5chbeb","5chl3u","5chleu","5chn0c","5chn0ck","5chn0qu3","5chn0que","5chnoc","5chnock","5chnoqu3","5chnoque","5ent-l@-p!55e","5ent-l@-p155e","5ent-l@-pi55e","5ent-la-p!55e","5ent-la-p155e","5ent-la-pi55e","5ott!53ux","5ott!5eux","5ott153ux","5ott15eux","5otti53ux","5otti5eux","5ou5-m3rd3","5ou5-merde","5t3@r!qu3","5t3@r1qu3","5t3@riqu3","5t3ar!qu3","5t3ar1qu3","5t3ariqu3","5té@r!qu3","5te@r!que","5té@r!que","5te@r1qu3","5té@r1qu3","5te@r1que","5té@r1que","5te@riqu3","5té@riqu3","5te@rique","5té@rique","5tear!qu3","5téar!qu3","5tear!que","5téar!que","5tear1qu3","5téar1qu3","5tear1que","5téar1que","5teariqu3","5téariqu3","5tearique","5téarique","abrut!","abrut1","abruti","and0u!ll3","and0u!lle","and0u1ll3","and0u1lle","and0uill3","and0uille","andou!ll3","andou!lle","andou1ll3","andou1lle","andouill3","andouille","av0rt0n","avorton","b!@tch","b!atch","b!c0t","b!cot","b!t3","b!t3mb0!5","b!t3mb0!s","b!t3mbo!5","b!t3mbo!s","b!te","b!temb0!5","b!temb0!s","b!tembo!5","b!tembo!s","b@t@rd","b0rd3l","b0rdel","b0uff0n","b0ugn0ul","B0ugn0ul!3","b0ugn0ul!5@t!0n","b0ugn0ul!53r","b0ugn0ul!5at!0n","b0ugn0ul!5er","B0ugn0ul!e","b0ugn0ul!s@t!0n","b0ugn0ul!s3r","b0ugn0ul!sat!0n","b0ugn0ul!ser","B0ugn0ul13","b0ugn0ul15@t10n","b0ugn0ul153r","b0ugn0ul15at10n","b0ugn0ul15er","B0ugn0ul1e","b0ugn0ul1s@t10n","b0ugn0ul1s3r","b0ugn0ul1sat10n","b0ugn0ul1ser","b0ugn0ul3","b0ugn0ule","B0ugn0uli3","b0ugn0uli5@ti0n","b0ugn0uli53r","b0ugn0uli5ati0n","b0ugn0uli5er","B0ugn0ulie","b0ugn0ulis@ti0n","b0ugn0ulis3r","b0ugn0ulisati0n","b0ugn0uliser","b0ugr3","b0ugre","b0uk@k","b0ukak","b0un!0ul","b0un10ul","b0uni0ul","b0urd!ll3","b0urd!lle","b0urd1ll3","b0urd1lle","b0urdill3","b0urdille","b0us3ux","b0useux","b1@tch","b1atch","b1c0t","b1cot","b1t3","b1t3mb015","b1t3mb01s","b1t3mbo15","b1t3mbo1s","b1te","b1temb015","b1temb01s","b1tembo15","b1tembo1s","b3@uf","b3auf","bât@rd","batard","bâtard","be@uf","beauf","bi@tch","biatch","bic0t","bicot","bit3","bit3mb0i5","bit3mb0is","bit3mboi5","bit3mbois","bite","bitemb0i5","bitemb0is","bitemboi5","bitembois","bord3l","bordel","bouffon","bougnoul","Bougnoul!3","bougnoul!5@t!on","bougnoul!53r","bougnoul!5at!on","bougnoul!5er","Bougnoul!e","bougnoul!s@t!on","bougnoul!s3r","bougnoul!sat!on","bougnoul!ser","Bougnoul13","bougnoul15@t1on","bougnoul153r","bougnoul15at1on","bougnoul15er","Bougnoul1e","bougnoul1s@t1on","bougnoul1s3r","bougnoul1sat1on","bougnoul1ser","bougnoul3","bougnoule","Bougnouli3","bougnouli5@tion","bougnouli53r","bougnouli5ation","bougnouli5er","Bougnoulie","bougnoulis@tion","bougnoulis3r","bougnoulisation","bougnouliser","bougr3","bougre","bouk@k","boukak","boun!oul","boun1oul","bounioul","bourd!ll3","bourd!lle","bourd1ll3","bourd1lle","bourdill3","bourdille","bous3ux","bouseux","br!53-burn35","br!5e-burne5","br!s3-burn3s","br!se-burnes","br@nl3r","br@nl3ur","br@nler","br@nleur","br@nqu3","br@nque","br153-burn35","br15e-burne5","br1s3-burn3s","br1se-burnes","branl3r","branl3ur","branler","branleur","branqu3","branque","bri53-burn35","bri5e-burne5","bris3-burn3s","brise-burnes","c@553-b0nb0n","c@553-bonbon","c@553-c0u!ll3","c@553-c0u!ll35","c@553-c0u1ll3","c@553-c0u1ll35","c@553-c0uill3","c@553-c0uill35","c@553-cou!ll3","c@553-cou!ll35","c@553-cou1ll3","c@553-cou1ll35","c@553-couill3","c@553-couill35","c@55e-b0nb0n","c@55e-bonbon","c@55e-c0u!lle","c@55e-c0u!lle5","c@55e-c0u1lle","c@55e-c0u1lle5","c@55e-c0uille","c@55e-c0uille5","c@55e-cou!lle","c@55e-cou!lle5","c@55e-cou1lle","c@55e-cou1lle5","c@55e-couille","c@55e-couille5","c@c0u","c@cou","c@fr3","c@fre","c@ld0ch3","c@ld0che","c@ldoch3","c@ldoche","c@ss3-b0nb0n","c@ss3-bonbon","c@ss3-c0u!ll3","c@ss3-c0u!ll3s","c@ss3-c0u1ll3","c@ss3-c0u1ll3s","c@ss3-c0uill3","c@ss3-c0uill3s","c@ss3-cou!ll3","c@ss3-cou!ll3s","c@ss3-cou1ll3","c@ss3-cou1ll3s","c@ss3-couill3","c@ss3-couill3s","c@sse-b0nb0n","c@sse-bonbon","c@sse-c0u!lle","c@sse-c0u!lles","c@sse-c0u1lle","c@sse-c0u1lles","c@sse-c0uille","c@sse-c0uilles","c@sse-cou!lle","c@sse-cou!lles","c@sse-cou1lle","c@sse-cou1lles","c@sse-couille","c@sse-couilles","c0ch3","c0che","c0n","c0n@553","c0n@55e","c0n@rd","c0n@ss3","c0n@sse","c0n5","c0na553","c0na55e","c0nard","c0nass3","c0nasse","c0nch!3r","c0nch!er","c0nch13r","c0nch1er","c0nchi3r","c0nchier","c0nn@553","c0nn@55e","c0nn@rd","c0nn@rd3","c0nn@rde","c0nn@ss3","c0nn@sse","c0nn3","c0nna553","c0nna55e","c0nnard","c0nnard3","c0nnarde","c0nnass3","c0nnasse","c0nne","c0ns","c0u1ll0n","c0u1ll0nn3r","c0u1ll3","c0u1ll3s","c0uill0n","c0uill0nn3r","c0uill0nner","c0uill3","c0uill3s","c0uille","c0uilles","c0un!fl3","c0un!fle","c0un1fl3","c0un1fle","c0unifl3","c0unifle","c0urt@ud","c0urtaud","ca553-b0nb0n","ca553-bonbon","ca553-c0u!ll3","ca553-c0u!ll35","ca553-c0u1ll3","ca553-c0u1ll35","ca553-c0uill3","ca553-c0uill35","ca553-cou!ll3","ca553-cou!ll35","ca553-cou1ll3","ca553-cou1ll35","ca553-couill3","ca553-couill35","ca55e-b0nb0n","ca55e-bonbon","ca55e-c0u!lle","ca55e-c0u!lle5","ca55e-c0u1lle","ca55e-c0u1lle5","ca55e-c0uille","ca55e-c0uille5","ca55e-cou!lle","ca55e-cou!lle5","ca55e-cou1lle","ca55e-cou1lle5","ca55e-couille","ca55e-couille5","cac0u","cacou","cafr3","cafre","cald0ch3","cald0che","caldoch3","caldoche","cass3-b0nb0n","cass3-bonbon","cass3-c0u!ll3","cass3-c0u!ll3s","cass3-c0u1ll3","cass3-c0u1ll3s","cass3-c0uill3","cass3-c0uill3s","cass3-cou!ll3","cass3-cou!ll3s","cass3-cou1ll3","cass3-cou1ll3s","cass3-couill3","cass3-couill3s","casse-b0nb0n","casse-bonbon","casse-c0u!lle","casse-c0u!lles","casse-c0u1lle","casse-c0u1lles","casse-c0uille","casse-c0uilles","casse-cou!lle","casse-cou!lles","casse-cou1lle","casse-cou1lles","casse-couille","casse-couilles","ch!3nn@553","ch!3nn@ss3","ch!3nna553","ch!3nnass3","ch!3r","ch!enn@55e","ch!enn@sse","ch!enna55e","ch!ennasse","ch!er","ch!n3t0c","ch!n3t0qu3","ch!n3toc","ch!n3toqu3","ch!net0c","ch!net0que","ch!netoc","ch!netoque","ch!nt0k","ch!ntok","ch@ch@r","ch@g@553","ch@g@55e","ch@g@ss3","ch@g@sse","ch@uff@rd","ch13nn@553","ch13nn@ss3","ch13nna553","ch13nnass3","ch13r","ch13ur","ch13urs","ch1enn@55e","ch1enn@sse","ch1enna55e","ch1ennasse","ch1er","ch1eur","ch1eurs","ch1n3t0c","ch1n3t0qu3","ch1n3toc","ch1n3toqu3","ch1net0c","ch1net0que","ch1netoc","ch1netoque","ch1nt0k","ch1ntok","chachar","chaga553","chaga55e","chagass3","chagasse","chauffard","chi3nn@553","chi3nn@ss3","chi3nna553","chi3nnass3","chi3r","chi3ur","chi3urs","chienn@55e","chienn@sse","chienna55e","chiennasse","chier","chieur","chieurs","chin3t0c","chin3t0qu3","chin3toc","chin3toqu3","chinet0c","chinet0que","chinetoc","chinetoque","chint0k","chintok","chl3uh","chleuh","chn0qu3","chn0que","chnoqu3","chnoque","coch3","coche","con","con@553","con@55e","con@rd","con@ss3","con@sse","con5","cona553","cona55e","conard","conass3","conasse","conch!3r","conch!er","conch13r","conch1er","conchi3r","conchier","conn@553","conn@55e","conn@rd","conn@rd3","conn@rde","conn@ss3","conn@sse","conn3","conna553","conna55e","connard","connard3","connarde","connass3","connasse","conne","cons","cou1lle","cou1lles","cou1llon","cou1llonner","couill3","couill3s","couille","couilles","couillon","couillonn3r","couillonner","coun!fl3","coun!fle","coun1fl3","coun1fle","counifl3","counifle","court@ud","courtaud","cr!cr!","cr0tt3","cr0tte","cr0tté","cr0u!ll@t","cr0u!ll3","cr0u!llat","cr0u!lle","cr0u1ll@t","cr0u1ll3","cr0u1llat","cr0u1lle","cr0uill@t","cr0uill3","cr0uillat","cr0uille","cr0ût0n","cr1cr1","cr3t!n","cr3t1n","cr3tin","cr3v@rd","cr3vard","cr3vur3","cret!n","crét!n","cret1n","crét1n","cretin","crétin","crev@rd","crevard","crevure","cricri","crott3","crotte","crotté","crou!ll@t","crou!ll3","crou!llat","crou!lle","crou1ll@t","crou1ll3","crou1llat","crou1lle","crouill@t","crouill3","crouillat","crouille","croûton","cul","d3b!l3","d3b1l3","d3bil3","d3gu3l@ss3","d3gu3lass3","d3m3rd3r","deb!l3","déb!l3","deb!le","déb!le","deb1l3","déb1l3","deb1le","déb1le","debil3","débil3","debile","débile","déguel@sse","deguelasse","déguelasse","demerder","démerder","dr0u!ll3","dr0u!lle","dr0u1ll3","dr0u1lle","dr0uill3","dr0uille","drou!ll3","drou!lle","drou1ll3","drou1lle","drouill3","drouille","du schn0c","du schnoc","du5chn0ck","du5chnock","duc0n","duc0nn0t","ducon","duconnot","dug3n0ux","dug3noux","dugen0ux","dugenoux","dugl@nd","dugland","duschn0ck","duschnock","e5p!ng0!n","e5p!ngo!n","e5p1ng01n","e5p1ngo1n","e5ping0in","e5pingoin","emm@nche","emm@nché","emmanche","emmanché","emmerder","emmerdeu5e","emmerdeur","emmerdeuse","emp@fe","emp@fé","emp@p@0ute","emp@p@0uté","emp@p@oute","emp@p@outé","empafe","empafé","empapa0ute","empapa0uté","empapaoute","empapaouté","encule","enculé","enculer","enculeur","enf0!re","enf0!ré","enf01re","enf01ré","enf0ire","enf0iré","enflure","enfo!re","enfo!ré","enfo1re","enfo1ré","enfoire","enfoiré","env@5el!neur","env@5el1neur","env@5elineur","env@sel!neur","env@sel1neur","env@selineur","enva5el!neur","enva5el1neur","enva5elineur","envasel!neur","envasel1neur","envaselineur","ep@!5","ép@!5","ep@!s","ép@!s","ep@15","ép@15","ep@1s","ép@1s","ep@i5","ép@i5","ep@is","ép@is","epa!5","épa!5","epa!s","épa!s","epa15","épa15","epa1s","épa1s","epai5","épai5","epais","épais","esp!ng0!n","esp!ngo!n","esp1ng01n","esp1ngo1n","esping0in","espingoin","etr0n","étr0n","etron","étron","f!0tt3","f!0tte","f!ott3","f!otte","f0ut3ur","f0uteur","f0utr3","f0utre","f10tt3","f10tte","f1ott3","f1otte","f31gn@ss3","f3ign@ss3","f3ignass3","FDP","fe1gnasse","feign@sse","feignasse","fi0tt3","fi0tte","fiott3","fiotte","fout3ur","fouteur","foutr3","foutre","fr!tz","fr1tz","fritz","fum!3r","fum!er","fum13r","fum1er","fumi3r","fumier","g@rc3","g@rce","g@up3","g@upe","G0d0n","g0g0l","g0ï","g0u!ll@nd","g0u!lland","g0u!n3","g0u!ne","g0u1ll@nd","g0u1lland","g0u1n3","g0u1ne","g0uill@nd","g0uilland","g0uin3","g0uine","g0urd3","g0urde","g0urg@nd!n3","g0urg@nd!ne","g0urg@nd1n3","g0urg@nd1ne","g0urg@ndin3","g0urg@ndine","g0urgand!n3","g0urgand!ne","g0urgand1n3","g0urgand1ne","g0urgandin3","g0urgandine","garc3","garce","gaup3","gaupe","GDM","gl@nd","gl@nd0u!ll0u","gl@nd0u1ll0u","gl@nd0uill0u","gl@nd3u53","gl@nd3ur","gl@nd3us3","gl@ndeu5e","gl@ndeur","gl@ndeuse","gl@ndou!llou","gl@ndou1llou","gl@ndouillou","gl@ndu","gland","gland0u!ll0u","gland0u1ll0u","gland0uill0u","gland3u53","gland3ur","gland3us3","glandeu5e","glandeur","glandeuse","glandou!llou","glandou1llou","glandouillou","glandu","gn0ul","gn0ul3","gn0ule","gnoul","gnoul3","gnoule","Godon","gogol","goï","gou!ll@nd","gou!lland","gou!n3","gou!ne","gou1ll@nd","gou1lland","gou1n3","gou1ne","gouill@nd","gouilland","gouin3","gouine","gourd3","gourde","gourg@nd!n3","gourg@nd!ne","gourg@nd1n3","gourg@nd1ne","gourg@ndin3","gourg@ndine","gourgand!n3","gourgand!ne","gourgand1n3","gourgand1ne","gourgandin3","gourgandine","gr0gn@553","gr0gn@55e","gr0gn@ss3","gr0gn@sse","gr0gna553","gr0gna55e","gr0gnass3","gr0gnasse","grogn@553","grogn@55e","grogn@ss3","grogn@sse","grogna553","grogna55e","grognass3","grognasse","gu!nd0ul3","gu!nd0ule","gu!ndoul3","gu!ndoule","gu1nd0ul3","gu1nd0ule","gu1ndoul3","gu1ndoule","gu3n!ch3","gu3n1ch3","gu3nich3","guen!che","guen1che","gueniche","guind0ul3","guind0ule","guindoul3","guindoule","imb3cil3","imbecil3","imbécil3","imbecile","imbécile","j3@n-f0utr3","j3@n-foutr3","j3an-f0utr3","j3an-foutr3","je@n-f0utre","je@n-foutre","jean-f0utre","jean-foutre","k!k00","k!k0u","k!koo","k!kou","k1k00","k1k0u","k1koo","k1kou","kik00","kik0u","kikoo","kikou","Kr@ut","Kraut","l@ch3ux","l@cheux","l@v3tt3","l@vette","l0p3tt3","l0pette","lach3ux","lâch3ux","lacheux","lâcheux","lav3tt3","lavette","lop3tt3","lopette","m!53r@bl3","m!53rabl3","m!5ér@bl3","m!5er@ble","m!5ér@ble","m!5erabl3","m!5érabl3","m!5erable","m!5érable","m!cht0","m!chto","m!n@bl3","m!n@ble","m!nabl3","m!nable","m!nu5","m!nus","m!s3r@bl3","m!s3rabl3","m!ser@bl3","m!sér@bl3","m!ser@ble","m!sér@ble","m!serabl3","m!sérabl3","m!serable","m!sérable","m@g0t","m@got","m@k0um3","m@k0ume","m@k0umé","m@koum3","m@koume","m@koumé","m@nch3","m@nche","m@ng3-m3rd3","m@nge-merde","m@rch@nd0t","m@rch@ndot","m@rg0u!ll!5t3","m@rg0u!ll!5te","m@rg0u!ll!st3","m@rg0u!ll!ste","m@rg0u1ll15t3","m@rg0u1ll15te","m@rg0u1ll1st3","m@rg0u1ll1ste","m@rg0uilli5t3","m@rg0uilli5te","m@rg0uillist3","m@rg0uilliste","m@rgou!ll!5t3","m@rgou!ll!5te","m@rgou!ll!st3","m@rgou!ll!ste","m@rgou1ll15t3","m@rgou1ll15te","m@rgou1ll1st3","m@rgou1ll1ste","m@rgouilli5t3","m@rgouilli5te","m@rgouillist3","m@rgouilliste","m@uv!3tt3","m@uv!ette","m@uv13tt3","m@uv1ette","m@uvi3tt3","m@uviette","m0!n@!ll3","m0!n@!lle","m0!n5-qu3-r!3n","m0!n5-que-r!en","m0!na!ll3","m0!na!lle","m0!ns-qu3-r!3n","m0!ns-que-r!en","m01n@1ll3","m01n@1lle","m01n5-qu3-r13n","m01n5-que-r1en","m01na1ll3","m01na1lle","m01ns-qu3-r13n","m01ns-que-r1en","m0in@ill3","m0in@ille","m0in5-qu3-ri3n","m0in5-que-rien","m0inaill3","m0inaille","m0ins-qu3-ri3n","m0ins-que-rien","m0n@c@!ll3","m0n@c@!lle","m0n@c@1ll3","m0n@c@1lle","m0n@c@ill3","m0n@c@ille","m0naca!ll3","m0naca!lle","m0naca1ll3","m0naca1lle","m0nacaill3","m0nacaille","m0r!c@ud","m0r!caud","m0r1c@ud","m0r1caud","m0ric@ud","m0ricaud","m153r@bl3","m153rabl3","m15er@bl3","m15ér@bl3","m15er@ble","m15ér@ble","m15erabl3","m15érabl3","m15erable","m15érable","m1cht0","m1chto","m1n@bl3","m1n@ble","m1nabl3","m1nable","m1nu5","m1nus","m1s3r@bl3","m1s3rabl3","m1ser@bl3","m1sér@bl3","m1ser@ble","m1sér@ble","m1serabl3","m1sérabl3","m1serable","m1sérable","m3rd@!ll0n","m3rd@!ll3","m3rd@!llon","m3rd@1ll0n","m3rd@1ll3","m3rd@1llon","m3rd@ill0n","m3rd@ill3","m3rd@illon","m3rd0u!ll@rd","m3rd0u!llard","m3rd0u1ll@rd","m3rd0u1llard","m3rd0uill@rd","m3rd0uillard","m3rd3","m3rd3ux","m3rda!ll0n","m3rda!ll3","m3rda!llon","m3rda1ll0n","m3rda1ll3","m3rda1llon","m3rdaill0n","m3rdaill3","m3rdaillon","m3rdou!ll@rd","m3rdou!llard","m3rdou1ll@rd","m3rdou1llard","m3rdouill@rd","m3rdouillard","mag0t","magot","mak0um3","mak0ume","mak0umé","makoum3","makoume","makoumé","manch3","manche","mang3-m3rd3","mange-merde","marchand0t","marchandot","marg0u!ll!5t3","marg0u!ll!5te","marg0u!ll!st3","marg0u!ll!ste","marg0u1ll15t3","marg0u1ll15te","marg0u1ll1st3","marg0u1ll1ste","marg0uilli5t3","marg0uilli5te","marg0uillist3","marg0uilliste","margou!ll!5t3","margou!ll!5te","margou!ll!st3","margou!ll!ste","margou1ll15t3","margou1ll15te","margou1ll1st3","margou1ll1ste","margouilli5t3","margouilli5te","margouillist3","margouilliste","mauv!3tt3","mauv!ette","mauv13tt3","mauv1ette","mauvi3tt3","mauviette","merd@!ll0n","merd@!lle","merd@!llon","merd@1ll0n","merd@1lle","merd@1llon","merd@ill0n","merd@ille","merd@illon","merd0u!ll@rd","merd0u!llard","merd0u1ll@rd","merd0u1llard","merd0uill@rd","merd0uillard","merda!ll0n","merda!lle","merda!llon","merda1ll0n","merda1lle","merda1llon","merdaill0n","merdaille","merdaillon","merde","merdeux","merdou!ll@rd","merdou!llard","merdou1ll@rd","merdou1llard","merdouill@rd","merdouillard","mi53r@bl3","mi53rabl3","mi5er@bl3","mi5ér@bl3","mi5er@ble","mi5ér@ble","mi5erabl3","mi5érabl3","mi5erable","mi5érable","micht0","michto","min@bl3","min@ble","minabl3","minable","minu5","minus","mis3r@bl3","mis3rabl3","miser@bl3","misér@bl3","miser@ble","misér@ble","miserabl3","misérabl3","miserable","misérable","mo!n@!ll3","mo!n@!lle","mo!n5-qu3-r!3n","mo!n5-que-r!en","mo!na!ll3","mo!na!lle","mo!ns-qu3-r!3n","mo!ns-que-r!en","mo1n@1ll3","mo1n@1lle","mo1n5-qu3-r13n","mo1n5-que-r1en","mo1na1ll3","mo1na1lle","mo1ns-qu3-r13n","mo1ns-que-r1en","moin@ill3","moin@ille","moin5-qu3-ri3n","moin5-que-rien","moinaill3","moinaille","moins-qu3-ri3n","moins-que-rien","mon@c@!ll3","mon@c@!lle","mon@c@1ll3","mon@c@1lle","mon@c@ill3","mon@c@ille","monaca!ll3","monaca!lle","monaca1ll3","monaca1lle","monacaill3","monacaille","mor!c@ud","mor!caud","mor1c@ud","mor1caud","moric@ud","moricaud","n!@!53ux","n!@!5eux","n!@!s3ux","n!@!seux","n!@c","n!@k0u3","n!@k0ue","n!@k0ué","n!@kou3","n!@koue","n!@koué","n!a!53ux","n!a!5eux","n!a!s3ux","n!a!seux","n!ac","n!ak0u3","n!ak0ue","n!ak0ué","n!akou3","n!akoue","n!akoué","n!qu3","n!qu3r","n!que","n!quer","n@s3","n@se","n@z3","n@ze","n1@153ux","n1@15eux","n1@1s3ux","n1@1seux","n1@c","n1@k0u3","n1@k0ue","n1@k0ué","n1@kou3","n1@koue","n1@koué","n1a153ux","n1a15eux","n1a1s3ux","n1a1seux","n1ac","n1ak0u3","n1ak0ue","n1ak0ué","n1akou3","n1akoue","n1akoué","n1qu3","n1qu3r","n1que","n1quer","n3gr0","n3gro","nas3","nase","naz3","naze","negr0","négr0","negro","négro","ni@c","ni@i53ux","ni@i5eux","ni@is3ux","ni@iseux","ni@k0u3","ni@k0ue","ni@k0ué","ni@kou3","ni@koue","ni@koué","niac","niai53ux","niai5eux","niais3ux","niaiseux","niak0u3","niak0ue","niak0ué","niakou3","niakoue","niakoué","niqu3","niqu3r","nique","niquer","NTM","p!550u","p!55ou","p!gn0uf","p!gnouf","p!ss0u","p!ssou","p@k05","p@k0s","p@ko5","p@kos","p@n0ufl3","p@n0ufle","p@noufl3","p@noufle","p@t@r!n","p@t@r1n","p@t@rin","p0rc@5","p0rc@553","p0rc@55e","p0rc@s","p0rc@ss3","p0rc@sse","p0rca5","p0rca553","p0rca55e","p0rcas","p0rcass3","p0rcasse","p0uc@v","p0ucav","p0uf","p0uf!@553","p0uf!@55e","p0uf!@ss3","p0uf!@sse","p0uf!a553","p0uf!a55e","p0uf!ass3","p0uf!asse","p0uf1@553","p0uf1@55e","p0uf1@ss3","p0uf1@sse","p0uf1a553","p0uf1a55e","p0uf1ass3","p0uf1asse","p0uff!@553","p0uff!@55e","p0uff!@ss3","p0uff!@sse","p0uff!a553","p0uff!a55e","p0uff!ass3","p0uff!asse","p0uff1@553","p0uff1@55e","p0uff1@ss3","p0uff1@sse","p0uff1a553","p0uff1a55e","p0uff1ass3","p0uff1asse","p0uffi@553","p0uffi@55e","p0uffi@ss3","p0uffi@sse","p0uffia553","p0uffia55e","p0uffiass3","p0uffiasse","p0ufi@553","p0ufi@55e","p0ufi@ss3","p0ufi@sse","p0ufia553","p0ufia55e","p0ufiass3","p0ufiasse","p0und3","p0unde","p0undé","p0urr!tur3","p0urr!ture","p0urr1tur3","p0urr1ture","p0urritur3","p0urriture","p1550u","p155ou","p1gn0uf","p1gnouf","p1mbêch3","p1mbêche","p1ss0u","p1ss3ux","p1sseux","p1ssou","p3cqu3","p3d@l3","p3d0qu3","p3d3","p3dal3","p3doqu3","p3qu3n@ud","p3qu3naud","p3t","p3t@553","p3t@ss3","p3t3ux","p3ta553","p3tass3","pak05","pak0s","pako5","pakos","pan0ufl3","pan0ufle","panoufl3","panoufle","patar!n","patar1n","patarin","PD","pecque","ped@l3","péd@l3","ped@le","péd@le","ped0qu3","péd0qu3","ped0que","péd0que","pedal3","pédal3","pedale","pédale","pede","pédé","pedoqu3","pédoqu3","pedoque","pédoque","pequ3n@ud","péqu3n@ud","pequ3naud","péqu3naud","pequen@ud","péquen@ud","pequenaud","péquenaud","pet","pét@553","pet@55e","pét@55e","pet@ss3","pét@ss3","pet@sse","pét@sse","peta553","péta553","peta55e","péta55e","petass3","pétass3","petasse","pétasse","peteux","péteux","pi550u","pi55ou","pign0uf","pignouf","pimbêch3","pimbêche","piss0u","piss3ux","pisseux","pissou","pl0uc","pl3utr3","pleutre","plouc","porc@5","porc@553","porc@55e","porc@s","porc@ss3","porc@sse","porca5","porca553","porca55e","porcas","porcass3","porcasse","pouc@v","poucav","pouf","pouf!@553","pouf!@55e","pouf!@ss3","pouf!@sse","pouf!a553","pouf!a55e","pouf!ass3","pouf!asse","pouf1@553","pouf1@55e","pouf1@ss3","pouf1@sse","pouf1a553","pouf1a55e","pouf1ass3","pouf1asse","pouff!@553","pouff!@55e","pouff!@ss3","pouff!@sse","pouff!a553","pouff!a55e","pouff!ass3","pouff!asse","pouff1@553","pouff1@55e","pouff1@ss3","pouff1@sse","pouff1a553","pouff1a55e","pouff1ass3","pouff1asse","pouffi@553","pouffi@55e","pouffi@ss3","pouffi@sse","pouffia553","pouffia55e","pouffiass3","pouffiasse","poufi@553","poufi@55e","poufi@ss3","poufi@sse","poufia553","poufia55e","poufiass3","poufiasse","pound3","pounde","poundé","pourr!tur3","pourr!ture","pourr1tur3","pourr1ture","pourritur3","pourriture","pun@!53","pun@!5e","pun@!s3","pun@!se","pun@153","pun@15e","pun@1s3","pun@1se","pun@i53","pun@i5e","pun@is3","pun@ise","puna!53","puna!5e","puna!s3","puna!se","puna153","puna15e","puna1s3","puna1se","punai53","punai5e","punais3","punaise","put!n","put@!n","put@1n","put@in","put1n","put3","puta!n","puta1n","putain","pute","putin","qu3ut@rd","qu3utard","queut@rd","queutard","r!p0p33","r!p0pe3","r!p0pé3","r!p0pee","r!p0pée","r!pop33","r!pope3","r!popé3","r!popee","r!popée","r@clur3","r@clure","r@t0n","r@ton","r05b!f","r05b1f","r05bif","r0b35p!3rr0t","r0b35p13rr0t","r0b35pi3rr0t","r0b3sp!3rr0t","r0b3sp13rr0t","r0b3spi3rr0t","r0be5p!err0t","r0be5p1err0t","r0be5pierr0t","r0besp!err0t","r0besp1err0t","r0bespierr0t","r0sb!f","r0sb1f","r0sbif","r0ulur3","r0ulure","r1p0p33","r1p0pe3","r1p0pé3","r1p0pee","r1p0pée","r1pop33","r1pope3","r1popé3","r1popee","r1popée","raclur3","raclure","rat0n","raton","rip0p33","rip0pe3","rip0pé3","rip0pee","rip0pée","ripop33","ripope3","ripopé3","ripopee","ripopée","ro5b!f","ro5b1f","ro5bif","rob35p!3rrot","rob35p13rrot","rob35pi3rrot","rob3sp!3rrot","rob3sp13rrot","rob3spi3rrot","robe5p!errot","robe5p1errot","robe5pierrot","robesp!errot","robesp1errot","robespierrot","rosb!f","rosb1f","rosbif","roulur3","roulure","s@g0u!n","s@g0u1n","s@g0uin","s@gou!n","s@gou1n","s@gouin","s@l@ud","s@l0p","s@l0p@rd","s@l0p3","s@l0p3r13","s@l0p3ri3","s@l0pe","s@l3","s@le","s@lop","s@lop@rd","s@lop3","s@lop3ri3","s@lope","s@loperie","s@tr0u!ll3","s@tr0u!lle","s@tr0u1ll3","s@tr0u1lle","s@tr0uill3","s@tr0uille","s@trou!ll3","s@trou!lle","s@trou1ll3","s@trou1lle","s@trouill3","s@trouille","s0tt!s3ux","s0tt!seux","s0tt1s3ux","s0tt1seux","s0ttis3ux","s0ttiseux","s0us-m3rd3","s0us-merde","s3nt-l@-p!ss3","s3nt-l@-p1ss3","s3nt-l@-piss3","s3nt-la-p!ss3","s3nt-la-p1ss3","s3nt-la-piss3","sag0u!n","sag0u1n","sag0uin","sagou!n","sagou1n","sagouin","sal0p","sal0p3","sal0pard","sal0pe","sal0perie","sal3","salaud","sale","salop","salop3","salop3ri3","salopard","salope","saloper1e","saloperie","satr0u!ll3","satr0u!lle","satr0u1ll3","satr0u1lle","satr0uill3","satr0uille","satrou!ll3","satrou!lle","satrou1ll3","satrou1lle","satrouill3","satrouille","schb3b","schbeb","schl3u","schleu","schn0c","schn0ck","schn0qu3","schn0que","schnoc","schnock","schnoqu3","schnoque","sent-l@-p!sse","sent-l@-p1sse","sent-l@-pisse","sent-la-p!sse","sent-la-p1sse","sent-la-pisse","sott!s3ux","sott!seux","sott1s3ux","sott1seux","sottis3ux","sottiseux","sous-m3rd3","sous-merde","st3@r!qu3","st3@r1qu3","st3@riqu3","st3ar!qu3","st3ar1qu3","st3ariqu3","ste@r!qu3","sté@r!qu3","ste@r!que","sté@r!que","ste@r1qu3","sté@r1qu3","ste@r1que","sté@r1que","ste@riqu3","sté@riqu3","ste@rique","sté@rique","stear!qu3","stéar!qu3","stear!que","stéar!que","stear1qu3","stéar1qu3","stear1que","stéar1que","steariqu3","stéariqu3","stearique","stéarique","t@f!0l3","t@f!0le","t@f!ol3","t@f!ole","t@f10l3","t@f10le","t@f1ol3","t@f1ole","t@fi0l3","t@fi0le","t@fiol3","t@fiole","t@nt0u53r!3","t@nt0u53r13","t@nt0u53ri3","t@nt0u5er!e","t@nt0u5er1e","t@nt0u5erie","t@nt0us3r!3","t@nt0us3r13","t@nt0us3ri3","t@nt0user!e","t@nt0user1e","t@nt0userie","t@nt0uz3","t@nt0uze","t@ntou53r!3","t@ntou53r13","t@ntou53ri3","t@ntou5er!e","t@ntou5er1e","t@ntou5erie","t@ntous3r!3","t@ntous3r13","t@ntous3ri3","t@ntouser!e","t@ntouser1e","t@ntouserie","t@ntouz3","t@ntouze","t@p3tt3","t@pette","t@rl0uz3","t@rl0uze","t@rlouz3","t@rlouze","t0c@rd","t0card","t3b3","t3be","t3bé","t3t3ux","t3ub3","t3ube","t3ubé","taf!0l3","taf!0le","taf!ol3","taf!ole","taf10l3","taf10le","taf1ol3","taf1ole","tafi0l3","tafi0le","tafiol3","tafiole","tant0u53r!3","tant0u53r13","tant0u53ri3","tant0u5er!e","tant0u5er1e","tant0u5erie","tant0us3r!3","tant0us3r13","tant0us3ri3","tant0user!e","tant0user1e","tant0userie","tant0uz3","tant0uze","tantou53r!3","tantou53r13","tantou53ri3","tantou5er!e","tantou5er1e","tantou5erie","tantous3r!3","tantous3r13","tantous3ri3","tantouser!e","tantouser1e","tantouserie","tantouz3","tantouze","tap3tt3","tapette","tarl0uz3","tarl0uze","tarlouz3","tarlouze","tebe","tebé","tet3ux","tét3ux","teteux","téteux","teube","teubé","toc@rd","tocard","tr@!n33","tr@!nee","tr@1n33","tr@1nee","tr@in33","tr@în33","tr@îne3","tr@îné3","tr@inee","tr@înee","tr@înée","tr0uduc","tra!n33","tra!nee","tra1n33","tra1nee","train33","traîn33","traîne3","traîné3","trainee","traînee","traînée","trouduc","tru!@553","tru!@55e","tru!@ss3","tru!@sse","tru!a553","tru!a55e","tru!ass3","tru!asse","tru1@553","tru1@55e","tru1@ss3","tru1@sse","tru1a553","tru1a55e","tru1ass3","tru1asse","trui@553","trui@55e","trui@ss3","trui@sse","truia553","truia55e","truiass3","truiasse","v!3d@53","v!3d@s3","v!3da53","v!3das3","v!3r","v!d3-c0u!ll35","v!d3-c0u!ll3s","v!d3-cou!ll35","v!d3-cou!ll3s","v!de-c0u!lle5","v!de-c0u!lles","v!de-cou!lle5","v!de-cou!lles","v!éd@53","v!ed@5e","v!éd@5e","v!ed@s3","v!éd@s3","v!ed@se","v!éd@se","v!eda53","v!éda53","v!eda5e","v!éda5e","v!edas3","v!édas3","v!edase","v!édase","v!er","v@ur!3n","v@ur!en","v@ur13n","v@ur1en","v@uri3n","v@urien","v13d@53","v13d@s3","v13da53","v13das3","v13r","v1d3-c0u1ll35","v1d3-c0u1ll3s","v1d3-cou1ll35","v1d3-cou1ll3s","v1de-c0u1lle5","v1de-c0u1lles","v1de-cou1lle5","v1de-cou1lles","v1ed@53","v1éd@53","v1ed@5e","v1éd@5e","v1ed@s3","v1éd@s3","v1ed@se","v1éd@se","v1eda53","v1éda53","v1eda5e","v1éda5e","v1edas3","v1édas3","v1edase","v1édase","v1er","vaur!3n","vaur!en","vaur13n","vaur1en","vauri3n","vaurien","vi3d@53","vi3d@s3","vi3da53","vi3das3","vi3r","vid3-c0uill35","vid3-c0uill3s","vid3-couill35","vid3-couill3s","vide-c0uille5","vide-c0uilles","vide-couille5","vide-couilles","vied@53","viéd@53","vied@5e","viéd@5e","vied@s3","viéd@s3","vied@se","viéd@se","vieda53","viéda53","vieda5e","viéda5e","viedas3","viédas3","viedase","viédase","vier","x3r0p!n3ur","x3r0p1n3ur","x3r0pin3ur","x3rop!n3ur","x3rop1n3ur","x3ropin3ur","xer0p!n3ur","xér0p!n3ur","xer0p!neur","xér0p!neur","xer0p1n3ur","xér0p1n3ur","xer0p1neur","xér0p1neur","xer0pin3ur","xér0pin3ur","xer0pineur","xér0pineur","xerop!n3ur","xérop!n3ur","xerop!neur","xérop!neur","xerop1n3ur","xérop1n3ur","xerop1neur","xérop1neur","xeropin3ur","xéropin3ur","xeropineur","xéropineur","y0ud","y0up!n","y0up!n!5@t!0n","y0up!n!5at!0n","y0up!n!s@t!0n","y0up!n!sat!0n","y0up!n3","y0up!ne","y0up1n","y0up1n15@t10n","y0up1n15at10n","y0up1n1s@t10n","y0up1n1sat10n","y0up1n3","y0up1ne","y0upin","y0upin3","y0upine","y0upini5@ti0n","y0upini5ati0n","y0upinis@ti0n","y0upinisati0n","y0utr3","y0utre","y3ul3","yeule","youd","youp!n","youp!n!5@t!on","youp!n!5at!on","youp!n!s@t!on","youp!n!sat!on","youp!n3","youp!ne","youp1n","youp1n15@t1on","youp1n15at1on","youp1n1s@t1on","youp1n1sat1on","youp1n3","youp1ne","youpin","youpin3","youpine","youpini5@tion","youpini5ation","youpinis@tion","youpinisation","youtr3","youtre","zgu3gu3","zguegu3","zguègu3","zguegue","zguègue"]; --------------------------------------------------------------------------------