NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name JVChat+ Quiz // @namespace JVChat+ Quiz // @version 1.13.2 // @description Permet aux organisateurs de quiz de faciliter le comptage de points et l'envoi du classement / PMQ // @author xRock // @match https://*.jeuxvideo.com/forums/42-* // @match https://*.jeuxvideo.com/forums/1-* // @updateURL https://openuserjs.org/install/xRock/JVChat+_Quiz.meta.js // @license MIT // ==/UserScript== const CSS = `<style type="text/css" id="jvchatplus-css">input[id^=jvchat-input]:focus,input[id^=slotq]:focus{outline:solid 2px #ffc926}span[class^=remove-user]:hover{opacity:1}#btn-recap{margin-top:.3125rem}#content.jvchat-night-mode .custom-btn-upload{background-color:#484c52;color:#fff}#content.jvchat-night-mode .custom-btn-upload:focus{outline:0}#content.jvchat-night-mode .custom-btn-upload:hover{background-color:#6c727b}input[type=file]{display:none}span[class^=remove-user]:before{transform:rotate(45deg)}span[class^=remove-user]:after{transform:rotate(-45deg)}#jvchat-clmt:focus,input[id^=ptsq]:focus{outline:solid 2px #ffc926}#jvchat-classement-info,#jvchat-quiz-info{margin-bottom:2px}#content.jvchat-night-mode #btn-addq,#content.jvchat-night-mode #btn-clmt,#content.jvchat-night-mode #btn-pmq,#content.jvchat-night-mode #btn-pmqimage,#content.jvchat-night-mode #btn-question,#content.jvchat-night-mode #btn-recap,#content.jvchat-night-mode .btn-copyq,#content.jvchat-night-mode .btn-copyr,#content.jvchat-night-mode .btn-qactuel,#content.jvchat-night-mode .btn-qmoins,#content.jvchat-night-mode .btn-qplus,#jvchat-mark-secondes-input{background-color:#484c52;color:#fff}#content.jvchat-night-mode #btn-addq:focus,#content.jvchat-night-mode #btn-clmt:focus,#content.jvchat-night-mode #btn-pmq:focus,#content.jvchat-night-mode #btn-pmqimage:focus,#content.jvchat-night-mode #btn-question:focus,#content.jvchat-night-mode #btn-recap:focus,#content.jvchat-night-mode .btn-copyq:focus,#content.jvchat-night-mode .btn-copyr:focus,#content.jvchat-night-mode .btn-qactuel,#content.jvchat-night-mode .btn-qmoins:focus,#content.jvchat-night-mode .btn-qplus:focus,#jvchat-mark-secondes-input{outline:0}#content.jvchat-night-mode #btn-addq:hover,#content.jvchat-night-mode #btn-clmt:hover,#content.jvchat-night-mode #btn-pmq:hover,#content.jvchat-night-mode #btn-pmqimage:hover,#content.jvchat-night-mode #btn-question:hover,#content.jvchat-night-mode #btn-recap:hover,#content.jvchat-night-mode .btn-copyq:hover,#content.jvchat-night-mode .btn-copyr:hover,#content.jvchat-night-mode .btn-qmoins:hover,#content.jvchat-night-mode .btn-qplus:hover,#jvchat-mark-secondes-input{background-color:#6c727b}#content.jvchat-night-mode input[id^=jvchat-input],#content.jvchat-night-mode input[id^=ptsq],#content.jvchat-night-mode input[id^=slotq],#jvchat-mark-secondes-input{background-color:#484c52;color:#fff}#jvchat-leftbar{width:250px}#jvchat-leftbar .panel-jv-forum{max-width:calc(15rem + 10px)}#jvchat-mark-secondes-input,input[id^=jvchat-input],input[id^=slotq]{color:#000;border:1px solid #2a2a2a;padding:5px;width:65%;height:1.5rem}input[id^=ptsq]{color:#000;border:1px solid #2a2a2a;max-width:21%;height:1.5rem;text-align:center}span[class^=remove-user]{position:absolute;right:7%;margin-top:3px;width:8px;height:8px;opacity:.3}span[class^=remove-user]:after,span[class^=remove-user]:before{position:absolute;content:' ';height:20px;width:5px;background-color:red}.custom-btn-upload{width:100%;background:#fff;color:#000;border:1px solid #2a2a2a;text-align:center}#upload-section{width:100%}#btn-addq,#btn-clmt,#btn-pmq,#btn-pmqimage,#btn-question,#btn-recap{width:100%;background:#fff;color:#000;border:1px solid #2a2a2a}.btn-qactuel{width:22%}.btn-copyq,.btn-copyr{width:30%}.btn-qmoins,.btn-qplus{width:9%;text-align:center;display:inline-block}.btn-copyq,.btn-copyr,.btn-qactuel,.btn-qmoins,.btn-qplus{background:#fff;color:#000;border:1px solid #2a2a2a}</style>` const HTML = `<div id="jvchat-quiz"><h4 class="titre-info-fofo">Quiz</h4> <div id="jvchat-quiz-info"> <div class="top-btn-question"> <button id="btn-question">Baliser la question</button> </div><div class="top-btn-upload"> <label for="btn-upload" class="custom-btn-upload">Uploader les questions</label> <input id="btn-upload" type="file" text="Clique salope"> </div></div></div><div id="jvchat-classement"> <h4 class="titre-info-fofo">Classement</h4> <div id="jvchat-classement-info"> <div class="top-btn-clmt"> <button id="btn-clmt">Copier le classement</button> </div><div class="top-btn-pmq"> <button id="btn-pmq">Copier le "PMQ"</button> </div><div class="top-btn-pmqimage"> <button id="btn-pmqimage">Copier le "PMQ image"</button> </div><div class="top-btn-addq"> <button id="btn-addq">Ajouter un joueur</button> </div></div><div id="jvchat-clmt"> <div id="jvchat-classement2"></div></div></div>` const options = `<div class="jvchat-config-option" id="jvchat-copy-repclmt"> <label> <input id="jvchat-copy-repclmt-checkbox" type="checkbox"> <span id="jvchat-copy-repclmt-span">Copier réponse et classement</span> </label> <p>Permet de copier le classement et la réponse en appuyant sur "REP" (pour ceux qui uploadent les questions).</p></div><div class="jvchat-config-option" id="jvchat-focus-zone"> <label> <input id="jvchat-focus-zone-checkbox" type="checkbox"> <span id="jvchat-focus-zone-span">Focus la zone d'envoi</span> </label> <p>Permet de focus la zone de texte dès qu'on a envoyé un message.</p></div><div class="jvchat-config-option" id="jvchat-mark-secondes"> <label> <input id="jvchat-mark-secondes-checkbox" type="checkbox"> <span id="jvchat-mark-secondes-span">Coloriser l'heure des messages postés</span> <input id="jvchat-mark-secondes-input" placeholder="15 par défaut"></input> </label> <p>Permet de coloriser les messages postés dans les Xs suivant les votres.</p></div><div class="jvchat-config-option" id="jvchat-balise-pmq"> <label> <span>Balise des PMQ</span> </label> <div class='jvchat-pmqbal-option'> <input id="jvchat-input-pmqbal" placeholder=":mac: par défaut"></input> </div><p>Permet de changer les smileys (balises) utilisés lorsqu'on copie le PMQ ou le PMQ image.</p></div><div class="jvchat-config-option" id="jvchat-balise-pmq"> <label> <span>Balise des classements</span> </label> <div class='jvchat-clmtbal-option'> <input id="jvchat-input-clmtbal" placeholder=":cd: par défaut"></input> </div><p>Permet de changer les smileys (balises) utilisés lorsqu'on copie le classement.</p></div><div class="jvchat-config-option" id="jvchat-text-clmt"> <label> <span>Texte avec le classement</span> </label> <div class='jvchat-textclmt-option'> <input id="jvchat-input-textclmt"></input> </div><p>Permet d'ajouter un texte personnalisé à chaque copie du classement.</p></div>` // Remplace des longues fonctions par des versions courtes const did = (selector) => { return document.getElementById(selector) } const gebcn = (selector) => { return document.getElementsByClassName(selector) } // Déclaration des variables let slotpvar = 0 let balisepmq, baliseclmt const storageKey = "jvchat-quiz-configuration" let configuration = { balisepmq: ":mac:", baliseclmt: ":cd:", messageperso: "", repclmt: false, focus: true, markcheck: true, marksec: 15 } // Sauvegarde de la configuration const saveConfig = () => { const config = JSON.stringify(configuration) localStorage.setItem(storageKey, config) } // Chargement de la configuration const loadConfig = () => { let config = JSON.parse(localStorage.getItem(storageKey) || "{}") for (const key in config) { if (config.hasOwnProperty(key) && configuration.hasOwnProperty(key)) { configuration[key] = config[key] } } did("jvchat-input-pmqbal").value = configuration.balisepmq did("jvchat-input-clmtbal").value = configuration.baliseclmt did("jvchat-input-textclmt").value = configuration.messageperso did("jvchat-copy-repclmt-checkbox").checked = configuration.repclmt did("jvchat-focus-zone-checkbox").checked = configuration.focus did("jvchat-mark-secondes-checkbox").checked = configuration.markcheck did("jvchat-mark-secondes-input").value = configuration.marksec } // Insertion du HTML et CSS dans la page const htmlcss = () => { did("jvchat-forum").insertAdjacentHTML("afterend", HTML) did("jvchat-max-width").insertAdjacentHTML("afterend", options) document.head.insertAdjacentHTML("beforeend", CSS) }; // Fonction principale du script const main = () => { // Bouton "Copier le classement" did("btn-clmt").addEventListener("click", () => { let varclmt = "" let varlist = [] const queryPlayers = did("jvchat-clmt").querySelectorAll(`div[class^="player"]`) for (const player of queryPlayers) { varlist.push([player.childNodes[0].value, player.childNodes[1].value]) } varlist.sort((a, b) => b[1] - a[1]) for (const vara of varlist) { varclmt += `${vara[0]} : ${vara[1]} poin${(vara[1] === "1") || (vara[1] === "0") ? "t" : "ts"}\n` } did("message_topic").value += `\n\n${configuration.baliseclmt} Classement ${configuration.baliseclmt}\n\n${varclmt}\n\n${configuration.messageperso ? "\n\n" + configuration.messageperso : ""}` did("message_topic").focus() }) // Bouton "Ajouter un joueur" did("btn-addq").addEventListener("click", () => { const jvchatClmtFin = did("jvchat-clmt").lastElementChild jvchatClmtFin.insertAdjacentHTML('afterend', `<div class="player${slotpvar}"><input id="slotq${slotpvar}" spellcheck="false"></input><input id="ptsq${slotpvar}"><span title="Retirer ce joueur" class="remove-user${slotpvar}"></span></div>`) slotpvar += 1 // Bouton "Supprimer un joueur" const remUserList = document.querySelectorAll("span[class^=remove-user]") const lastRemUserButton = remUserList[remUserList.length - 1] lastRemUserButton.addEventListener("click", () => { const lastRemUserButtonIndex = remUserList[remUserList.length - 1].getAttribute("class").replace(/^\D+/g, '') if (confirm(`Voulez-vous supprimer ${did("slotq" + lastRemUserButtonIndex).value ? did("slotq" + lastRemUserButtonIndex).value : "cette case vide"} du classement ?`)) { const playerToRem = "player" + lastRemUserButtonIndex gebcn(playerToRem)[0].remove() } }) }) // Boutons "Copier le PMQ" / "Copier le PMQ image" / "Baliser la question" did("btn-pmqimage").addEventListener("click", () => { did("message_topic").value += `\n\n${configuration.balisepmq} PMQ IMAGE ${configuration.balisepmq}` did("message_topic").focus() }) did("btn-pmq").addEventListener("click", () => { did("message_topic").value += `\n\n${configuration.balisepmq} PMQ ${configuration.balisepmq}` did("message_topic").focus() }) did("btn-question").addEventListener("click", () => { did("message_topic").value = `:globe: ${did("message_topic").value} :globe:` did("message_topic").focus() }) // Bouton upload un fichier de questions did("btn-upload").addEventListener("change", () => { const selectedFile = did("btn-upload").files[0] const reader = new FileReader() let arrayQuestions reader.onload = () => { arrayQuestions = reader.result .replace(new RegExp(":globe: ", "g"), "") .replace(new RegExp("Question", "g"), "") .replace(new RegExp(" :globe:", "g"), "") .replace(new RegExp(/\d\d\/\d\d |\d\/\d\d /, 'g'), "") .replace(new RegExp(/""/, 'g'), "") .split(/\r?\n/) .filter(Boolean) let mapQuestions = new Map() // Merci Hassen pour l'aide sur la map :hassen: let qa let separator = prompt("Tapez le chiffre correspondant à votre type de fichier :\n1. Fichier excel sauvegardé en .txt\n2. Fichier normal séparé par des \\") if (separator === "1") { arrayQuestions.forEach((question, i) => { qa = question.split(" ") const questRep = { "question": qa[0], "reponse": qa[1] } mapQuestions.set(i, questRep) }) } else if (separator === "2") { arrayQuestions.forEach(function (question, i) { qa = question.split(/\\/) const questRep = { "question": qa[0], "reponse": qa[1] } mapQuestions.set(i, questRep) }) } else { alert("Tu dois choisir 1 ou 2.") reader.abort() return } const uploadBtn = gebcn("top-btn-upload")[0] if (gebcn("btn-copyq")[0]) { did("upload-section").remove() } let currentQuestion = 1 uploadBtn.insertAdjacentHTML('afterend', `<div id="upload-section"><button class="btn-copyq">QST</button><button class="btn-copyr">REP</button><button class="btn-qmoins"><</button><button class="btn-qactuel">0${currentQuestion}/${arrayQuestions.length < 10 ? "0" : ""}${arrayQuestions.length}</button><button class="btn-qplus">></button><button id="btn-recap">Créer le récap</button></div>`) // Limites max et minimium pour le choix du numéro de question gebcn("btn-qmoins")[0].addEventListener("click", () => { if (currentQuestion === 1) { alert("Vous ne pouvez pas descendre à la question 0 :hap:") } else { currentQuestion -= 1 gebcn("btn-qactuel")[0].firstChild.data = `${currentQuestion < 10 ? "0" : ""}${currentQuestion}/${arrayQuestions.length < 10 ? "0" : ""}${arrayQuestions.length}` } }) gebcn("btn-qplus")[0].addEventListener("click", () => { if (currentQuestion === arrayQuestions.length) { alert(`Vous ne pouvez pas monter au dessus de la question ${arrayQuestions.length}`) } else { currentQuestion += 1 gebcn("btn-qactuel")[0].firstChild.data = `${currentQuestion < 10 ? "0" : ""}${currentQuestion}/${arrayQuestions.length < 10 ? "0" : ""}${arrayQuestions.length}` } }) // Bouton "Copier la question" gebcn("btn-copyq")[0].addEventListener("click", () => { let questionToGet = currentQuestion - 1 let questionToCopy = mapQuestions.get(questionToGet).question did("message_topic").value += `Question ${currentQuestion < 10 ? "0" : ""}${currentQuestion}/${arrayQuestions.length < 10 ? "0" : ""}${arrayQuestions.length}\n:globe: ${questionToCopy} :globe:` did("message_topic").focus() }) // Bouton "Copier la réponse" gebcn("btn-copyr")[0].addEventListener("click", () => { let answerToGet = currentQuestion - 1 let answerToCopy = mapQuestions.get(answerToGet).reponse did("message_topic").value += `Réponse: ${answerToCopy}` if (configuration.repclmt) { let varclmt = "" let varlist = [] const queryPlayers = did("jvchat-clmt").querySelectorAll(`div[class^="player"]`) for (const player of queryPlayers) { varlist.push([player.childNodes[0].value, player.childNodes[1].value]) } varlist.sort((a, b) => b[1] - a[1]) for (const vara of varlist) { varclmt += `${vara[0]} : ${vara[1]} poin${(vara[1] === "1") || (vara[1] === "0") ? "t" : "ts"}\n` } did("message_topic").value += `\n\n${configuration.baliseclmt} Classement ${configuration.baliseclmt}\n\n` + varclmt + `${configuration.messageperso ? `\n\n${configuration.messageperso}` : ""}` } did("message_topic").focus() }) // Bouton "Créer le récap" did("btn-recap").addEventListener("click", () => { if (confirm("Voulez-vous copier le récapitulatif des questions et réponses ?")) { let recap = "Récapitulatif :\n\n<spoil>" for (var q = 1; q < mapQuestions.size + 1; q++) { recap += `Question ${q < 10 ? "0" : ""}${q}/${mapQuestions.size} :globe: ${mapQuestions.get(q - 1).question} :globe:\nRéponse: ${mapQuestions.get(q - 1).reponse}\n\n` } recap += "</spoil>" did("message_topic").value += recap did("message_topic").focus() } }) } reader.readAsText(selectedFile, 'ISO-8859-15') }) // On change la configuration quand quelque chose est saisi dans les inputs des balises pmq / classement et le message personnalisé did("jvchat-input-pmqbal").addEventListener("input", () => { configuration.balisepmq = did("jvchat-input-pmqbal").value saveConfig() }) did("jvchat-input-clmtbal").addEventListener("input", () => { configuration.baliseclmt = did("jvchat-input-clmtbal").value saveConfig() }) did("jvchat-input-textclmt").addEventListener("input", () => { configuration.messageperso = did("jvchat-input-textclmt").value saveConfig() }) did("jvchat-copy-repclmt").addEventListener("change", () => { configuration.repclmt = did("jvchat-copy-repclmt-checkbox").checked saveConfig() }) did("jvchat-focus-zone").addEventListener("change", () => { configuration.focus = did("jvchat-focus-zone-checkbox").checked saveConfig() }) did("jvchat-mark-secondes-checkbox").addEventListener("change", () => { configuration.markcheck = did("jvchat-mark-secondes-checkbox").checked saveConfig() }) did("jvchat-mark-secondes-input").addEventListener("input", () => { configuration.marksec = did("jvchat-mark-secondes-input").value saveConfig() }) // Coloriser l'heure des posts après x secondes // Focus la zone de texte let heuresCurrent = [] addEventListener("jvchat:newmessage", function (event) { if (configuration.markcheck) { let temps = configuration.marksec let currentUser = did("jvchat-user-pseudo").textContent let message = document.querySelector(`.jvchat-message[jvchat-id="${event.detail.id}"]`) if (!message.querySelector(".jvchat-date").textContent.includes("/")) { let messageAuthor = message.querySelector(".jvchat-author").textContent let messageHeure = message.querySelector(".jvchat-date") let messageHeureText = message.querySelector(".jvchat-date").textContent.replace(/\*/g, "").split(":") let heureTotale = parseInt(messageHeureText[0] * 60) + parseInt(messageHeureText[1] * 60) + parseInt(messageHeureText[2]) if (messageAuthor === currentUser) { heuresCurrent.push(heureTotale) } else { for (let i = 0; i < heuresCurrent.length; i++) { if (heureTotale - heuresCurrent[i] <= temps && heureTotale - heuresCurrent[i] >= 0) { let messageHeureAfter = messageHeure.outerHTML.replace(`to-quote=`, `ident=\"${heureTotale}\" to-quote=`) const CSS2 = `<style type="text/css" id="jvchatplus-css-seconds">.jvchat-date[ident="${heureTotale}"] { color: blue; }</style>` document.head.insertAdjacentHTML("beforeend", CSS2) messageHeure.innerHTML = messageHeureAfter } } } } } if (configuration.focus) { let currentUser = did("jvchat-user-pseudo").textContent let message = document.querySelector(`.jvchat-message[jvchat-id="${event.detail.id}"]`) let messageAuthor = message.querySelector(".jvchat-author").textContent if (currentUser === messageAuthor) { document.querySelector("textarea#message_topic.area-editor.js-focus-field.jvchat-textarea").focus() document.querySelector("textarea#message_topic.area-editor.js-focus-field.jvchat-textarea").click() } } }) // Une pop-up s'affiche si l'utilisateur tente de refresh window.onbeforeunload = (evt) => { const message = 'Êtes-vous sûr de vouloir quitter la page ? Le classement de JVChat+ Quiz ne sera pas sauvegardé' if (typeof evt == 'undefined') { evt = window.event } if (evt) { evt.returnValue = message } return message } } // Chargement du script addEventListener("jvchat:activation", () => { htmlcss() main() loadConfig() })