NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name PigeonLibre // @description Statistique sur les achats PayUTC // @author Benjamin BONVARLET // @author Paul LESUR // @match https://payutc.nemopay.net/* // @run-at document-end // @require https://code.jquery.com/jquery-3.3.1.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js // @version 1.8.3 // @licence MIT // ==/UserScript== // Initialisation et chargement des données utilisateurs if(localStorage.getItem("maxClassement") == undefined) { localStorage.setItem("maxClassement", 10); } if(localStorage.getItem("stopList") == undefined) { localStorage.setItem("stopList", JSON.stringify(["Rechargement"])); } let debut, fin; let maxClassement = localStorage.getItem("maxClassement"); let stopList = JSON.parse(localStorage.getItem("stopList")); var totalSpend = 0; var rechargementTotal = 0; /* On récupère la totalité des articles et on les traites pour obtenir un tableau d'objets avec les informations suivantes name : String quantity : Int date : Date type : String price : Float */ let items = []; $("tr", $("table")).each((key, tr) => { let parsedItem = $(".expand", tr).text().replace(/ {2,}/g, " ").replace(/\n /g, "").split(" "); let formattedDate = $("td", tr).first().text().replace(/ +/g, "").replace("\n", "").substr(0, 8); let parsedDate = new Date("20" + formattedDate.split("/")[2] + "-" + formattedDate.split("/")[1] + "-" + formattedDate.split("/")[0]); if(parsedItem[parsedItem.length - 1] == "") { parsedItem = parsedItem.slice(0, -1); } let item = { quantity : ($(tr).attr("class") === "cancelled") ? 0 : parseInt(parsedItem[0]), name : parsedItem.slice(1).join(" "), date : parsedDate, cancelled : $(tr).attr("class") === "cancelled" } let parsedDebit = $(".debit", tr).text().replace(/.*?(\d+,\d+).*/, "$1").replace(/\n */g, "").replace(",", "."); let parsedCredit = $(".credit", tr).text().replace(/.*?(\d+,\d+).*/, "$1").replace(/\n */g, "").replace(",", "."); if(parsedDebit != "") { item.type = "debit"; item.price = ($(tr).attr("class") === "cancelled") ? 0 : parseFloat(parsedDebit); } else { item.type = "credit"; item.price = ($(tr).attr("class") === "cancelled") ? 0 : parseFloat(parsedCredit); } items.push(item); }); /* Permet le filtre du tableau des articles entre deux dates */ if(localStorage.getItem("debut") == undefined) { debut = +items[items.length - 1].date; } else { debut = localStorage.getItem("debut"); } if(localStorage.getItem("fin") == undefined) { fin = +new Date(); } else { fin = localStorage.getItem("fin"); } items = items.filter((a) => +a.date >= +debut && +a.date <= +fin); var totalSpend = 0; var rechargementTotal = 0; var hashMapItemsQuantity = {}; var hashMapItemsPrice = {}; var hashMapItemsCredit = {}; items.forEach((item) => { /* Création d'un dictionnaire Item -> Quantité Total */ if($.inArray(item.name, stopList) === -1) { if(hashMapItemsQuantity[item.name] === undefined) { hashMapItemsQuantity[item.name] = 0; } hashMapItemsQuantity[item.name] += item.quantity; } /* Création d'un dictionnaire Item -> Prix Total Débit */ if($.inArray(item.name, stopList) === -1 && item.type === "debit") { if(hashMapItemsPrice[item.name] === undefined) { hashMapItemsPrice[item.name] = 0; } hashMapItemsPrice[item.name] += item.price; } /* Création d'un dictionnaire Item -> Prix Total Crédit */ else if($.inArray(item.name, stopList) === -1 && item.type === "credit") { if(hashMapItemsCredit[item.name] === undefined) { hashMapItemsCredit[item.name] = 0; } hashMapItemsCredit[item.name] += item.price; } /* Récupération des dépenses total */ if(item.type === "debit") { totalSpend += item.price; } else if(item.type === "credit") { if(item.quantity < 0) { totalSpend -= item.price; } else { rechargementTotal += item.price; } } }); /* Création d'un tableau trié par rapport à la quantité total d'un article */ var sortedItemsQuantity = Object.keys(hashMapItemsQuantity).sort((a, b) => hashMapItemsQuantity[b] - hashMapItemsQuantity[a]); /* Création d'un tableau trié par rapport au prix total d'un article */ var sortedItemsPrice = Object.keys(hashMapItemsPrice).sort((a, b) => hashMapItemsPrice[b] - hashMapItemsPrice[a]); /* Création d'un tableau trié par rapport au credit total d'un article */ var sortedItemsCredit = Object.keys(hashMapItemsCredit).sort((a, b) => hashMapItemsCredit[b] - hashMapItemsCredit[a]); /* Création de l'UI composé de : - Un onglet classement, avec des informations en rapport avec les données que nous avons. - Un onglet de configuration pour paramétrer PigeonLibre. */ $("h2").eq(3).after('<ul class="nav nav-tabs"><li class="active"><a id="historiqueTab">Historique</a></li><li><a id="consommation">Classement</a></li><li><a id="configuration">Configuration</a></li></ul>'); $("table#historique").parent().attr("id", "historiqueDiv"); $("#historiqueTab").on("click", () => { if($("#historiqueTab").parent().attr("class") != "active") { $(".active").first().attr("class", ""); $("#historiqueTab").parent().attr("class", "active"); $("#historiqueDiv").show(); $("#configurationDiv").hide(); $("#consommationDiv").hide(); } }); $("#consommation").on("click", () => { if($("#consommation").parent().attr("class") != "active") { $(".active").first().attr("class", ""); $("#consommation").parent().attr("class", "active"); $("#historiqueDiv").hide(); $("#configurationDiv").hide(); $("#consommationDiv").show(); } }); $("#configuration").on("click", () => { if($("#configuration").parent().attr("class") != "active") { $(".active").first().attr("class", ""); $("#configuration").parent().attr("class", "active"); $("#historiqueDiv").hide(); $("#consommationDiv").hide(); $("#configurationDiv").show(); } }); let classementDiv = $(`<div id="consommationDiv"> <h3>Information concernant vos articles</h3> <style> .myCard{ padding: 10px; } .myCard-content{ text-align:center; padding:10px; min-height: 150px; border-radius:5px; color:white; } .myCard-green{ background: rgb(81,187,63); background: linear-gradient(0deg, rgba(25,171,0,1) 0%, rgba(81,187,63,1) 100%); } .myCard-red{ background: rgb(81,187,63); background: linear-gradient(0deg, rgba(171,0,0,1) 0%, rgba(187,63,63,1) 100%); } .myCard-purple{ background: rgb(187,63,150); background: linear-gradient(0deg, rgba(187,63,150,1) 0%, rgba(113,0,171,1) 100%); } .myCard-blue{ background: rgb(74,63,187); background: linear-gradient(0deg, rgba(74,63,187,1) 0%, rgba(0,171,156,1) 100%); } </style> <div class="row"> <div class="col-md-3 myCard"> <div class="myCard-content myCard-green"> <h4> <i class="glyphicon glyphicon-euro glyphicon-white"></i> </h4> <h5>Rechargements</h5> <h4><span id="rechargementTotal"></span>€</h4> </div> </div> <div class="col-md-3 myCard"> <div class="myCard-content myCard-red"> <h4> <i class="glyphicon glyphicon-shopping-cart glyphicon glyphicon-white"></i> </h4> <h5>Dépenses</h5> <h4><span id="total"></span>€</h4> </div> </div> <div class="col-md-3 myCard"> <div class="myCard-content myCard-purple"> <h4> <i class="glyphicon glyphicon-heart glyphicon glyphicon-white"></i> </h4> <h5>Le plus consommé</h5> <h4><span id="mostQuantity"></span></h4> </div> </div> <div class="col-md-3 myCard"> <div class="myCard-content myCard-blue"> <h4> <i class="glyphicon glyphicon-fire glyphicon glyphicon-white"></i> </h4> <h5>Le plus onéreux</h5> <h4><span id="mostPrice"></span></h4> </div> </div> </div> <h3>Classement des articles par quantité</h3> <div id="quantityChartDiv"> <canvas id="quantityChart"></canvas> </div> <h3>Classement des articles par débit</h3> <div id="priceChartDiv"> <canvas id="priceChart"></canvas> </div> <h3>Classement des articles par crédit</h3> <div id="creditChartDiv"> <canvas id="creditChart"></canvas> </div> </div>`); let configurationDiv = $(`<div style="display: none;" id="configurationDiv"> <h3>Filtre de début et de fin du classement</h3> Début : <input class="form-control" id="debut" name="debut" placeholder="YYYY-MM-DD" type="text"> <br> Fin : <input class="form-control" id="fin" name="fin" placeholder="YYYY-MM-DD" type="text"> <h3>Filtre des articles du classement</h3> <style> .subject-info-box-1, .subject-info-box-2 { float: left; width: 45%; } .subject-info-arrows { float: left; width: 10%; input { width: 70%; margin-bottom: 5px; } } #itemList, #stopList { height: 400px; } </style> Nombre d'articles dans le classement : <input name="maxClassement" type="number" id="maxClassement" placeholder="0" class="form-control amount-selector" min="0" value="" step="1"> <br> <div class="subject-info-box-1"> Filtrer certains articles : <select multiple="multiple" id='itemList' class="form-control"> </select> <br> </div> <div class="subject-info-arrows text-center"> <br> <input type="button" id="addStopList" value=">" class="btn btn-default" /><br /><br> <input type="button" id="removeStopList" value="<" class="btn btn-default" /><br /> <br> </div> <div class="subject-info-box-2"> <br> <select multiple="multiple" id='stopList' class="form-control"> </select> <br> </div> <div style="text-align: center;"> <input type="button" id="validate" value="Valider" class="btn btn-default" /> </div> <br> Pour signaler un bug ou me faire part de suggestion : <a href="mailto:benjamin.bonvarlet@etu.utc.fr">cliquez-ici</a>. </div>`) $("#historiqueDiv").after(classementDiv); $("#quantityChartDiv").css("height", (25 * maxClassement) + "px"); $("#priceChartDiv").css("height", (27 * maxClassement) + "px"); $("#creditChartDiv").css("height", (32 * maxClassement) + "px"); $("#total").text(totalSpend.toFixed(2)); $("#mostQuantity").text(sortedItemsQuantity[0]); $("#mostPrice").text(sortedItemsPrice[0]); $("#mostPriceValue").text(hashMapItemsPrice[sortedItemsQuantity[0]].toFixed(2)); $("#rechargementTotal").text(rechargementTotal.toFixed(2)); function getRandomInt(max) { return Math.floor(Math.random() * Math.floor(max)); } let colors = []; for(var i = 0 ; i < maxClassement ; i++) { colors.push('rgba(' + getRandomInt(255) + ',' + getRandomInt(255) + ',' + getRandomInt(255) + ')'); } let ctxQuantityChart = $("#quantityChart"); // Data let dataQuantity = { datasets: [{ data: sortedItemsQuantity.slice(0, maxClassement).map((item) => hashMapItemsQuantity[item]), backgroundColor: colors }], // These labels appear in the legend and in the tooltips when hovering different arcs labels: sortedItemsQuantity.slice(0, maxClassement) }; // For a pie chart let quantityChart = new Chart(ctxQuantityChart,{ type: 'pie', data: dataQuantity, options : { maintainAspectRatio: false, } }); let ctxPriceChart = $("#priceChart"); // Data let dataPrice = { datasets: [{ data: sortedItemsPrice.slice(0, maxClassement).map((item) => hashMapItemsPrice[item]).map((item) => item.toFixed(2)), backgroundColor: colors }], // These labels appear in the legend and in the tooltips when hovering different arcs labels: sortedItemsPrice.slice(0, maxClassement) }; // For a pie chart let priceChart = new Chart(ctxPriceChart,{ type: 'pie', data: dataPrice, options : { maintainAspectRatio: false, } }); let ctxCreditChart = $("#creditChart"); // Data let dataCredit = { datasets: [{ data: sortedItemsCredit.slice(0, maxClassement).map((item) => hashMapItemsCredit[item]).map((item) => item.toFixed(2)), backgroundColor: colors }], // These labels appear in the legend and in the tooltips when hovering different arcs labels: sortedItemsCredit.slice(0, maxClassement) }; // For a pie chart let creditChart = new Chart(ctxCreditChart,{ type: 'pie', data: dataCredit, options : { maintainAspectRatio: false, } }); $("#historiqueDiv").next().after(configurationDiv); if(localStorage.getItem("debut") != undefined) { let debutDate = new Date(parseInt(localStorage.getItem("debut"))); $("#debut").val(debutDate.getFullYear() + "-" + (debutDate.getMonth() + 1) + "-" + debutDate.getDate()); } if(localStorage.getItem("fin") != undefined) { let debutDate = new Date(parseInt(localStorage.getItem("fin"))); $("#fin").val(debutDate.getFullYear() + "-" + (debutDate.getMonth() + 1) + "-" + debutDate.getDate()); } $("#maxClassement").attr("max", sortedItemsQuantity.length); $("#maxClassement").val(maxClassement); $("#validate").on("click", () => { localStorage.setItem("maxClassement", $("#maxClassement").val()); let newStopList = []; $("option", $("#stopList")).each((key, val) => newStopList.push($(val).val())); localStorage.setItem("stopList", JSON.stringify(newStopList)); if($("#debut").val() === "") { localStorage.removeItem("debut"); } else { localStorage.setItem("debut", +new Date($("#debut").val())); } if($("#fin").val() === "") { localStorage.removeItem("fin"); } else { localStorage.setItem("fin", +new Date($("#fin").val())); } location.href = "https://payutc.nemopay.net/"; }); let whiteList = Object.keys(hashMapItemsQuantity).sort(); whiteList.forEach((item) => $("#itemList").append("<option>" + item + "</option>")); stopList.forEach((item) => $("#stopList").append("<option>" + item + "</option>")); $("#addStopList").on("click", () => { $("option:selected", $("#itemList")).each((key, val) => $("#stopList").append("<option>" + $(val).val() + "</option>")); $("option:selected", $("#itemList")).remove(); }); $("#removeStopList").on("click", () => { $("option:selected", $("#stopList")).each((key, val) => $("#itemList").append("<option>" + $(val).val() + "</option>")); $("option:selected", $("#stopList")).remove(); }); $("#configurationDiv").hide(); $("#consommationDiv").hide();