YARCO.js 15 KB

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