Guest User

Untitled

a guest
Mar 5th, 2020
310
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 16.25 KB | None | 0 0
  1. // ==UserScript==
  2. // @name [HFR] Stats
  3. // @namespace ddst.github.io
  4. // @version 0.0.2
  5. // @description Afficher les statistiques d'un membre
  6. // @author DdsT
  7. // @URL https://ddst.github.io/HFR_Stats/
  8. // @downloadURL https://ddst.github.io/HFR_Stats/hfrstats.user.js
  9. // @updateURL https://ddst.github.io/HFR_Stats/hfrstats.meta.js
  10. // @icon https://forum.hardware.fr/favicon.ico
  11. // @match *://forum.hardware.fr/forum2.php*
  12. // @require https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js
  13. // ==/UserScript==
  14.  
  15. /*
  16. Copyright (C) 2020 DdsT
  17.  
  18. This program is free software: you can redistribute it and/or modify
  19. it under the terms of the GNU Affero General Public License as published
  20. by the Free Software Foundation, either version 3 of the License, or
  21. (at your option) any later version.
  22.  
  23. This program is distributed in the hope that it will be useful,
  24. but WITHOUT ANY WARRANTY; without even the implied warranty of
  25. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  26. GNU Affero General Public License for more details.
  27.  
  28. You should have received a copy of the GNU Affero General Public License
  29. along with this program. If not, see https://ddst.github.io/HFR_Stats/LICENSE.
  30. */
  31.  
  32. /************** TODO *****************
  33. * beaucoup
  34. *************************************/
  35.  
  36. this.$ = this.jQuery = jQuery.noConflict(true);
  37.  
  38. const VERSION = GM.info.script.version;
  39. const ROOT = document.getElementById("mesdiscussions");
  40. const POST = $("input[name='post']").attr("value");
  41. const CAT = $("input[name='cat']").attr("value");
  42. const TOPIC = CAT & POST;
  43. const TITLE = $(".fondForum2Title").find("h3").text();
  44.  
  45. /********************
  46. * MODULE INTERFACE *
  47. ********************/
  48.  
  49. const CHART_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGvSURBVDjLpZO7alZREEbXiSdqJJDKYJNCkPBXYq12prHwBezSCpaidnY+graCYO0DpLRTQcR3EFLl8p+9525xgkRIJJApB2bN+gZmqCouU+NZzVef9isyUYeIRD0RTz482xouBBBNHi5u4JlkgUfx+evhxQ2aJRrJ/oFjUWysXeG45cUBy+aoJ90Sj0LGFY6anw2o1y/mK2ZS5pQ50+2XiBbdCvPk+mpw2OM/Bo92IJMhgiGCox+JeNEksIC11eLwvAhlzuAO37+BG9y9x3FTuiWTzhH61QFvdg5AdAZIB3Mw50AKsaRJYlGsX0tymTzf2y1TR9WwbogYY3ZhxR26gBmocrxMuhZNE435FtmSx1tP8QgiHEvj45d3jNlONouAKrjjzWaDv4CkmmNu/Pz9CzVh++Yd2rIz5tTnwdZmAzNymXT9F5AtMFeaTogJYkJfdsaaGpyO4E62pJ0yUCtKQFxo0hAT1JU2CWNOJ5vvP4AIcKeao17c2ljFE8SKEkVdWWxu42GYK9KE4c3O20pzSpyyoCx4v/6ECkCTCqccKorNxR5uSXgQnmQkw2Xf+Q+0iqQ9Ap64TwAAAABJRU5ErkJggg==";
  50. /* Icons by Mark James - http://www.famfamfam.com/lab/icons/silk/ - CC BY 2.5 - https://creativecommons.org/licenses/by/2.5/ */
  51.  
  52. const STYLE = `
  53. #hfr-stats-container {
  54. font-size : 10px;
  55. position : fixed;
  56. left : 0;
  57. right : 0;
  58. top : 0;
  59. bottom : 0;
  60. display : none;
  61. z-index : 9999;
  62. }
  63.  
  64. #hfr-stats-modal {
  65. margin : auto auto;
  66. background : white;
  67. padding : 10px;
  68. box-shadow : 0 4px 8px 0 rgba(0, 0, 0, 0.2),
  69. 0 6px 20px 0 rgba(0, 0, 0, 0.19);
  70. transition : opacity 0.7s ease 0s;
  71. pointer-events : auto;
  72. }
  73.  
  74. #hfr-stats-tooltip {
  75. padding : 10px;
  76. background : rgba(0,0,0,0.8);
  77. color : #bbb;
  78. font-size : 12px;
  79. position : absolute;
  80. z-index : 99999;
  81. text-align : center;
  82. border-radius : 3px;
  83. display : none;
  84. }
  85.  
  86. #hfr-stats-tooltip:after {
  87. -moz-box-sizing : border-box;
  88. box-sizing : border-box;
  89. position : absolute;
  90. left : 50%;
  91. height : 5px;
  92. width : 5px;
  93. bottom : -10px;
  94. margin : 0 0 0 -5px;
  95. content : " ";
  96. border : 5px solid transparent;
  97. border-top-color : rgba(0,0,0,0.8);
  98. }
  99.  
  100. #hfr-stats-progress {
  101. width : 400px;
  102. background-color : #eee;
  103. margin : 0 auto;
  104. display : none;
  105. }
  106.  
  107. #hfr-stats-bar {
  108. width : 0%;
  109. height : 30px;
  110. background-color : #8cc665;
  111. }
  112.  
  113. #hfr-stats-results {
  114. display : none;
  115. }
  116. `;
  117.  
  118. /* Remplace GM.addstyle pour des soucis de compatibilité */
  119. function addStyle(aCss) {
  120. let head = document.getElementsByTagName('head')[0];
  121. if (head) {
  122. let style = document.createElement('style');
  123. style.setAttribute('type', 'text/css');
  124. style.textContent = aCss;
  125. $(head).append($(style));
  126. }
  127. }
  128.  
  129. let statsContainer = document.createElement("div");
  130. statsContainer.id = "hfr-stats-container";
  131. statsContainer.show = () => {
  132. statsContainer.style.display = "flex";
  133. };
  134. statsContainer.hide = () => {
  135. statsContainer.style.display = "none";
  136. results.hide();
  137. };
  138. $(ROOT).append(statsContainer);
  139.  
  140. /* Cache la fenêtre lors d'un clic extérieur */
  141. document.addEventListener("click", (event) => {
  142. const targetClass = event.target.classList[0];
  143. if (targetClass != "hfr-stats" && !event.target.closest("#hfr-stats-modal"))
  144. statsContainer.hide();
  145. });
  146.  
  147. let statsModal = document.createElement("div");
  148. statsModal.id = "hfr-stats-modal";
  149. $(statsContainer).append($(statsModal));
  150.  
  151. let progress = document.createElement("div");
  152. progress.id = "hfr-stats-progress";
  153. progress.show = () => {
  154. progress.style.display = "block";
  155. statsContainer.show();
  156. };
  157. progress.hide = () => {
  158. progress.style.display = "none";
  159. bar.setProgress(0);
  160. };
  161. let bar = document.createElement("div");
  162. bar.id = "hfr-stats-bar";
  163. bar.setProgress = (progress) => {bar.style.width = progress + "%";};
  164. $(statsModal).append($(progress));
  165. $(progress).append($(bar));
  166.  
  167. let results = document.createElement("div");
  168. results.id = "hfr-stats-results";
  169. results.show = () => {
  170. results.style.display = "block";
  171. statsContainer.show();
  172. summary.update();
  173. };
  174. results.hide = () => {
  175. results.style.display = "none";
  176. };
  177. $(statsModal).append($(results));
  178.  
  179. let summary = document.createElement("div");
  180. summary.id = "hfr-stats-summary";
  181. summary.update = () => {
  182. let html = "";
  183. let begin = new Date(formattedData.begin);
  184. let end = new Date(formattedData.end);
  185. let messageDay = formattedData.total / (end- begin) * 1000*60*60*24;
  186. let subMessageDay = formattedData.subtotal / (query.end - query.begin) * 1000*60*60*24;
  187. html += `<h2>Statistiques de ${query.pseudo}</h2><h3>${TITLE}</h3><p>`;
  188. html += `<b>${formattedData.total}</b> Messages postés entre le ${getDisplayDate(begin)} et le ${getDisplayDate(end)}, soit <b>${messageDay.toFixed(2)}</b> messages/jour.<br>`;
  189. html += `<b>${formattedData.subtotal}</b> Messages postés entre le ${getDisplayDate(query.begin)} et le ${getDisplayDate(query.end)}, soit <b>${subMessageDay.toFixed(2)}</b> messages/jour.</p>`;
  190. $(summary).html(html);
  191. };
  192. $(results).append($(summary));
  193.  
  194. /* Ajouter le bouton de statistiques à la barre d'outil d'un message */
  195. function decorate(message) {
  196. let toolbar = $(message).find(".toolbar div").get(0);
  197. let button = document.createElement("img");
  198. let pseudo = $(message).find("b.s2").text();
  199. button.src = CHART_ICON;
  200. button.className = "hfr-stats";
  201. button.title = `Statistiques de ${pseudo} pour ce sujet`;
  202. button.style.cursor = "pointer";
  203. button.pseudo = pseudo;
  204. button.onclick = buttonClick;
  205. $(toolbar).append($(button));
  206. }
  207.  
  208. function buttonClick() {
  209. launchQuery(this.pseudo);
  210. progress.show();
  211. }
  212.  
  213. function launchQuery(pseudo) {
  214. let end = new Date();
  215. let begin = new Date();
  216. begin.setMonth(begin.getMonth() - 12);
  217. update(pseudo, begin, end);
  218. }
  219.  
  220. /********************
  221. * MODULE GRAPHIQUE *
  222. ********************/
  223.  
  224. /* Afficher les résultats */
  225. function showResult() {
  226. let yearChart = newChart({data: formattedData, begin: query.begin, end: query.end});
  227. if (results.yearChart) results.yearChart.remove();
  228. results.yearChart = yearChart;
  229. yearChart.id = "hfr-stats-chart";
  230. results.appendChild(yearChart);
  231. results.show();
  232. progress.hide();
  233. }
  234.  
  235. /* Infobulle du graphique */
  236. let tooltip = document.createElement("div");
  237. tooltip.id = "hfr-stats-tooltip";
  238. ROOT.appendChild(tooltip);
  239. function mouseLeave(evt) {
  240. tooltip.style.display ="none";
  241. }
  242. function mouseEnter(evt) {
  243. let targetOffset = $(evt.target).offset();
  244. let count = $(evt.target).attr('data-count');
  245. let date = $(evt.target).attr('data-date');
  246. let countText = ( count > 1 ) ? "messages" : "message";
  247. tooltip.style.display ="block";
  248. $(tooltip).html(`${count} ${countText} le ${date}`);
  249.  
  250. let svgWidth = Math.round($(tooltip).width() / 2 + 5 ) ;
  251. let svgHeight = $(tooltip).height() * 2 + 10 ;
  252.  
  253. $(tooltip).css({top:targetOffset.top - svgHeight - 5});
  254. $(tooltip).css({left:targetOffset.left - svgWidth});
  255. }
  256.  
  257. /* Créer un graphique d'activité
  258. * Code adapté du projet Github-Contribution-Graph par bachvtuan
  259. * https://github.com/bachvtuan/Github-Contribution-Graph
  260. */
  261. function newChart(options) {
  262.  
  263. let div = document.createElement("div");
  264.  
  265. let settings = $.extend({
  266. colors: ['#eeeeee','#d6e685','#8cc665','#44a340','#44a340'],
  267. threshold: [0 ,1 ,5 ,10 ,20 ],
  268. monthNames: ['Jan','Fev','Mar','Avr','Mai','Jui','Jui','Aoû','Sep','Oct','Nov','Dec'],
  269. dayNames : ["Lu","Ma","Me","Je","Ve","Sa","Di"],
  270. data:{}
  271. }, options );
  272.  
  273. let dateList = settings.data.dateList;
  274.  
  275. let getCount = function(displayDate) {
  276. return (dateList[displayDate]) ? dateList[displayDate] : 0;
  277. }
  278.  
  279. let getColor = function(count) {
  280. let i = 0;
  281. while (i < settings.threshold.length && count >= settings.threshold[i]) ++i;
  282. return settings.colors[i-1];
  283. }
  284.  
  285. if (!settings.end) settings.end = new Date();
  286. let end = new Date(settings.end);
  287. if (!settings.begin) {
  288. settings.begin = new Date();
  289. settings.begin.setMonth(settings.begin.getMonth() - 12);
  290. }
  291. let date = new Date(settings.begin);
  292. let dayOffset = date.getDay();
  293. if (dayOffset != 1) {
  294. //Si le premier jour de la plage considérée n'est pas un lundi, commencer au lundi précédent
  295. date.setDate( date.getDate() + 1 - dayOffset);
  296. }
  297. let currentMonth = date.getMonth();
  298. let html = "";
  299. let size = 13;
  300. let monthIndex = [{index: date.getMonth(), x: 0 }];
  301. let xOffset = 0;
  302.  
  303. for (let week = 0; date <= end; ++week) {
  304. //chaque semaine est parcourue jusqu'à arriver à la fin de la plage considérée
  305. html += `<g transform="translate(${xOffset.toString()},0)">`;
  306.  
  307. let yOffset = 0;
  308. for (let day = 0; (day < 7 && date <= end); ++day) {
  309.  
  310. let month = date.getMonth();
  311. let displayDate = getDisplayDate(date);
  312.  
  313. if ( day == 0 && month != currentMonth ){
  314. //En cas de nouveau mois, noter son emplacement
  315. currentMonth = month;
  316. monthIndex.push({index: currentMonth, x: xOffset });
  317. }
  318.  
  319. let count = getCount(displayDate);
  320. let color = getColor(count);
  321. if (dayOffset > 1) {
  322. //Les premiers jours du graphique sont ignorés s'ils ne sont pas dans la plage temporelle considérée
  323. html += `<rect width="${size - 2}" height="${size - 2}" y="${yOffset}" fill="white"/>`;
  324. --dayOffset;
  325. } else {
  326. html += `<rect class="hfr-stats-day" width="${size - 2}" height="${size - 2}" y="${yOffset}" fill="${color}" data-count="${count}" data-date="${displayDate}"/>`;
  327. }
  328.  
  329. //la date est incrémentée d'un jour
  330. date.setDate( date.getDate() + 1 );
  331. yOffset += size;
  332. }
  333. xOffset += size;
  334. html += "</g>";
  335. }
  336.  
  337. if (monthIndex[1].x - monthIndex[0].x < 40) {
  338. //Retirer le label du premier mois en cas de chevauchement
  339. monthIndex.shift(0);
  340. }
  341.  
  342. //Ajout des mois
  343. for (const month of monthIndex){
  344. html += `<text x="${month.x}" y="-5" class="month">${settings.monthNames[month.index]}</text>'`;
  345. }
  346.  
  347. //Ajout des jours de la semaine
  348. for (const [i,text] of settings.dayNames.entries()) {
  349. html += `<text text-anchor="middle" class="wday" dx="-10" dy="${size * (i + 1) - 4}">${text}</text>`;
  350. }
  351.  
  352. html = `<svg width="721" height="110"><g transform="translate(20, 20)">${html}</g></svg>`;
  353.  
  354. $(div).html(html);
  355. $(div).find('.hfr-stats-day').on("mouseenter", mouseEnter);
  356. $(div).find('.hfr-stats-day').on("mouseleave", mouseLeave);
  357.  
  358. return div;
  359. };
  360.  
  361. /******************
  362. * MODULE DONNÉES *
  363. ******************/
  364.  
  365. /* Liste contenant l'ensemble des messages du membre */
  366. let messageList = [];
  367.  
  368. /* Liste des messages utilisés pour les visualisations */
  369. let formattedData = {};
  370.  
  371. /* Décomposer un message en un objet facilement manipulable */
  372. function parse(message) {
  373. let parsedMessage = {
  374. id : null, // ID du message
  375. date : null, // date de création du message
  376. editDate : null, // dernière date d'édition du message
  377. quoteNumber : null, // nombre de fois que le message a été cité
  378. quotedMember : [], // liste des membres cités par le message
  379. smileys : [], // liste des smileys présents dans le message
  380. body : null // corps du message dépouillé des éléments non textuels
  381. };
  382.  
  383. const dateMatched = $(message).find(".toolbar").text().match(/(\d\d)-(\d\d)-(\d\d\d\d).*(\d\d:\d\d:\d\d)/);
  384. if (dateMatched.length >= 5) {
  385. parsedMessage.date = Date.parse(`${dateMatched[3]}-${dateMatched[2]}-${dateMatched[1]} ${dateMatched[4]}`);
  386. }
  387.  
  388. return parsedMessage;
  389. }
  390.  
  391. /* Mettre en forme les données dans la plage temporelle considérée pour la visualisation */
  392. function formatData() {
  393. formattedData = {
  394. dateList : {},
  395. total : messageList.length,
  396. subtotal : 0,
  397. begin : messageList[0].date,
  398. end : messageList[messageList.length-1].date
  399. };
  400. for (const timestamp of messageList) {
  401. let timestampDate = new Date(timestamp.date);
  402. if (timestampDate >= query.begin && timestampDate <= query.end) {
  403. ++formattedData.subtotal;
  404. let displayDate = getDisplayDate(timestampDate);
  405. if (!formattedData.dateList[displayDate]) {
  406. formattedData.dateList[displayDate] = 1;
  407. } else {
  408. ++formattedData.dateList[displayDate];
  409. }
  410. }
  411. }
  412. }
  413.  
  414. /********************
  415. * MODULE RECHERCHE *
  416. ********************/
  417.  
  418. /* Requête utilisée pour récupérer les messages d'un membre */
  419. let query = {
  420. pseudo : "",
  421. currentnum : 0,
  422. get url() {
  423. return `https://forum.hardware.fr/forum2.php?post=${POST}&cat=${CAT}&spseudo=${fixPseudo(query.pseudo)}&currentnum=${query.currentnum}&filter=1`;
  424. }
  425. }
  426.  
  427. /* Mettre à jour les statistiques d'un membre */
  428. function update(pseudo, begin, end) {
  429. query.pseudo = pseudo;
  430. query.currentnum = 0;
  431. query.begin = begin;
  432. query.end = end;
  433. messageList = [];
  434. search();
  435. }
  436.  
  437. /* Effectuer une recherche filtrée sur un pseudo */
  438. function search() {
  439. $.get(query.url, {}, parseSearch);
  440. }
  441.  
  442. /* Traiter la page obtenue lors d'une recherche filtrée sur un pseudo */
  443. function parseSearch(data) {
  444. let searchPage = $.parseHTML(data);
  445. let searchTable = $(searchPage).find(".messagetable");
  446. for (let i = 0; i < searchTable.length; ++i) {
  447. let parsedMessage = parse(searchTable.get(i));
  448. messageList.push(parsedMessage);
  449. }
  450. let currentnum = $(searchPage).find("input[name='currentnum']").get(0)
  451. let today = Date.parse(new Date());
  452. let progress = (messageList[messageList.length-1].date - messageList[0].date) / (today - messageList[0].date) * 100;
  453. bar.setProgress(progress);
  454. if (currentnum) {
  455. //Une page contient au maximum 200 messages du membre, le numéro du dernier message est utilisé pour voir les messages suivants
  456. query.currentnum = currentnum.value;
  457. search();
  458. } else {
  459. formatData();
  460. showResult();
  461. }
  462. }
  463.  
  464. /*********************
  465. * MODULE TRANSVERSE *
  466. *********************/
  467.  
  468. function fixPseudo(str) {
  469. return encodeURIComponent(str.replace(/\u200b/g, "").toLowerCase()).replace(/[!'()*]/g, function(c) {
  470. return '%' + c.charCodeAt(0).toString(16);
  471. });
  472. }
  473.  
  474. function getDisplayDate(date) {
  475. return date.toLocaleString("fr-fr").replace(/ à .*/,"");
  476. }
  477.  
  478. /***************************
  479. * INTIALISATION DU SCRIPT *
  480. ***************************/
  481.  
  482. addStyle(STYLE);
  483. // Ajout des icones aux barres d'outil
  484. $(".messagetable").each(function() {
  485. decorate(this);
  486. });
Advertisement
Add Comment
Please, Sign In to add comment