YARCO.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  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 only_delete_old_comments = false //ignore comments newer than 24 hours
  13. let only_delete_by_subreddit = false //ignore comments from subreddits other than the one chosen in the dropdown
  14. let time_between_actions = 2000 //reddit API limit is 60 actions per minute so don't exceed that
  15. // TODO check feedback for Reddit Overwrite for extra features
  16. // TODO consider caching comments array OR not
  17. // TODO test Overview page
  18. // TODO add STOP button
  19. // TODO add optional confirmation dialog OR start up delay
  20. // TODO add status message while processing
  21. // reddit username
  22. unsafeWindow.user = '';
  23. // array of comments (more precisely author tags)
  24. unsafeWindow.comments = [];
  25. // top section contents
  26. unsafeWindow.div = '';
  27. // subreddit selected for deletion
  28. unsafeWindow.subreddit = "ALL";
  29. unsafeWindow.subreddit_array = [];
  30. // on page loaded, initialize the script
  31. window.addEventListener("DOMContentLoaded", init_script, false);
  32. function init_script(ev) {
  33. // get logged in username
  34. unsafeWindow.user = document.querySelector("span.user > a:not(.login-required)").innerHTML;
  35. // if not logged in exit
  36. if (!unsafeWindow.user) return;
  37. //retrieve all VISIBLE comments
  38. get_comments()
  39. // generate the top buttons
  40. generate_top_buttons();
  41. }
  42. function get_comments() {
  43. // find all author tags to eventually get comments
  44. let comments = document.querySelectorAll("a.author");
  45. // filter out other authors
  46. unsafeWindow.comments = [].filter.call(comments, filter_author);
  47. // if active, filter out comments from the past 24 hours
  48. if (only_delete_old_comments) {
  49. unsafeWindow.comments = [].filter.call(unsafeWindow.comments, filter_time);
  50. }
  51. // if active, filter out comments from other subreddits than the chosen one
  52. if (only_delete_by_subreddit && unsafeWindow.subreddit !== "ALL") {
  53. unsafeWindow.comments = [].filter.call(comments, filter_subreddit);
  54. }
  55. }
  56. // append buttons to page
  57. function generate_top_buttons() {
  58. if (unsafeWindow.comments.length) {
  59. unsafeWindow.div = document.createElement("div");
  60. unsafeWindow.div.setAttribute('class', 'nextprev secure_delete_all');
  61. unsafeWindow.div.innerHTML = "";
  62. unsafeWindow.div.style.marginBottom = "10px";
  63. unsafeWindow.div.style.display = "flex";
  64. // make Overwrite and Delete All link
  65. let odlink = document.createElement("a");
  66. odlink.setAttribute('class', 'bylink');
  67. odlink.setAttribute('onClick', 'javascript: recursive_process(true, true)');
  68. odlink.setAttribute('href', 'javascript:void(0)');
  69. odlink.style.marginLeft = "10px";
  70. odlink.appendChild(document.createTextNode('OVERWRITE AND DELETE ' +
  71. unsafeWindow.comments.length +
  72. ' COMMENTS'));
  73. unsafeWindow.div.appendChild(odlink);
  74. let br = document.createElement("br");
  75. unsafeWindow.div.appendChild(br);
  76. // make Overwrite All link
  77. let olink = document.createElement("a");
  78. olink.setAttribute('class', 'bylink');
  79. olink.setAttribute('onClick', 'javascript: recursive_process(true, false)');
  80. olink.setAttribute('href', 'javascript:void(0)');
  81. olink.style.marginLeft = "10px";
  82. olink.appendChild(document.createTextNode('OVERWRITE ' +
  83. unsafeWindow.comments.length +
  84. ' COMMENTS'));
  85. unsafeWindow.div.appendChild(olink);
  86. let br2 = document.createElement("br");
  87. unsafeWindow.div.appendChild(br2);
  88. // make Delete All link
  89. let dlink = document.createElement("a");
  90. dlink.setAttribute('class', 'bylink');
  91. dlink.setAttribute('onClick', 'javascript: recursive_process(false, true)');
  92. dlink.setAttribute('href', 'javascript:void(0)');
  93. dlink.style.marginLeft = "10px";
  94. dlink.appendChild(document.createTextNode('DELETE ' +
  95. unsafeWindow.comments.length +
  96. ' COMMENTS'));
  97. unsafeWindow.div.appendChild(dlink);
  98. // TODO add status message
  99. // let status_message = document.createTextNode("p");
  100. // status_message.innerHTML = "STATUS";
  101. // status_message.style.marginLeft = "10px";
  102. // status_message.style.position = "relative";
  103. // status_message.style.top = "10px";
  104. // unsafeWindow.span.appendChild(status_message);
  105. // make Subreddit Filter
  106. //TODO add label
  107. if (only_delete_by_subreddit) {
  108. //Create array of subreddits from comments
  109. unsafeWindow.subreddit_array = get_subreddit_array();
  110. let selectList = document.createElement("select");
  111. selectList.id = "subredditSelect";
  112. selectList.style.marginLeft = "10px";
  113. selectList.setAttribute('onChange', 'javascript: subreddit_select(this.value)')
  114. let selectedTitle = document.createElement("option");
  115. selectedTitle.selected = true;
  116. selectedTitle.disabled = true;
  117. selectedTitle.label = "Subreddit";
  118. selectList.append(selectedTitle);
  119. //Create and append the options
  120. for (let i = 0; i < unsafeWindow.subreddit_array.length; i++) {
  121. let option = document.createElement("option");
  122. option.value = unsafeWindow.subreddit_array[i];
  123. option.text = unsafeWindow.subreddit_array[i];
  124. selectList.appendChild(option);
  125. }
  126. unsafeWindow.div.appendChild(selectList);
  127. }
  128. //add our div to the webpage
  129. document.querySelector("div.content").insertBefore(unsafeWindow.div, document.querySelector("div.content").firstChild);
  130. //add per comment buttons (disabled by default)
  131. if (generate_individual_delete_buttons) unsafeWindow.generate_delete_buttons()
  132. } else if (unsafeWindow.div != null) {
  133. unsafeWindow.div.style.display = 'none';
  134. }
  135. }
  136. unsafeWindow.recursive_process = function (overwrite_all, delete_all) {
  137. //get comments again in case the user has scrolled and revealed more comments
  138. get_comments()
  139. let commentsArray = [];
  140. for (let i = 0; i < unsafeWindow.comments.length; i++) {
  141. //for each author, get ID of the input field of the comment
  142. let thing_id = unsafeWindow.comments[i].parentNode.parentNode.querySelector("form.usertext > input[name='thing_id']").value;
  143. if (commentsArray.indexOf(thing_id) == -1) {
  144. commentsArray.push(thing_id);
  145. }
  146. }
  147. if (overwrite_all && delete_all) {
  148. unsafeWindow.overwrite_all(commentsArray, true);
  149. } else if (overwrite_all) {
  150. unsafeWindow.overwrite_all(commentsArray, false);
  151. } else if (delete_all) {
  152. unsafeWindow.delete_all(commentsArray);
  153. }
  154. }
  155. unsafeWindow.overwrite_all = function (comments, also_delete) {
  156. //get next comment id
  157. let thing_id = comments.shift();
  158. //overwrite the next comment in the stack
  159. unsafeWindow.overwrite_comment(thing_id);
  160. //if also deleting, add a timeout and delete the comment
  161. if (also_delete) unsafeWindow.setTimeout(unsafeWindow.delete_comment(thing_id), time_between_actions);
  162. //if there are still comments left, get next comment
  163. //increase timeout if also deleting
  164. if (comments.length) unsafeWindow.setTimeout(unsafeWindow.overwrite_all, also_delete ? time_between_actions * 2 : time_between_actions, comments, also_delete);
  165. }
  166. unsafeWindow.delete_all = function (comments) {
  167. unsafeWindow.delete_comment(comments.shift());
  168. //if there are still comments left, get next comment
  169. if (comments.length) unsafeWindow.setTimeout(unsafeWindow.delete_all, time_between_actions, comments);
  170. }
  171. unsafeWindow.overwrite_comment = function (thing_id) {
  172. try {
  173. //find edit form (hidden on page but active)
  174. let edit_form = document.querySelector("input[name='thing_id'][value='" + thing_id + "']").parentNode;
  175. //if comment is currently being edited, cancel out of that
  176. let edit_cancel_btn = edit_form.querySelector("div.usertext-edit > div.bottom-area > div.usertext-buttons > button.cancel");
  177. edit_cancel_btn.click();
  178. //find edit button and click it
  179. let edit_btn = edit_form.parentNode.querySelector("ul > li > a.edit-usertext");
  180. if (edit_btn) edit_btn.click();
  181. //find edit textbox and replace the string with random chars
  182. let edit_textbox = edit_form.querySelector("div.usertext-edit > div > textarea");
  183. let repl_str = '';
  184. let chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz><.-,+!#$%^&*();:[]~";
  185. for (let i = 0; i < edit_textbox.value.length; i++) {
  186. if (edit_textbox.value.substr(i, 1) == '\n') {
  187. repl_str += '\n';
  188. } else {
  189. let random_char = Math.floor(Math.random() * chars.length);
  190. repl_str += chars.charAt(random_char, 1);
  191. }
  192. }
  193. //set edited value to the random string
  194. edit_textbox.value = repl_str;
  195. //find save comment button and click it
  196. let edit_save_btn = edit_form.querySelector("div.usertext-edit > div.bottom-area > div.usertext-buttons > button.save");
  197. edit_save_btn.click();
  198. } catch (e) {
  199. alert("Error interacting with overwrite form: " + e);
  200. }
  201. }
  202. unsafeWindow.delete_comment = function (thing_id) {
  203. try {
  204. // get current status of comment editing box to prevent deleting comment before overwrite is complete
  205. let thing = document.querySelector("input[name='thing_id'][value='" + thing_id + "']");
  206. let status = thing.parentNode.querySelector("div.usertext-edit > div.bottom-area > div.usertext-buttons > span.status").innerHTML;
  207. if (status.indexOf("error") != -1) {
  208. alert("Failed to overwrite comment " + thing_id + " due to an unknown reddit error, skipping.");
  209. return;
  210. }
  211. // if status is submitting, there may be an internet connectivity error, so we retry
  212. if (status.indexOf("submitting") != -1) {
  213. unsafeWindow.setTimeout(unsafeWindow.delete_comment, time_between_actions * 5, thing_id);
  214. return;
  215. }
  216. // find delete button and click it and then yes confirmation button
  217. let del_form = thing.parentNode.parentNode.querySelector("ul.buttons > li > form.del-button");
  218. unsafeWindow.toggle(del_form.querySelector("span.main > a"));
  219. del_form.querySelector("span.error > a.yes").click();
  220. } catch (e) {
  221. alert("Error deleting comment: " + e);
  222. }
  223. }
  224. //[UTILITY FUNCTIONS]
  225. function filter_author(comment) {
  226. return comment.innerHTML == unsafeWindow.user;
  227. }
  228. function filter_time(comment) {
  229. return comment.parentNode.parentNode.querySelector("time").innerHTML.indexOf("hour") === -1;
  230. }
  231. function filter_subreddit(comment) {
  232. return comment.parentNode.parentNode.parentNode.querySelector("a.subreddit").innerHTML == unsafeWindow.subreddit;
  233. }
  234. function get_subreddit_array() {
  235. let array = [];
  236. for (let i = 0; i < unsafeWindow.comments.length; i++) {
  237. let sub = unsafeWindow.comments[i].parentNode.parentNode.parentNode.querySelector("a.subreddit").innerHTML;
  238. if (array.indexOf(sub) === -1) array.push(sub);
  239. }
  240. // Sort the array case insensitive and add option to disable subreddit filtering
  241. array = array.sort(sort_ignore_caps);
  242. array.unshift("ALL");
  243. return array;
  244. }
  245. function sort_ignore_caps(a, b) {
  246. return a.toLowerCase().localeCompare(b.toLowerCase());
  247. }
  248. unsafeWindow.overwrite_delete = function (thing_id) {
  249. unsafeWindow.overwrite_comment(thing_id)
  250. unsafeWindow.setTimeout(unsafeWindow.delete_comment, time_between_actions, thing_id)
  251. }
  252. //function to regenerate secure delete buttons after only overwriting a comment
  253. unsafeWindow.overwrite_reload = function (thing_id) {
  254. unsafeWindow.overwrite_comment(thing_id)
  255. unsafeWindow.setTimeout(unsafeWindow.generate_delete_buttons, 500)
  256. }
  257. //[EXTRA FEATURES]
  258. //Add a "SECURE DELETE" button near each comment delete button
  259. unsafeWindow.generate_delete_buttons = function () {
  260. get_comments()
  261. for (let i = 0; i < unsafeWindow.comments.length; i++) {
  262. try {
  263. // get the parent
  264. let main_parent = unsafeWindow.comments[i].parentNode.parentNode;
  265. let thing_id = main_parent.querySelector("form > input[name='thing_id']").value;
  266. let list = main_parent.querySelector("ul.flat-list");
  267. // if it already contains the tags, skip
  268. if (list.querySelector("li.secure_delete") && list.querySelector("li.overwrite")) continue;
  269. // add SECURE DELETE link to comments
  270. let secure_delete_link = document.createElement("li");
  271. secure_delete_link.setAttribute('class', 'secure_delete');
  272. let dlink = document.createElement("a");
  273. dlink.setAttribute('class', 'bylink secure_delete');
  274. dlink.setAttribute('onClick', 'javascript: overwrite_delete("' + thing_id + '")');
  275. dlink.setAttribute('href', 'javascript:void(0)');
  276. dlink.appendChild(document.createTextNode('SECURE DELETE'));
  277. secure_delete_link.appendChild(dlink);
  278. main_parent.querySelector("ul.flat-list").appendChild(secure_delete_link);
  279. // add OVERWRITE link to comments
  280. let overwrite_link = document.createElement("li");
  281. overwrite_link.setAttribute('class', 'overwrite');
  282. let olink = document.createElement("a");
  283. olink.setAttribute('class', 'bylink secure_delete');
  284. olink.setAttribute('onClick', 'javascript: overwrite_reload("' + thing_id + '")');
  285. olink.setAttribute('href', 'javascript:void(0)');
  286. olink.appendChild(document.createTextNode('OVERWRITE'));
  287. overwrite_link.appendChild(olink);
  288. main_parent.querySelector("ul.flat-list").appendChild(overwrite_link);
  289. } catch (e) {
  290. alert("Error adding Secure Delete links to comments.\nError: " + e + " Stack:" + e.stack);
  291. }
  292. }
  293. }
  294. unsafeWindow.subreddit_select = function (option) {
  295. unsafeWindow.subreddit = option;
  296. get_comments();
  297. }