Disqus Click Automate

Automate repetitive clicks on those sites that use Disqus as commenting system.

  1. // ==UserScript==
  2. // @grant GM_setValue
  3. // @grant GM_getValue
  4. // @grant GM_registerMenuCommand
  5. // @name Disqus Click Automate
  6. // @author aaferrari@eml.cc, updated by flatline4400@gmail.com (thanks woxxom at greasyfork)
  7. // @namespace Disqus Click Automate
  8. // @description Automate repetitive clicks on those sites that use Disqus as commenting system.
  9. // @include *://*.disqus.com/*
  10. // @include *://disqus.com/*
  11. // @version 0.7
  12. // ==/UserScript==
  13.  
  14. // This open a dialog to customize some parameters of the script, add the click events in
  15. // the respective radioboxs, checkboxes and set the way that this dialogue disappears from
  16. // the page when the user wants to close
  17. function config_dialog(){
  18. var i18n = {
  19. "en": {
  20. "SORTING_LABEL": "Sort comments by",
  21. "RECENT_LABEL": "Most recent",
  22. "BEST_LABEL": "The best",
  23. "OLDEST_LABEL": "Oldest",
  24. "AUTOLOAD_LABEL": "Autoload all comments",
  25. "EXPAND_LABEL": "Expand shortened comments",
  26. "GUEST_LABEL": "I am not interested in log in/create an account, I just want to comment",
  27. "CLOSE_TITLE": "Close",
  28. "AUTOLOAD_TITLE": "Load all comments available automatically instead of showing the 50 first",
  29. "EXPAND_TITLE": 'Expand automatically shortened comment containing the link "See more"',
  30. "GUEST_TITLE": 'Check the option "I prefer commenting as guest" and hidden the password field on the comment form'
  31. },
  32. "es": {
  33. "SORTING_LABEL": "Ordenar comentarios por",
  34. "RECENT_LABEL": "Mas nuevos",
  35. "BEST_LABEL": "El mejor",
  36. "OLDEST_LABEL": "Mas antiguos",
  37. "AUTOLOAD_LABEL": "Autocargar todos los comentarios",
  38. "EXPAND_LABEL": "Expandir comentarios acortados",
  39. "GUEST_LABEL": "No me interesa iniciar sesión/crear una cuenta, solo quiero comentar",
  40.  
  41. "CLOSE_TITLE": "Cerrar",
  42. "AUTOLOAD_TITLE": "Carga automáticamente todos los comentarios disponibles en vez de mostrar los 50 primeros",
  43. "EXPAND_TITLE": 'Expande de forma automática los comentarios acortados que contienen el enlace "Ver más"',
  44. "GUEST_TITLE": 'Marca la opcion "Prefiero comentar como invitado" y oculta el campo de contraseña en el formulario para comentar'
  45. }
  46. };
  47.  
  48. function translate(string){
  49. var lang = navigator.userLanguage || navigator.language;
  50. if (i18n[lang]) {
  51. if (i18n[lang][string]) { return i18n[lang][string]; }
  52. }
  53. return i18n["en"][string];
  54. }
  55.  
  56. // Changed the inline html to Javascript DOM (special thanks to
  57. // http://rick.measham.id.au/paste/html2dom.htm for saving so much work) to
  58. // avoid problems with the Content-Security-Policy on pages that have embedded
  59. // the Disqus comments, as well as other sites that also possess these policies.
  60. configDialogStyle = document.createElement('style');
  61. configDialogStyle.id = "config-dialog-style";
  62. configDialogStyle.appendChild(document.createTextNode("\
  63. #div_Content{position: fixed;\
  64. top: 50%; left: 50%;\
  65. transform: translate(-50%, -50%);\
  66. -ms-transform:translate(-50%, -50%); /* IE 9 */\
  67. webkit-transform:translate(-50%, -50%); /* Safari and Chrome */\
  68. z-index: 999; background-color: #ffffff;\
  69. border-color: black; border: 2px solid #000;}\
  70. #div_Content:focus{display:none;}\
  71. .dsq-options li {float: left; margin: 0px 5px;}"));
  72. document.body.appendChild(configDialogStyle);
  73.  
  74. div_Content = document.createElement('div');
  75. div_Content.id = "div_Content";
  76.  
  77. var table_0 = document.createElement('table');
  78. table_0.style.textAlign = "left";
  79.  
  80. var tbody_0 = document.createElement('tbody');
  81.  
  82. var tr_0 = document.createElement('tr');
  83. tr_0.style.color = "white";
  84.  
  85. var td_0 = document.createElement('td');
  86. td_0.style.verticalAlign = "top";
  87. td_0.style.textAlign = "right";
  88.  
  89. var btnClose = document.createElement('span');
  90. btnClose.style.background = "red none repeat scroll 0% 50%";
  91. btnClose.style.cursor = "pointer";
  92. btnClose.style.fontWeight = "bold";
  93. btnClose.style.MozBackgroundClip = "-moz-initial";
  94. btnClose.style.MozBackgroundOrigin = "-moz-initial";
  95. btnClose.style.MozBackgroundInlinePolicy = "-moz-initial";
  96. btnClose.id = "btn close";
  97. btnClose.title = translate("CLOSE_TITLE");
  98. btnClose.innerHTML = " X ";
  99.  
  100. td_0.appendChild(btnClose);
  101.  
  102. tr_0.appendChild(td_0);
  103.  
  104. tbody_0.appendChild(tr_0);
  105.  
  106. var tr_1 = document.createElement('tr');
  107.  
  108. var td_1 = document.createElement('td');
  109. td_1.appendChild(document.createTextNode(translate("SORTING_LABEL") + ":"));
  110.  
  111. tr_1.appendChild(td_1);
  112.  
  113. tbody_0.appendChild(tr_1);
  114.  
  115. var tr_2 = document.createElement('tr');
  116.  
  117. var td_2 = document.createElement('td');
  118.  
  119. var ul_0 = document.createElement('ul');
  120. ul_0.style.listStyle = "none outside none";
  121. ul_0.className = "dsq-options";
  122.  
  123. var li_0 = document.createElement('li');
  124.  
  125. var dsq_sorting_desc = document.createElement('input');
  126. dsq_sorting_desc.name = "sorting";
  127. dsq_sorting_desc.type = "radio";
  128. dsq_sorting_desc.id = "dsq-sorting-desc";
  129. li_0.appendChild(dsq_sorting_desc);
  130.  
  131. li_0.appendChild(document.createTextNode(translate("RECENT_LABEL")));
  132. ul_0.appendChild(li_0);
  133.  
  134. var li_1 = document.createElement('li');
  135.  
  136. var dsq_sorting_popular = document.createElement('input');
  137. dsq_sorting_popular.name = "sorting";
  138. dsq_sorting_popular.type = "radio";
  139. dsq_sorting_popular.id = "dsq-sorting-popular";
  140. li_1.appendChild(dsq_sorting_popular);
  141.  
  142. li_1.appendChild(document.createTextNode(translate("BEST_LABEL")));
  143. ul_0.appendChild(li_1);
  144.  
  145. var li_2 = document.createElement('li');
  146.  
  147. var dsq_sorting_asc = document.createElement('input');
  148. dsq_sorting_asc.name = "sorting";
  149. dsq_sorting_asc.type = "radio";
  150. dsq_sorting_asc.id = "dsq-sorting-asc";
  151. li_2.appendChild(dsq_sorting_asc);
  152.  
  153. li_2.appendChild(document.createTextNode(translate("OLDEST_LABEL")));
  154. ul_0.appendChild(li_2);
  155.  
  156. td_2.appendChild(ul_0);
  157.  
  158. tr_2.appendChild(td_2);
  159.  
  160. tbody_0.appendChild(tr_2);
  161.  
  162. var tr_3 = document.createElement('tr');
  163.  
  164. var td_3 = document.createElement('td');
  165.  
  166. var br_2 = document.createElement('br');
  167. td_3.appendChild(br_2);
  168.  
  169. tr_3.appendChild(td_3);
  170.  
  171. tbody_0.appendChild(tr_3);
  172.  
  173. var tr_4 = document.createElement('tr');
  174.  
  175. var td_4 = document.createElement('td');
  176. td_4.title = translate("AUTOLOAD_TITLE");
  177.  
  178. var autoload = document.createElement('input');
  179. autoload.type = "checkbox";
  180. autoload.id = "autoload_comments";
  181. autoload.title = translate("AUTOLOAD_TITLE");
  182. td_4.appendChild(autoload);
  183.  
  184. td_4.appendChild(document.createTextNode(translate("AUTOLOAD_LABEL")));
  185. tr_4.appendChild(td_4);
  186.  
  187. tbody_0.appendChild(tr_4);
  188.  
  189.  
  190. var tr_5 = document.createElement('tr');
  191.  
  192. var td_5 = document.createElement('td');
  193. td_5.title = translate("EXPAND_TITLE");
  194.  
  195. var autoexpand = document.createElement('input');
  196. autoexpand.type = "checkbox";
  197. autoexpand.id = "autoexpand_comments";
  198. autoexpand.title = translate("EXPAND_TITLE");
  199. td_5.appendChild(autoexpand);
  200.  
  201. td_5.appendChild(document.createTextNode(translate("EXPAND_LABEL")));
  202. tr_5.appendChild(td_5);
  203.  
  204. tbody_0.appendChild(tr_5);
  205.  
  206. var tr_6 = document.createElement('tr');
  207.  
  208. var td_6 = document.createElement('td');
  209. td_6.title = translate("GUEST_TITLE");
  210.  
  211. var without_login = document.createElement('input');
  212. without_login.type = "checkbox";
  213. without_login.id = "guest-comment";
  214. without_login.title = translate("GUEST_TITLE");
  215. td_6.appendChild(without_login);
  216.  
  217. td_6.appendChild(document.createTextNode(translate("GUEST_LABEL")));
  218. tr_6.appendChild(td_6);
  219. tbody_0.appendChild(tr_6);
  220.  
  221. table_0.appendChild(tbody_0);
  222.  
  223. div_Content.appendChild(table_0);
  224.  
  225. document.body.appendChild(div_Content);
  226.  
  227. // This function is only added for the purpose of facilitate the process of
  228. // inserting click events in radiobuttons and checkboxes (both current and
  229. // new) of the dialog config
  230. function click_check_setter(element, gm_value, pseudo_global_var, getted_value){
  231. var superior_scope = this; //Necessary to modify the "pseudo_global_var" from click event
  232. create_click_event(element, function () {
  233. GM_setValue(gm_value, getted_value === "" ? element.checked: getted_value);
  234. superior_scope[pseudo_global_var] = getted_value === "" ? element.checked: getted_value;
  235. });
  236. }
  237. // Init config dialog
  238. var sorting = {"desc": dsq_sorting_desc, "popular": dsq_sorting_popular, "asc": dsq_sorting_asc};
  239. sorting[default_order].checked= true;
  240. autoload.checked = autoload_comments;
  241. click_check_setter(autoload, 'autoload', "autoload_comments", "");
  242. autoexpand.checked = expand_comments;
  243. click_check_setter(autoexpand, 'expand', "expand_comments", "");
  244. without_login.checked = guest_comment;
  245. click_check_setter(without_login, 'guest', "guest_comment", "");
  246. // Set onclick event in radiobuttons
  247. click_check_setter(dsq_sorting_desc, 'sorting', "default_order", "desc");
  248. click_check_setter(dsq_sorting_popular, 'sorting', "default_order", "popular");
  249. click_check_setter(dsq_sorting_asc, 'sorting', "default_order", "asc");
  250. // Click event to close de config dialog
  251. create_click_event(document, function (e) {
  252. e = e || event
  253. var target = e.target || e.srcElement
  254. do {
  255. if (div_Content == target) {
  256. // Click occured inside the box, do nothing.
  257. return
  258. }
  259. target = target.parentNode
  260. } while (target)
  261. // Click was outside the box, destroy it.
  262. del_element(div_Content);
  263. del_element(configDialogStyle);
  264. });
  265.  
  266. create_click_event(btnClose, function(){
  267. del_element(div_Content);
  268. del_element(configDialogStyle);
  269. });
  270. }
  271.  
  272. function clicker(link){
  273. if (link.click) {link.click();}
  274. else{
  275. var new_event = document.createEvent("MouseEvents");
  276. new_event.initMouseEvent("click", true, true, window,0, 0, 0, 0, 0, false, false, false, false, 0, null);
  277. try { link.dispatchEvent(new_event); }
  278. // Do nothing in this catch, this is a problem with JQuery compatibility, more details in
  279. // http://stackoverflow.com/questions/980697/element-dispatchevent-is-not-a-function-js-error-caught-in-firebug-of-ff3-0
  280. catch(error) {}
  281. }
  282. }
  283.  
  284. function finder(tag, attrib, value, one_element){
  285. if (document.querySelector || document.querySelectorAll){
  286. var selectors = {true: "querySelector", false: "querySelectorAll"}
  287. return document[selectors[one_element]](tag + '['+ attrib+ '="'+ value + '"]');
  288. }
  289. else {
  290. var elems = document.getElementsByTagName(tag);
  291. var results = Array();
  292. for (i in elems){
  293. if(elems[i].getAttribute(attrib) == value) {
  294. if (one_element == true) { return elems[i]; }
  295. else { results.push(elems[i]); }
  296. }
  297. }
  298. return results;
  299. }
  300. }
  301.  
  302. function is_class_name(elem, matchClass){
  303. //Adapted from http://stackoverflow.com/a/3808886
  304. if (elem == null) { return null; }
  305. else if (typeof(matchClass) == 'string') {
  306. return (' ' + elem.className + ' ').indexOf(' ' + matchClass + ' ') > -1;
  307. }
  308. else if (matchClass instanceof RegExp) { return matchClass.test(elem.className); }
  309. }
  310.  
  311. function create_click_event(element, func) {
  312. if (element.addEventListener) {
  313. element.addEventListener("click", func, false); }
  314. else { elemen.attachEvent("onclick", func); } //Internet Explorer
  315. }
  316.  
  317. function del_element(id){
  318. if (typeof(id) == "string") { var element = document.getElementById(id); }
  319. else { element = id; }
  320. if (element){
  321. var ancestry = element.parentNode;
  322. ancestry.removeChild(element);
  323. }
  324. }
  325.  
  326. //saved values
  327. default_order = GM_getValue("sorting", "desc");
  328. autoload_comments = GM_getValue("autoload", true);
  329. expand_comments = GM_getValue("expand", true);
  330. guest_comment = GM_getValue("guest", true);
  331.  
  332. //register GM menu command
  333. GM_registerMenuCommand("[Disqus Click Automate] Configuration", config_dialog);
  334.  
  335. // This line (with the @include *) is needed to open the configuration dialog at any time from any page
  336. if (window.location.href.search("disqus.com/embed/comments") == -1 ) {return;}
  337.  
  338. all_comments_retrieved = false;
  339. //var JSON_Disqus = eval("("+document.getElementById("disqus-jsonData").innerHTML+")");
  340. //var actual_comments = JSON_Disqus["response"]["posts"].length;
  341. total_comments = null;
  342. comments = null;
  343. howmany = null;
  344. racecond = 0;
  345. actual_sort = null;
  346.  
  347. // Ordering comments and get some extra data
  348. sorted_comments = false;
  349.  
  350. initial_timer = window.setInterval(function (){
  351.  
  352. // console.log("Racecond: ",racecond);
  353.  
  354. if (actual_sort == null) {
  355. var actual_sort = finder("li", 'class', "selected", true);
  356. // var actual_sort = finder("li", 'active', "data-tab", false);
  357. racecond = racecond + 1;
  358. if (racecond > 100) {
  359. console.log("Race!");
  360. actual_sort = "unknown";
  361. window.clearInterval(initial_timer);
  362. }
  363. }
  364. console.log("actual_sort:",actual_sort);
  365. if (actual_sort != null) {
  366. var choosen_sort = finder("a", 'data-sort', default_order, true);
  367. if (actual_sort.children[0].getAttribute('data-sort') != default_order){
  368. clicker(choosen_sort);
  369. }
  370. sorted_comments = true;
  371. total_comments = parseInt(finder("li", 'data-role', "post-count", true).innerHTML.match(/([0-9]+) /));
  372. comments = document.getElementById("post-list");
  373. window.clearInterval(initial_timer);
  374. console.log("Finished first timer");
  375. console.log("sorted_comments:",sorted_comments);
  376. }
  377. }, 100);
  378.  
  379. if (autoload_comments == true){
  380. timer1 = window.setInterval(function (){
  381. // if (is_class_name(comments, "post-list loading") == false && sorted_comments == true) {
  382. if (1 == 1) {
  383. console.log("Loading button: ",is_class_name(comments, "post-list loading"));
  384. console.log("Autoload please...");
  385. console.log("Window location: ",window.location.hostname);
  386. // Autoload all comments
  387. var more_comments = finder("a", "data-action", "more-posts", true);
  388. if (is_class_name(more_comments, "btn busy") == false && more_comments.parentNode.style.display != "none"){
  389. clicker(more_comments);
  390. }
  391. else if ((finder("li", "class", "post", false).length + finder("li", "class", "post minimized", false).length) >= (total_comments-5)) {
  392. all_comments_retrieved = true;
  393. window.clearInterval(timer1);
  394. console.log("Finished second timer");
  395. console.log("Counted comments:",finder("li", "class", "post", false).length + finder("li", "class", "post minimized", false).length);
  396. console.log("Total comments:",total_comments);
  397.  
  398. // Clean Disqus mangled links
  399. var links = document.evaluate("//a[contains(@href, 'https://disq.us/url?url=') or contains(@href, 'http://disq.us/url?url=')]", document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
  400. console.log("Links to fix:",links.snapshotLength);
  401. for (var i=0; i < links.snapshotLength; i++)
  402. {
  403. var thisLink = links.snapshotItem(i);
  404. var str = decodeURIComponent(thisLink);
  405. str = str.substring(str.indexOf("=") + 1);
  406. thisLink.href = (str.substring(0, str.lastIndexOf(":")));
  407. console.log("Fixed link: ",thisLink.href);
  408. }
  409. }
  410. }
  411. // }, 100); changed because Disqus freaking out on shorter timer
  412. }, 1000);
  413. }
  414. else { all_comments_retrieved = true; } //Necessary to run the next timer
  415.  
  416. // This timer is held in execution because normally the "long" comments often contain many
  417. // images and/or videos, that when the user read this, then Disqus load comments with this
  418. // kind of content and the system at that moment decides whether shorten (or not) the comment
  419. // based on the resultant height (in pixels) of the div where is the comment.
  420. if (expand_comments == true){
  421. timer2 = window.setInterval(function (){// Unhide long comments
  422. var comments = finder("div", "class", "post-body-inner", false);
  423. for (var i=0; i < comments.length; i++) {
  424. var nodes = comments[i].childNodes;
  425. for (var j=0; j < nodes.length; j++) {
  426. if (is_class_name(nodes[j], "post-message-container") == true) {
  427. nodes[j].style.height = "";
  428. }
  429. else if (is_class_name(nodes[j], /message-shadow|more-button|see-more/gi) == true) {
  430. comments[i].removeChild(nodes[j]);
  431. }
  432. }
  433. }
  434. // Adjust the height of the iframe to match with the unshorted comments
  435. // height with media content that are loaded when the user checks them
  436. // (for some reason does not work or only works sometimes, if anyone
  437. // knows how to fix this, then notify on the forum)
  438. if (top.location != self.location) {
  439. var dsq_iframe = top.document.getElementById("dsq-2");
  440. if (dsq_iframe != null){
  441. dsq_iframe.style.height = document.getElementsByTagName("body")[0].clientHeight + "px";
  442. }
  443. }
  444. //console.log("Finished third timer");
  445. }, 500);
  446. }
  447.  
  448. // Check the option "I prefer commenting as guest" when you click in the textbox to write the nick
  449. if (guest_comment == true){
  450. timer3 = window.setInterval(function (){
  451. var nick_textbox = finder("input", "name", "author-name", true);
  452. if (nick_textbox != null){
  453. create_click_event(nick_textbox, function(){
  454. finder("input", "name", "author-guest", true).checked = true;
  455. del_element(finder("input", "name", "author-password", true));
  456. });
  457. window.clearInterval(timer3);
  458. }
  459. }, 500);
  460. }