YARCO.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. // ==UserScript==
  2. // @name Yet Another Reddit Comment Overwriter
  3. // @namespace https://github.com/adriantache/YARCO/
  4. // @description Local script to overwrite all your comments with random ASCII characters and delete them. This works because Reddit doesn't store editing history, so technically this is the only way to obfuscate the contents of the comments. Based on Reddit Overwrite script v.1.4.8.
  5. // @include https://*.reddit.com/user/*
  6. // @include http://*.reddit.com/user/*
  7. // @version 0.1
  8. // @run-at document-start
  9. // ==/UserScript==
  10. //EXTRA OPTIONS
  11. let generate_individual_delete_buttons = false //generate per comment delete and overwrite links
  12. let time_between_actions = 2000 //reddit API limit is 60 actions per minute so don't exceed that
  13. let only_delete_old_comments = true //disabled by default
  14. // TODO separate overwrite and delete
  15. // TODO only delete comments older than a day
  16. // TODO only delete comments from a certain subreddit
  17. // TODO check feedback for Reddit Overwrite for extra features
  18. // TODO consider caching comments array
  19. // TODO test Overview page
  20. // TODO add STOP button
  21. // TODO add optional confirmation dialog
  22. // TODO add status message while processing
  23. // reddit username
  24. unsafeWindow.user = '';
  25. // array of comments (more precisely author tags)
  26. unsafeWindow.comments = [];
  27. // top section contents
  28. unsafeWindow.span = '';
  29. // on page loaded, initialize the script
  30. window.addEventListener("DOMContentLoaded", init_script, false);
  31. function init_script(ev) {
  32. // get logged in username
  33. unsafeWindow.user = document.querySelector("span.user > a:not(.login-required)").innerHTML;
  34. // if not logged in exit
  35. if (!unsafeWindow.user) return;
  36. //retrieve all VISIBLE comments
  37. get_comments()
  38. // generate the top buttons
  39. generate_top_buttons();
  40. }
  41. function get_comments() {
  42. // find all author tags to eventually get comments
  43. let comments = document.querySelectorAll("a.author");
  44. // filter out other authors
  45. unsafeWindow.comments = [].filter.call(comments, filter_author)
  46. }
  47. // append buttons to page
  48. function generate_top_buttons() {
  49. if (unsafeWindow.comments.length) {
  50. unsafeWindow.span = document.createElement("div");
  51. unsafeWindow.span.setAttribute('class', 'nextprev secure_delete_all');
  52. unsafeWindow.span.innerHTML = "";
  53. unsafeWindow.span.style.marginBottom = "10px";
  54. // make Overwrite and Delete All link
  55. let odlink = document.createElement("a");
  56. odlink.setAttribute('class', 'bylink');
  57. odlink.setAttribute('onClick', 'javascript: recursive_process(true, true)');
  58. odlink.setAttribute('href', 'javascript:void(0)');
  59. odlink.style.marginLeft = "10px";
  60. odlink.appendChild(document.createTextNode('OVERWRITE AND DELETE ' +
  61. unsafeWindow.comments.length +
  62. ' COMMENTS'));
  63. unsafeWindow.span.appendChild(odlink);
  64. let br = document.createElement("br");
  65. unsafeWindow.span.appendChild(br);
  66. // make Overwrite All link
  67. let olink = document.createElement("a");
  68. olink.setAttribute('class', 'bylink');
  69. olink.setAttribute('onClick', 'javascript: recursive_process(true, false)');
  70. olink.setAttribute('href', 'javascript:void(0)');
  71. olink.style.marginLeft = "10px";
  72. olink.style.position = "relative";
  73. olink.style.top = "5px";
  74. olink.appendChild(document.createTextNode('OVERWRITE ' +
  75. unsafeWindow.comments.length +
  76. ' COMMENTS'));
  77. unsafeWindow.span.appendChild(olink);
  78. let br2 = document.createElement("br");
  79. unsafeWindow.span.appendChild(br2);
  80. // make Delete All link
  81. let dlink = document.createElement("a");
  82. dlink.setAttribute('class', 'bylink');
  83. dlink.setAttribute('onClick', 'javascript: recursive_process(false, true)');
  84. dlink.setAttribute('href', 'javascript:void(0)');
  85. dlink.style.marginLeft = "10px";
  86. dlink.style.position = "relative";
  87. dlink.style.top = "10px";
  88. dlink.appendChild(document.createTextNode('DELETE ' +
  89. unsafeWindow.comments.length +
  90. ' COMMENTS'));
  91. unsafeWindow.span.appendChild(dlink);
  92. // TODO test status message
  93. // let status_message = document.createTextNode("p");
  94. // status_message.innerHTML = "STATUS";
  95. // status_message.style.marginLeft = "10px";
  96. // status_message.style.position = "relative";
  97. // status_message.style.top = "10px";
  98. // unsafeWindow.span.appendChild(status_message);
  99. document.querySelector("div.content").insertBefore(unsafeWindow.span, document.querySelector("div.content").firstChild);
  100. //add per comment buttons (disabled by default)
  101. if (generate_individual_delete_buttons) unsafeWindow.generate_delete_buttons()
  102. } else if (unsafeWindow.span != null) {
  103. unsafeWindow.span.style.display = 'none';
  104. }
  105. }
  106. unsafeWindow.recursive_process = function (overwrite_all, delete_all) {
  107. //get comments again in case the user has scrolled and revealed more comments
  108. get_comments()
  109. let commentsArray = [];
  110. for (let i = 0; i < unsafeWindow.comments.length; i++) {
  111. //for each author, get ID of the input field of the comment
  112. let thing_id = unsafeWindow.comments[i].parentNode.parentNode.querySelector("form.usertext > input[name='thing_id']").value;
  113. if (commentsArray.indexOf(thing_id) == -1) {
  114. commentsArray.push(thing_id);
  115. }
  116. }
  117. if (overwrite_all && delete_all) {
  118. unsafeWindow.overwrite_all(commentsArray, true);
  119. } else if (overwrite_all) {
  120. unsafeWindow.overwrite_all(commentsArray, false);
  121. } else if (delete_all) {
  122. unsafeWindow.delete_all(commentsArray);
  123. }
  124. }
  125. unsafeWindow.overwrite_all = function (comments, also_delete) {
  126. //get next comment id
  127. let thing_id = comments.shift();
  128. //overwrite the next comment in the stack
  129. unsafeWindow.overwrite_comment(thing_id);
  130. //if also deleting, add a timeout and delete the comment
  131. if (also_delete) unsafeWindow.setTimeout(unsafeWindow.delete_comment(thing_id), time_between_actions);
  132. //if there are still comments left, get next comment
  133. //increase timeout if also deleting
  134. if (comments.length) unsafeWindow.setTimeout(unsafeWindow.overwrite_all, also_delete ? time_between_actions * 2 : time_between_actions, comments, also_delete);
  135. }
  136. unsafeWindow.delete_all = function (comments) {
  137. unsafeWindow.delete_comment(comments.shift());
  138. //if there are still comments left, get next comment
  139. if (comments.length) unsafeWindow.setTimeout(unsafeWindow.delete_all, time_between_actions, comments);
  140. }
  141. unsafeWindow.overwrite_comment = function (thing_id) {
  142. try {
  143. //find edit form (hidden on page but active)
  144. let edit_form = document.querySelector("input[name='thing_id'][value='" + thing_id + "']").parentNode;
  145. //if comment is currently being edited, cancel out of that
  146. let edit_cancel_btn = edit_form.querySelector("div.usertext-edit > div.bottom-area > div.usertext-buttons > button.cancel");
  147. edit_cancel_btn.click();
  148. //find edit button and click it
  149. let edit_btn = edit_form.parentNode.querySelector("ul > li > a.edit-usertext");
  150. if (edit_btn) edit_btn.click();
  151. //find edit textbox and replace the string with random chars
  152. let edit_textbox = edit_form.querySelector("div.usertext-edit > div > textarea");
  153. let repl_str = '';
  154. let chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz><.-,+!#$%^&*();:[]~";
  155. for (let i = 0; i < edit_textbox.value.length; i++) {
  156. if (edit_textbox.value.substr(i, 1) == '\n') {
  157. repl_str += '\n';
  158. } else {
  159. let random_char = Math.floor(Math.random() * chars.length);
  160. repl_str += chars.charAt(random_char, 1);
  161. }
  162. }
  163. //set edited value to the random string
  164. edit_textbox.value = repl_str;
  165. //find save comment button and click it
  166. let edit_save_btn = edit_form.querySelector("div.usertext-edit > div.bottom-area > div.usertext-buttons > button.save");
  167. edit_save_btn.click();
  168. } catch (e) {
  169. alert("Error interacting with overwrite form: " + e);
  170. }
  171. }
  172. unsafeWindow.delete_comment = function (thing_id) {
  173. try {
  174. // get current status of comment editing box to prevent deleting comment before overwrite is complete
  175. let thing = document.querySelector("input[name='thing_id'][value='" + thing_id + "']");
  176. let status = thing.parentNode.querySelector("div.usertext-edit > div.bottom-area > div.usertext-buttons > span.status").innerHTML;
  177. if (status.indexOf("error") != -1) {
  178. alert("Failed to overwrite comment " + thing_id + " due to an unknown reddit error, skipping.");
  179. return;
  180. }
  181. // if status is submitting, there may be an internet connectivity error, so we retry
  182. if (status.indexOf("submitting") != -1) {
  183. unsafeWindow.setTimeout(unsafeWindow.delete_comment, time_between_actions * 5, thing_id);
  184. return;
  185. }
  186. // find delete button and click it and then yes confirmation button
  187. let del_form = thing.parentNode.parentNode.querySelector("ul.buttons > li > form.del-button");
  188. unsafeWindow.toggle(del_form.querySelector("span.main > a"));
  189. del_form.querySelector("span.error > a.yes").click();
  190. } catch (e) {
  191. alert("Error deleting comment: " + e);
  192. }
  193. }
  194. //[UTILITY FUNCTIONS]
  195. function filter_author(comment) {
  196. return comment.innerHTML == unsafeWindow.user
  197. }
  198. unsafeWindow.overwrite_delete = function (thing_id) {
  199. unsafeWindow.overwrite_comment(thing_id)
  200. unsafeWindow.setTimeout(unsafeWindow.delete_comment, time_between_actions, thing_id)
  201. }
  202. //function to regenerate secure delete buttons after only overwriting a comment
  203. unsafeWindow.overwrite_reload = function (thing_id) {
  204. unsafeWindow.overwrite_comment(thing_id)
  205. unsafeWindow.setTimeout(unsafeWindow.generate_delete_buttons, 500)
  206. }
  207. //[EXTRA FEATURES]
  208. //Add a "SECURE DELETE" button near each comment delete button
  209. unsafeWindow.generate_delete_buttons = function () {
  210. get_comments()
  211. for (let i = 0; i < unsafeWindow.comments.length; i++) {
  212. try {
  213. // get the parent
  214. let main_parent = unsafeWindow.comments[i].parentNode.parentNode;
  215. let thing_id = main_parent.querySelector("form > input[name='thing_id']").value;
  216. let list = main_parent.querySelector("ul.flat-list");
  217. //TODO REmove this
  218. alert(main_parent.querySelector("time").innerHTML)
  219. // if it already contains the tags, skip
  220. if (list.querySelector("li.secure_delete") && list.querySelector("li.overwrite")) continue;
  221. // add SECURE DELETE link to comments
  222. let secure_delete_link = document.createElement("li");
  223. secure_delete_link.setAttribute('class', 'secure_delete');
  224. let dlink = document.createElement("a");
  225. dlink.setAttribute('class', 'bylink secure_delete');
  226. dlink.setAttribute('onClick', 'javascript: overwrite_delete("' + thing_id + '")');
  227. dlink.setAttribute('href', 'javascript:void(0)');
  228. dlink.appendChild(document.createTextNode('SECURE DELETE'));
  229. secure_delete_link.appendChild(dlink);
  230. main_parent.querySelector("ul.flat-list").appendChild(secure_delete_link);
  231. // add OVERWRITE link to comments
  232. let overwrite_link = document.createElement("li");
  233. overwrite_link.setAttribute('class', 'overwrite');
  234. let olink = document.createElement("a");
  235. olink.setAttribute('class', 'bylink secure_delete');
  236. olink.setAttribute('onClick', 'javascript: overwrite_reload("' + thing_id + '")');
  237. olink.setAttribute('href', 'javascript:void(0)');
  238. olink.appendChild(document.createTextNode('OVERWRITE'));
  239. overwrite_link.appendChild(olink);
  240. main_parent.querySelector("ul.flat-list").appendChild(overwrite_link);
  241. } catch (e) {
  242. alert("Error adding Secure Delete links to comments.\nError: " + e + " Stack:" + e.stack);
  243. }
  244. }
  245. }