xRock / JVChat+ Quiz

// ==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()
})