Advertisement
Guest User

Untitled

a guest
Feb 14th, 2018
1,509
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 20.94 KB | None | 0 0
  1. // ==UserScript==
  2. // @name AveNoel
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1
  5. // @description try to take over the world!
  6. // @author You
  7. // @match https://avenoel.org/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. //-----------------------------------------------------
  12. // TODO list
  13. //-----------------------------------------------------
  14. //Passer les commentaires avec les fleches du clavier
  15. //Ouvirir et fermer un commentaire
  16. //-----------------------------------------------------
  17.  
  18. //Profils
  19. var favProfiles;
  20. var banProfiles;
  21. var currentProfile;
  22. var colorListProfil = [
  23. "White",
  24. "LightBlue",
  25. "LightCoral",
  26. "LightCyan",
  27. "LightGoldenRodYellow",
  28. "LightGrey",
  29. "LightGreen",
  30. "LightPink",
  31. "LightSalmon",
  32. "LightSeaGreen",
  33. "LightSkyBlue",
  34. "LightSlateGrey",
  35. "LightSteelBlue"
  36. ];
  37.  
  38. //Topic
  39. var bans;
  40.  
  41. // Global
  42. var id = 0;
  43. const space = "&nbsp";
  44. const url = "https://avenoel.org";
  45. const imgQuote = "<img src=\"/images/topic/quote.png\" alt=\"Icône citation\">";
  46. const imgEdit = "<img src=\"/images/topic/edit.png\" alt=\"Icône éditer\" title=\"Éditer le message\">";
  47. const imgDelete = "<img src=\"/images/topic/delete.png\" alt=\"Icône suppression\">";
  48.  
  49. (function() {
  50. 'use strict';
  51.  
  52. var t0 = performance.now();
  53.  
  54. //debugger;
  55. //localStorage.removeItem("favs");
  56. //localStorage.removeItem("bans");
  57. //localStorage.removeItem("favProfiles");
  58. //localStorage.removeItem("banProfiles");
  59.  
  60. initCache();
  61.  
  62. var path = window.location.pathname;
  63.  
  64. if (path.startsWith('/profil')) {
  65. traiterProfil();
  66. } else {
  67. ajouterBarFav();
  68. if (path.startsWith('/forum')) {
  69. traiterForum();
  70. } else if (path.startsWith('/topic')) {
  71. traiterTopic();
  72. } else {
  73. console.log('Cas ' + path + " non traité.");
  74. }
  75. }
  76.  
  77. var t1 = performance.now();
  78. console.log("Call to AveNoel took " + (t1 - t0) + " milliseconds.");
  79.  
  80. })();
  81.  
  82. function initCache() {
  83. bans = localStorage.bans === undefined ? [] : JSON.parse(localStorage.bans);
  84. favProfiles = localStorage.favProfiles === undefined ? [] : JSON.parse(localStorage.favProfiles);
  85. banProfiles = localStorage.banProfiles === undefined ? [] : JSON.parse(localStorage.banProfiles);
  86. }
  87.  
  88. function ajouterBarFav() {
  89.  
  90. var idCourrier = getId();
  91. var idFavoris = getId();
  92. var barFav = document.getElementsByClassName("col-md-3 col-sm-12 col-xs-12 pull-right hidden-sm hidden-xs");
  93.  
  94.  
  95.  
  96. barFav[0].replaceChild(barFav[0].children[0].cloneNode(true), barFav[0].children[0]);
  97.  
  98. var listDiv = barFav[0].children[1];
  99. listDiv.classList = [];
  100. //listDiv.style.position = "fixed";
  101. //listDiv.style.top = "20px";
  102. listDiv.innerHTML = "<section>"+
  103. " <div id=\""+idCourrier+"\"></div>"+
  104. " <div id=\""+idFavoris+"\">Favoris</div>"+
  105. "</section>";
  106.  
  107. httpGetAsync("https://avenoel.org/messagerie", function (html) {
  108. var mails = html.getElementsByClassName("active");
  109.  
  110. if (mails.length > 2) {
  111. document.title = "(" + (mails.length-2) + ") " + document.title;
  112. var innerHTMLCourrier = "Courrier" + ((mails.length-2) > 1 ? "s" : "") + " (" + (mails.length-2) + ")";
  113.  
  114. for(var i = 1; i < mails.length -1; ++i) {
  115. innerHTMLCourrier += "<li>";
  116. innerHTMLCourrier += mails[i].getElementsByClassName("author")[0].innerHTML + " : ";
  117. innerHTMLCourrier += mails[i].getElementsByClassName("title")[0].innerHTML;
  118. innerHTMLCourrier += "</li>";
  119. }
  120.  
  121. document.getElementById(idCourrier).innerHTML = innerHTMLCourrier;
  122. }
  123. });
  124.  
  125. httpGetAsync("https://avenoel.org/favoris", function (html) {
  126. var list = html.getElementsByTagName("tBody")[0].children;
  127. var buttonBar = document.getElementById(idFavoris);
  128. var innerHTMLFav = "";
  129.  
  130. for(var i = 0; i < list.length; i++) {
  131. var fav = list[i];
  132.  
  133. fav.children[1].children[0].href = fav.children[0].children[0].getAttribute("href");
  134.  
  135. fav.removeChild(fav.children[2]);
  136. fav.removeChild(fav.children[2]);
  137. fav.removeChild(fav.children[2]);
  138. fav.removeChild(fav.children[0]);
  139. innerHTMLFav += "<li>" + fav.innerHTML + "</li>";
  140. }
  141.  
  142. document.getElementById(idFavoris).innerHTML += innerHTMLFav;
  143. });
  144.  
  145. }
  146.  
  147. //-----------------------------------------------------
  148. // Topic
  149. //-----------------------------------------------------
  150.  
  151. function traiterTopic() {
  152. var lstTopic = document.getElementsByClassName("topic-messages");
  153.  
  154. var trsTopic = lstTopic[0].children;
  155.  
  156. document.onkeydown = function(event) {
  157. if (event.key === "ArrowUp") {
  158. console.log(event);
  159. }
  160.  
  161. if (event.key === "ArrowDown") {
  162. document.scrollTop(trsTopic.children[3].offset().top);
  163. // $(this).parent().next() // this is the next div container.
  164. return false; // prevent anchor
  165. }
  166. };
  167.  
  168.  
  169. for(var iTopic = 0; iTopic < trsTopic.length; ++iTopic) {
  170. traiterTrTopic(trsTopic[iTopic]);
  171. collapseQuote(trsTopic[iTopic]);
  172. }
  173.  
  174. traiterNavBarTopic();
  175. }
  176.  
  177.  
  178. function traiterNavBarTopic() {
  179.  
  180. var relativePath = "";
  181. getPath().split("-").splice(2).forEach(function(split) {
  182. relativePath += split + "-";
  183. });
  184. relativePath = relativePath.slice(0, -1);
  185.  
  186. var buttons = addButtonToNavBar(["buttonBan"]);
  187. var buttonBan = buttons[0];
  188.  
  189. // Bouton des bans
  190. var isInBan = contentsString(bans, relativePath) != -1;
  191. if (isInBan) {
  192. buttonBan.innerHTML = "DEBAN";
  193. buttonBan.onclick = function() {
  194. deleteFromCache(bans, "bans");
  195. };
  196. } else {
  197. buttonBan.innerHTML = "BAN";
  198. buttonBan.onclick = function() {
  199. addInCache(bans, "bans");
  200. };
  201. }
  202.  
  203. }
  204.  
  205. function addInCache(cacheList, cacheName) {
  206. console.log("addInCache : " + cacheName);
  207. cacheList.push(getPath());
  208. localStorage.setItem(cacheName, JSON.stringify(cacheList));
  209. ajouterBarFav();
  210. traiterNavBarTopic();
  211. }
  212.  
  213. function deleteFromCache(cacheList, cacheName) {
  214. console.log("deleteFromCache : " + cacheName);
  215.  
  216. var relativePath = "";
  217. getPath().split("-").splice(2).forEach(function(split) {
  218. relativePath += split + "-";
  219. });
  220. relativePath = relativePath.slice(0, -1);
  221.  
  222. var i = contentsString(cacheList, relativePath);
  223. if (i !== -1) {
  224. cacheList.splice(i, 1);
  225. localStorage.setItem(cacheName, JSON.stringify(cacheList));
  226. }
  227.  
  228. ajouterBarFav();
  229. traiterNavBarTopic();
  230. }
  231.  
  232. function traiterTrTopic(tr) {
  233.  
  234. var message = null;
  235. var rank = -1;
  236.  
  237. // Suppression des FDP
  238. banProfiles.some(function(connard) {
  239.  
  240. var id = getId();
  241. message = null;
  242. if (hasProfilLink(tr, connard.name)) {
  243. message = "<div id=\"" + id + "\"><span>Réponse sur" + space + "</span><a href=\"https://avenoel.org/profil/" + connard.name + "\">" + connard.name + "</a></div>";
  244. }
  245. if (hasProfilAvatar(tr, connard.name)) {
  246. message = "<div id=\"" + id + "\"><span>Message de" + space + "</span><a href=\"https://avenoel.org/profil/" + connard.name + "\">" + connard.name + "</a></div>";
  247. }
  248.  
  249. if (message !== null) {
  250. rank = connard.rank;
  251. return true;
  252. }
  253.  
  254. return false;
  255. });
  256.  
  257. if (rank == 2) {
  258. tr.innerHTML = message;
  259. tr.focus();
  260. } else {
  261. addButtonHiddenTopicMessage(tr, rank == 1);
  262. }
  263. }
  264.  
  265. function collapseQuote(tr) {
  266.  
  267. var content = tr.getElementsByClassName("message-content")[0];
  268. if (!content) {
  269. return;
  270. }
  271.  
  272. var lines = content.innerHTML.match(/[^\r\n]+/g);
  273.  
  274. var ids = [];
  275. var currentLine;
  276. var lastRank = -1;
  277. var currentRank = -1;
  278. for(var i = lines.length - 1; i > -1; --i) {
  279. currentRank = calculRank(lines[i]);
  280.  
  281. if (lastRank > 0 && currentRank > lastRank) {
  282. lines.splice(i + 1, 0, "</div>");
  283. } else if (currentRank > 0 && currentRank < lastRank) {
  284. var idButton = getId();
  285. var idDiv = getId();
  286. var beforeButton = "";
  287. for(var iGt = 0; iGt < currentRank; ++iGt) {
  288. beforeButton += "&gt; ";
  289. }
  290. var maskInnerHTML = "<span class=\"message-content-quote\">" + beforeButton + "<i><button id=\"" + idButton + "\" ></button></i></span><div id=\"" + idDiv + "\" >";
  291. ids.push({idButton:idButton, idDiv:idDiv});
  292. lines.splice(i + 1, 0, maskInnerHTML);
  293. }
  294.  
  295. lastRank = currentRank;
  296. }
  297.  
  298. content.innerHTML = lines.join('');
  299. addCollapseEvent(tr, ids);
  300. }
  301.  
  302. function addCollapseEvent(tr, ids) {
  303. ids.forEach(function(id) {
  304. var button = document.getElementById(id.idButton);
  305. // button.style.backgroundColor = "Transparent";
  306. button.style.border = "none";
  307. button.innerHTML = ">";
  308. button.style.textAlign = "center";
  309. var div = document.getElementById(id.idDiv);
  310. div.style.display = "none";
  311.  
  312. button.onclick = function() {
  313. console.log(button.innerHTML );
  314. button.innerHTML = button.innerHTML === "&lt;" ? "&gt;" : "&lt;";
  315. div.style.display = div.style.display === "none" ? "" : "none";
  316. };
  317. });
  318. }
  319.  
  320. function calculRank(line) {
  321. var rank = -1;
  322. var index;
  323. var matcher = "quote\">&gt; ";
  324.  
  325. while (index != -1) {
  326. rank++;
  327. index = line.indexOf(matcher);
  328. matcher += "&gt; ";
  329. }
  330.  
  331. return rank;
  332. }
  333.  
  334. function addButtonHiddenTopicMessage(tr, isHidden) {
  335.  
  336. var aside = tr.getElementsByClassName("message-aside hidden-xs")[0];
  337. var content = tr.getElementsByClassName("message-content")[0];
  338. var header = tr.getElementsByClassName("message-header")[0];
  339. var ulHeader = header.getElementsByClassName("message-actions")[0];
  340. var footer = tr.getElementsByClassName("message-footer")[0];
  341.  
  342. var idButton = getId();
  343.  
  344. content.isHidden = isHidden;
  345. var innerButton = content.isHidden ? "Afficher" : "Masquer";
  346. ulHeader.innerHTML = "<button id=\"" + idButton + "\" >" + innerButton + "</button>" + ulHeader.innerHTML;
  347.  
  348. var imageUp = "<li><img src=\"https://img-fi-n2.akamaized.net/icons/svg/53/53604.svg\"></li>";
  349. var imageDown = "<li><img src=\"https://img-fi-n2.akamaized.net/icons/svg/53/53598.svg\" alt=\"Icône citation\"></li>";
  350.  
  351. var button = document.getElementById(idButton);
  352.  
  353. button.style.backgroundColor = "Transparent";
  354. button.style.border = "none";
  355.  
  356. button.onclick = function() {
  357. content.isHidden = !content.isHidden;
  358. button.innerHTML = content.isHidden ? imageUp : imageDown;
  359. content.style.display = content.isHidden ? "none" : "";
  360. footer.style.display = content.isHidden ? "none" : "";
  361. aside.style.display = content.isHidden ? "none" : "";
  362. };
  363. content.isHidden = !content.isHidden;
  364. button.onclick.apply();
  365. }
  366.  
  367. //-----------------------------------------------------
  368. // Forum
  369. //-----------------------------------------------------
  370.  
  371.  
  372. function traiterForum() {
  373. var lst = document.getElementsByClassName("table table-striped topics");
  374. var trs = lst[0].children[1].children;
  375. for(var i = 0; i < trs.length; ++i) {
  376. traiterTrForum(trs[i]);
  377. }
  378. }
  379.  
  380. function traiterTrForum(tr) {
  381.  
  382. // Suppression des caracteres ching chong
  383. if (tr.innerText.match(/[\u3400-\u9FBF]/)) {
  384. console.log("Suppression des caracteres ching chong");
  385. tr.innerHTML = "";
  386. return;
  387. }
  388.  
  389. // Suppression des FDP
  390. banProfiles.some(function(connard) {
  391. if (hasProfilLink(tr, connard.name)) {
  392. console.log("Suppression du FDP : " + connard.name);
  393. tr.innerHTML = "";
  394. return true;
  395. }
  396. return false;
  397. });
  398.  
  399. bans.some(function(url) {
  400. if (hasUrl(tr, url)) {
  401. console.log("Suppression du sujet : " + url);
  402. tr.innerHTML = "";
  403. return true;
  404. }
  405. return false;
  406. });
  407.  
  408. // Surlignage
  409. favProfiles.some(function(surligne) {
  410. if (hasProfilLink(tr, surligne.name)) {
  411. console.log("Surlignage : " + surligne.name);
  412. tr.style.background = colorListProfil[surligne.color];
  413. return true;
  414. }
  415. return false;
  416. });
  417.  
  418. }
  419.  
  420. //-----------------------------------------------------
  421. // Profil
  422. //-----------------------------------------------------
  423.  
  424. function traiterProfil() {
  425. var elem = document.getElementsByClassName("profile-wrapper-right");
  426. currentProfile = window.location.href.substring("https://avenoel.org/profil/".length);
  427.  
  428. elem[0].innerHTML += "Banni de rang <select id=\"idBanRank\"></select><div id=\"idBanRankDetail\"></div>";
  429. elem[0].innerHTML += "Favoris couleur <select id=\"idFavColor\"></select>";
  430.  
  431. // favoris ----------------------------------------------------------
  432. var isFav = false;
  433. var color = 0;
  434. favProfiles.some(function(fav) {
  435. if (fav.name === currentProfile) {
  436. isFav = true;
  437. color = fav.color;
  438. return true;
  439. }
  440. return false;
  441. });
  442.  
  443. var colorList = [];
  444. for (var iColor = 0; iColor < colorListProfil.length; iColor++) {
  445. colorList.push(iColor);
  446. }
  447.  
  448. createComboBox("idFavColor", colorList);
  449. var comboFavColor = document.getElementById("idFavColor");
  450.  
  451. for (var j = 0; j < comboFavColor.length; j++) {
  452. comboFavColor[j].innerHTML = "";
  453. comboFavColor[j].style.backgroundColor = colorListProfil[j];
  454. }
  455.  
  456. if (isFav) {
  457. comboFavColor.selectedIndex = parseInt(color);
  458. comboFavColor.style.backgroundColor = colorListProfil[color];
  459. }
  460. detailRankProfile(rank);
  461. comboFavColor.onchange = function(event) {
  462. favProfile(event.target.selectedOptions[0].value);
  463. };
  464.  
  465. // bans ----------------------------------------------------------
  466. var isBan = false;
  467. var rank = 0;
  468. banProfiles.forEach(function(connard) {
  469. if (connard.name === currentProfile) {
  470. isBan = true;
  471. rank = connard.rank;
  472. return;
  473. }
  474. });
  475.  
  476. createComboBox("idBanRank", ["non","0","1","2"]);
  477. var comboBanRank = document.getElementById("idBanRank");
  478. if (isBan) {
  479. comboBanRank.selectedIndex = parseInt(rank) + 1;
  480. }
  481. detailRankProfile(rank);
  482. comboBanRank.onchange = function(event) {
  483. banProfile(event.target.selectedOptions[0].value);
  484. };
  485.  
  486. }
  487.  
  488. function favProfile(color) {
  489. console.log("favProfile : " + currentProfile + ", color : " + colorListProfil[color]);
  490.  
  491. var index = -1;
  492. for(var i = 0; i < favProfiles.length; ++i) {
  493. if (stringContents(favProfiles[i].name, currentProfile)) {
  494. index = i;
  495. break;
  496. }
  497. }
  498.  
  499. if (index !== -1) {
  500. favProfiles.splice(index, 1);
  501. localStorage.setItem("favProfiles", JSON.stringify(favProfiles));
  502. }
  503.  
  504. if (color !== "0") {
  505. favProfiles.push({name:currentProfile, color:color});
  506. localStorage.setItem("favProfiles", JSON.stringify(favProfiles));
  507. }
  508.  
  509. document.getElementById("idFavColor").style.backgroundColor = colorListProfil[color];
  510. }
  511.  
  512. function banProfile(rank) {
  513. console.log("banProfile : " + currentProfile + ", rang : " + rank);
  514.  
  515. var index = -1;
  516. for(var i = 0; i < banProfiles.length; ++i) {
  517. if (stringContents(banProfiles[i].name, currentProfile)) {
  518. index = i;
  519. break;
  520. }
  521. }
  522.  
  523. if (index !== -1) {
  524. banProfiles.splice(index, 1);
  525. localStorage.setItem("banProfiles", JSON.stringify(banProfiles));
  526. }
  527.  
  528. if (rank !== "non") {
  529. banProfiles.push({name:currentProfile, rank:rank});
  530. localStorage.setItem("banProfiles", JSON.stringify(banProfiles));
  531. }
  532.  
  533. detailRankProfile(rank);
  534. }
  535.  
  536. function detailRankProfile(rank) {
  537. var innerHTML;
  538. if (rank === "0") {
  539. innerHTML = "Ses sujets sont supprimés.";
  540. } else if (rank === "1") {
  541. innerHTML = "Ses sujets sont supprimés et ses messages sont masqués.";
  542. } else if (rank === "2") {
  543. innerHTML = "Ses sujets et messages sont supprimés.";
  544. } else {
  545. innerHTML = "Non banni.";
  546. }
  547.  
  548. document.getElementById("idBanRankDetail").innerHTML = innerHTML;
  549. }
  550.  
  551. //-----------------------------------------------------
  552. // Utils
  553. //-----------------------------------------------------
  554.  
  555. function createImageButton(idButton, buttonHtml) {
  556. var button = document.getElementById(idButton);
  557. button.innerHTML = buttonHtml;
  558. return button;
  559. }
  560.  
  561. function createComboBox(idParent, list) {
  562. var sel = document.getElementById(idParent);
  563. var optionoption = null;
  564.  
  565. for(i = 0; i < list.length; i++) {
  566.  
  567. optionoption = document.createElement('option');
  568. optionoption.value = list[i];
  569. optionoption.innerHTML = list[i];
  570. sel.appendChild(optionoption);
  571. }
  572. return sel;
  573. }
  574.  
  575. function httpGetAsync(theUrl, callback)
  576. {
  577. var xmlHttp = new XMLHttpRequest();
  578. xmlHttp.onreadystatechange = function() {
  579. if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
  580. callback(new DOMParser().parseFromString(xmlHttp.responseText, "text/html"));
  581. };
  582. xmlHttp.open("GET", theUrl, true); // true for asynchronous
  583. xmlHttp.send(null);
  584. }
  585.  
  586. function contentsString(list, match) {
  587. for(var i = 0; i < list.length; ++i) {
  588. if (stringContents(list[i], match)) {
  589. return i;
  590. }
  591. }
  592. return -1;
  593. }
  594.  
  595. function getPath() {
  596. return url + window.location.pathname;
  597. }
  598.  
  599. function designTopButton(button) {
  600. button.style.color = "white";
  601. button.style.backgroundColor = "transparent";
  602. button.style.border = "none";
  603. }
  604.  
  605. function hasProfilLink(elem, name) {
  606. return stringContents(elem.innerHTML, "https://avenoel.org/profil/" + name + "\"");
  607. }
  608.  
  609. function hasProfilAvatar(elem, name) {
  610. return stringContents(elem.innerHTML, "alt=\"Avatar de "+ name + "\"");
  611. }
  612.  
  613. function hasUrl(elem, url) {
  614. return stringContents(elem.innerHTML, "<a href=\"" + url + "\">");
  615. }
  616.  
  617. function stringContents(string, match) {
  618. return string.indexOf(match) !== -1;
  619. }
  620.  
  621. function addButtonToNavBar(names) {
  622.  
  623. var buttons = [];
  624.  
  625. if (document.getElementById(names[0]) === null) {
  626. var navbar = document.getElementsByClassName("nav navbar-nav navbar-links");
  627. var innerHTML = "";
  628. names.forEach(function(name) {
  629. innerHTML += "<li class=\"\"><a><button id=\"" + name + "\" ></button></a></li>";
  630. });
  631. navbar[0].innerHTML += innerHTML;
  632. names.forEach(function(name) {
  633. designTopButton(document.getElementById(name));
  634. });
  635. }
  636.  
  637. names.forEach(function(name) {
  638. buttons.push(document.getElementById(name));
  639. });
  640.  
  641. return buttons;
  642. }
  643.  
  644. function getId() {
  645. return "id" + id++;
  646. }
  647.  
  648. //-----------------------------------------------------
  649. // Patch
  650. //-----------------------------------------------------
  651. //
  652. // 2.0.5 : Pastebin => Etape 3
  653. // + ajout du nombre de courriers non lus sur l'onglet
  654. // + possibilité d'afficher/masquer les citiations (masquées par défaut)
  655. // 2.0.4 : Pastebin => https://pastebin.com/4mCdppfi
  656. // + affichage de la liste des favoris dans les topics noirs
  657. // + correction bug sur les BL
  658. // + optimisation des listes
  659. // 2.0.3 : Pastebin =>https://pastebin.com/nPvLrBbV
  660. // + correction du bug d'affichage des courriers
  661. // 2.0.2 : Pastebin => https://pastebin.com/haqi3XRa
  662. // + Modification du bouton pour afficher/masquer
  663. // 2.0.1 : Pastebin => https://pastebin.com/vNmgUrCs
  664. // + Le clique sur le favoris emmene à la dernière page
  665. // 2.0.0 : Pastebin => https://pastebin.com/eHCt5h8E
  666. // + Prise en compte de la liste de favoris (affichage reste à droite)
  667. // L'icone du fichier n'est pas encore mise
  668. // + Changement du bouton afficher/masquer un commentaire.
  669. // 1.1.0 : Pastebin => https://pastebin.com/85F6ydsC
  670. // + gestion de la lovelist sur les profils
  671. // + plus besoin d'aller dans le code pour gérer ses listes
  672. // 1.0.3 : Pastebin => https://pastebin.com/RNWPdyyF
  673. // + suppression de 'Courriers' quand la liste est vide
  674. // + gestion de la banlist sur les profils
  675. // 1.0.2 : https://pastebin.com/TJzUnw69
  676. // + ajout de la liste des messages non lus au niveau de la barre des favs
  677. // 1.0.1 : https://pastebin.com/mFv7z7wb
  678. // + correction du bug des bans de topics
  679. // + detection du favoris/ban à chaque page du topic
  680. // 1.0.0 : https://pastebin.com/bjVmYYgM
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement