MD5 filter bugfix
[4Free-FSE.git] / Thread-Rebuilder.user.js
blob0794abe614846f434f6858deea7e90457606990a
1         // ==UserScript==
2         // @name         Thread Rebuilder
3         // @namespace    http://tampermonkey.net/
4         // @version      2.3
5         // @description  try to take over the world!
6         // @author       ECHibiki /qa/
7         // @match https://boards.4chan.org/*/thread/*
8         // @match http://boards.4chan.org/*/thread/*
9         // @grant         GM_xmlhttpRequest
10         // @updateURL    https://github.com/ECHibiki/4chan-UserScripts/raw/master/Thread-Rebuilder.user.js
11         // @downloadURL  https://github.com/ECHibiki/4chan-UserScripts/raw/master/Thread-Rebuilder.user.js
12         // @run-at document-start
13         // ==/UserScript==
15         var board = "qa";
16         var thread_data = [['Comment'], ['Image URLs'], ['Image Names'] ,['Post No.']];
17         var semaphore = 1;
18         var semaphore_posts = 1;
19         var timeListen;
21         var use_offsite_archive = false;
22         var window_displayed = false;
23         var in_sequence = false;
25         //1) CREATE INTERFACE
26         //set listener to build interface in 4chanX
27         //set listeners to build interface in 4chanX
28 document.addEventListener("4chanXInitFinished", function(e){
29         document.addEventListener("QRDialogCreation", enhance4ChanX);
31         rebuildWindow();
32         rebuildButton();
34         use_offsite_archive =  localStorage.getItem("ArchiveType") == 0 ? true : false;
35         if(use_offsite_archive) document.getElementById("OffsiteArchive").checked = true;
36         else document.getElementById("OnsiteArchive").checked = true;
38         loaded = true;
39 }, false);
41 //is storage possible
42 function storageAvailable(type) {
43     try {
44         var storage = window[type],
45             x = '__storage_test__';
46         storage.setItem(x, x);
47         storage.removeItem(x);
48         return true;
49     }
50     catch(e) {
51                 //From https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
52         return e instanceof DOMException && (
53             // everything except Firefox
54             e.code === 22 ||
55             // Firefox
56             e.code === 1014 ||
57             // test name field too, because code might not be present
58             // everything except Firefox
59             e.name === 'QuotaExceededError' ||
60             // Firefox
61             e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
62             // acknowledge QuotaExceededError only if there's something already stored
63             storage.length !== 0;
64     }
67 //settings for time expiration on image hiding
68 function rebuildWindow(){
69     var style = document.createElement('style');
70     style.innerHTML = ".inputs{background-color:rgb(200,200,200);margin:5px 7px;width:100px;}";
71     document.body.appendChild(style);
73     var background_div = document.createElement("div");
74     background_div.setAttribute("style", "border:solid 1px black;position:fixed;width:100%;height:100%;background-color:rgba(200,200,200,0.3);top:0;left:0;display:none; z-index:9");
75     background_div.setAttribute("id", "rebuildBackground");
76     document.body.appendChild(background_div);
77     background_div.addEventListener("click", rebuildToggle);
79     var window_div = document.createElement("div");
80     window_div.setAttribute("style", "border:solid 1px black;position:fixed;width:400px;background-color:rgb(200,200,200);left:40%;top:20%;margin-bottom:0;  display:none; z-index:10");
81     window_div.setAttribute("id", "rebuildWindow");
83     var close_div = document.createElement("div");
84     close_div.setAttribute("style", "border:solid 1px black;position:absolute;width:25px;height:25px;background-color:rgba(255,100,90,0.9); right:3px;top:3px; z-index:10");
85     close_div.addEventListener("click", rebuildToggle);
86     window_div.appendChild(close_div);
88     var title_para = document.createElement("p");
89     title_para.setAttribute("style", "margin-left:5px;margin-top:5px");
90     var title_text = document.createTextNode("Rebuild Settings");
91     title_para.appendChild(title_text);
92     window_div.appendChild(title_para);
94     var container_div = document.createElement("div");
95     container_div.setAttribute("style","background-color:white;margin:0 0;padding:5px;");
96     window_div.appendChild(container_div);
98     var rebuild_label_local = document.createElement("label");
99     var rebuild_text_local = document.createTextNode("Use 4chan Archives: ");
100     rebuild_label_local.appendChild(rebuild_text_local);
101     container_div.appendChild(rebuild_label_local);
102     var rebuild_input_local = document.createElement("input");
103         rebuild_input_local.setAttribute("type", "radio");
104         rebuild_input_local.setAttribute("name", "ArchiveSettings");
105     rebuild_input_local.setAttribute("id", "OnsiteArchive");
106     container_div.appendChild(rebuild_input_local);
107     container_div.appendChild(rebuild_input_local);
108     container_div.appendChild(document.createElement("br"));
110         var rebuild_label_offsite = document.createElement("label");
111     var rebuild_text_offsite = document.createTextNode("Use Offsite Archives: ");
112     rebuild_label_offsite.appendChild(rebuild_text_offsite);
113     container_div.appendChild(rebuild_label_offsite);
114     var rebuild_input_offsite = document.createElement("input");
115         rebuild_input_offsite.setAttribute("type", "radio");
116         rebuild_input_offsite.setAttribute("name", "ArchiveSettings");
117     rebuild_input_offsite.setAttribute("id", "OffsiteArchive");
118     container_div.appendChild(rebuild_input_offsite);
119     container_div.appendChild(rebuild_input_offsite);
120     container_div.appendChild(document.createElement("br"));
122     var set_button = document.createElement("input");
123     set_button.setAttribute("type", "button");
124     set_button.setAttribute("id", "setTime");
125     set_button.setAttribute("value", "Set Archive");
126     set_button.addEventListener("click", function(){
127         if (storageAvailable('localStorage')) {
128                         var radio_options = document.getElementsByName("ArchiveSettings");
129                         for (var radio_input = 0 ; radio_input < radio_options.length; radio_input++)
130                                 if(radio_options[radio_input].checked){
131                                         localStorage.setItem("ArchiveType", radio_input);
132                                         if(radio_input == 0) use_offsite_archive = true;
133                                 }
134             rebuildToggle();
135         }
136     });
137     container_div.appendChild(set_button);
139     document.body.appendChild(window_div);
143 function rebuildToggle(){
144     if(window_displayed){
145         document.getElementById("rebuildWindow").style.display = "none";
146         document.getElementById("rebuildBackground").style.display = "none";
147         window_displayed = false;
148     }
149     else{
150         document.getElementById("rebuildWindow").style.display = "inline-block";
151         document.getElementById("rebuildBackground").style.display = "inline-block";
152         window_displayed = true;
153     }
156 function rebuildButton(){
157     var rebuild_button = document.createElement("input");
158     rebuild_button.setAttribute("Value", "Thread Rebuilder Settings");
159     rebuild_button.setAttribute("type", "button");
160     rebuild_button.setAttribute("style", "position:absolute;top:105px");
161     rebuild_button.addEventListener("click", rebuildWindow);
162     if(document.body === null){
163         setTimeout(rebuildButton, 30);
164     }
165     else{
166         document.body.appendChild(rebuild_button);
167         rebuild_button.addEventListener("click", rebuildToggle);
168     }
171 var enhance4ChanX = function(){
172         var qr_window = document.getElementById("qr");
174         if(document.getElementById("qrRebuilder") !== null) qr_window.removeChild(document.getElementById("qrRebuilder"));
176         var thread_rebuilder_table = document.createElement("TABLE");
177         thread_rebuilder_table.setAttribute("id", "qrRebuilder");
178         thread_rebuilder_table.setAttribute("style", "text-align:center");
179         qr_window.appendChild(thread_rebuilder_table);
181         var instruction_row = document.createElement("TR");
182         var top_row_nodes = [document.createElement("BR"),
183                                            document.createTextNode("Insert the thread number of the post to rebuild"),
184                                            document.createElement("BR"),
185                                            document.createTextNode("Must be in either the 4chan archives or archived.moe"),
186                                            document.createElement("BR"),
187                                           ];
188         top_row_nodes.forEach(
189                 function(node){
190                         instruction_row.appendChild(node);
191                 });
192         thread_rebuilder_table.appendChild(instruction_row);
194         var thread_row = document.createElement("TR");
195         var second_row_nodes = [
196                 document.createTextNode("Thread: "),
197                 document.createElement("INPUT"),
198                 document.createElement("INPUT"),
199         ];
200         second_row_nodes.forEach(
201                 function(node){
202                         thread_row.appendChild(node);
203                 });
204         thread_rebuilder_table.appendChild(thread_row);
206         second_row_nodes[1].setAttribute("ID", "threadInput");
207         second_row_nodes[1].setAttribute("style", "width:44.9%");
209         second_row_nodes[2].setAttribute("ID", "threadButton");
210         second_row_nodes[2].setAttribute("type", "button");
211         second_row_nodes[2].setAttribute("value", "Set Rebuild Queue");
213         second_row_nodes[2].addEventListener("click", function(){
214                 in_sequence = true;
215                 killAll();
216                 getThread(second_row_nodes[1].value);
217                 postID = setInterval(postRoutine, 1000);
218                 if(timeListen === undefined) timeListen = setInterval(timeListenerFunction, 1000);
219         });
222 var thread_data_length = 0;
223 var posts_created = 0;
224 var postID = "";
225 var postRoutine = function(){
226         if(semaphore == 0){
227                 semaphore++;
228                 thread_data_length = thread_data[0].length;
229                 fillID = setInterval(fillRoutine, 10);
230                 stopRoutine();
231         }
234 var stopRoutine = function(){
235         clearInterval(postID);
238 var fillID  = "";
239 var fillRoutine = function(){
240         if(posts_created >= thread_data_length) {semaphore_posts  = 0 ; stopFillRoutine();}
241         else if(semaphore_posts == 1){
242                 semaphore_posts--;
243                 createPost(thread_data[0][posts_created], thread_data[1][posts_created], thread_data[2][posts_created]);
244                 posts_created++;
245         }
248 var stopFillRoutine = function(){
249         clearInterval(fillID);
252 var setPropperLinking = function(text){
253         var search_regex = RegExp(">>\\d+", "g");
254         var result;
255         var index_old = -1;
256         var link_arr = Array();
257         while((result = search_regex.exec(text)) != null){
258                 var end_index = search_regex.lastIndex;
259                 var post_no = result.toString().replace(/>/g, "");
260                 link_arr.push([post_no, end_index]);
261         }
262 //hunt down the text of what it linked to
263 //Get the links inside of the origonal message to show text contents
264         var responding_text = Array();
265         if(use_offsite_archive)
266                 URL  = "https://www.archived.moe/_/api/chan/thread/?board=" + board + "&num=" + document.getElementById("threadInput").value;
267         else
268                 URL  = "https://a.4cdn.org/" + board + "/thread/" + document.getElementById("threadInput").value + ".json";
269                 var xhr = new GM_xmlhttpRequest(({
270                         method: "GET",
271                         url: URL,
272                         responseType : "json",
273                         onload: function(data){
274                                 if(use_offsite_archive)
275                                         data = data.response["" + document.getElementById("threadInput").value]["posts"];
276                                 else
277                                         data = data.response["posts"];
278                                 if(data == undefined){
279                                         alert("Invalid Thread ID: " + document.getElementById("threadInput").value + ". ");
280                                 }
281                                 else{
282                                         link_arr.forEach(function(link_item){
283                                                 for(var data_entry = 0 ; data_entry < data.length ; data_entry++){
284                                                         if(parseInt(link_item[0]) == parseInt(data[data_entry]["no"])){
285                                                                 if(use_offsite_archive)
286                                                                         responding_text.push([ [post_no, end_index], data[data_entry]["comment_processed"].replace(/(&gt;&gt;|https:\/\/www\.archived\.moe\/.*\/thread\/.*\/#)\d+/g, ""), link_item["media"]["safe_media_hash"] ]);
287                                                                 else
288                                                                         responding_text.push([ [post_no, end_index], data[data_entry]["com"].replace(/(&gt;&gt;|#p)\d+/g, ""), data[data_entry]["md5"] ]);
289                                                                 break;
290                                                         }
291                                                 }
292                                         });
294                                         var current_url = window.location.href;
295                                         var hash_index = current_url.lastIndexOf("#") != -1 ? current_url.lastIndexOf("#"):  window.location.href.length;
296                                         var current_thread = window.location.href.substring(current_url.lastIndexOf("/")+1, hash_index);
297                                         var current_url =  "https://a.4cdn.org/" + board + "/thread/" + current_thread + ".json";
298                                         //open current thread to hunt down the text found in links
299                                         var xhr = new GM_xmlhttpRequest(({
300                                                 method: "GET",
301                                                 url: current_url,
302                                                 responseType : "json",
303                                                 onload: function(data){
304                                                         data = data.response["posts"];
305                                                         if(data == undefined){
306                                                                 alert("Invalid Thread ID: " + document.getElementById("threadInput").value + ". ");
307                                                         }
308                                                         else{
309                                                                 responding_text.forEach(function(response_item){
310                                                                         for(var data_entry = 0 ; data_entry < data.length ; data_entry++){
311                                                                                 if((response_item[1] == data[data_entry]["com"].replace(/(&gt;&gt;|#p)\d+/g, "") || response_item[1] == null)
312                                                                                         && (response_item[2] == data[data_entry]["md5"] || response_item[2] == null)){
313                                                                                         var start_index = response_item[0][0].legth - response_item[0][1];
314                                                                                         text = text.substring(0, start_index) + ">>" + data[data_entry]["no"] + text.substring(response_item[0][1]);
315                                                                                                 break;
316                                                                                 }
317                                                                         }
318                                                                 });
319                                                                                         document.getElementById("qr").getElementsByTagName("TEXTAREA")[0].value = text;
320                                                                                         document.getElementById("add-post").click();
321                                                                                         semaphore_posts++;
322                                                         }
323                                                 }
324                                         }));
325                                 }
326                         }
327                 }));
331 //2) GET ARCHIVED THREAD
332 var getThread = function(threadNo){
333         thread_data = [[], [], [], []];
335         if(use_offsite_archive)
336                 URL  = "https://www.archived.moe/_/api/chan/thread/?board=" + board + "&num=" + document.getElementById("threadInput").value;
337         else
338                 URL  = "https://a.4cdn.org/" + board + "/thread/" + document.getElementById("threadInput").value + ".json";
339         var xhr = new GM_xmlhttpRequest(({
340                 method: "GET",
341                 url: URL,
342                 responseType : "json",
343                 onload: function(data){
344                         var starting_post = -1;
345                         if(use_offsite_archive){
346                                 starting_post = 0;
347                                 data = data.response["" + document.getElementById("threadInput").value];
348                         }
349                         else{
350                                 starting_post = 1;
351                                 data = data.response;
352                         }
353                         if(data == undefined){
354                                 alert("Invalid Thread ID: " + threadNo + ".\n4chan Archive ");
355                         }
356                         else{
357                                 if(use_offsite_archive) data["posts"] = Object.values(data["posts"]);
358                                 var len = data["posts"].length;
360                                 for(var post_number = starting_post ; post_number < len ; post_number++){
361                                         var comment = undefined;
362                                         if(use_offsite_archive)
363                                                 comment = data["posts"][post_number]["comment"];
364                                         else
365                                                 comment = data["posts"][post_number]["com"];
366                                         if(comment !== undefined && comment !== null)
367                                                 thread_data[0].push(comment);
368                                         else
369                                                 thread_data[0].push(-1);
371                                         var filename = undefined;
372                                         if(use_offsite_archive)
373                                                 if(data["posts"][post_number]["media"] !== null)
374                                                         filename = "" + data["posts"][post_number]["media"]["media_filename"];
375                                         else
376                                                 filename = "" + data["posts"][post_number]["tim"] + data["posts"][post_number]["ext"];
378                                         if(filename !== undefined && filename !== null && filename.indexOf("undefined") == -1)
379                                                 if(use_offsite_archive)
380                                                         if(data["posts"][post_number]["media"] !== null)
381                                                                 thread_data[1].push(data["posts"][post_number]["media"]["remote_media_link"]);
382                                                         else  thread_data[1].push(-1);
383                                                 else
384                                                         thread_data[1].push("https://i.4cdn.org/" + board + "/" + filename);
385                                         else  thread_data[1].push(-1);
387                                         if(use_offsite_archive)
388                                                 if(data["posts"][post_number]["media"] !== null)
389                                                         thread_data[2].push(data["posts"][post_number]["media"]["media_id"]);
390                                         else
391                                                 thread_data[2].push(data["posts"][post_number]["filename"]);
393                                         if(use_offsite_archive)
394                                                 thread_data[3].push(data["posts"][post_number]["num"]);
395                                         else
396                                                 thread_data[3].push(data["posts"][post_number]["no"]);
397                                 }
398                         }
399                         semaphore--;
400                 }
401         }));
403 //3) RIP POSTS AND IMAGES
404 var createPost = function(text, imageURL, imageName){
405         if(imageURL != -1){
406                 var response_type = "arraybuffer";
407                 if(use_offsite_archive) response_type = "text"
408                 var xhr = new GM_xmlhttpRequest(({
409                         method: "GET",
410                         url: imageURL,
411                         responseType : response_type,
412                         onload: function(response)
413                         {
414                                 if(use_offsite_archive){
415                                         var parser = new DOMParser();
416                                         var content_attribute = parser.parseFromString(response.response, "text/html").getElementsByTagName("META")[0].getAttribute("content");
417                                         var redirect_url = content_attribute.substring(content_attribute.indexOf("http"));
418                                         var xhr = new GM_xmlhttpRequest(({method:"GET", url: redirect_url, responseType:"arraybuffer",
419                                                 onload:function(response){
420                                                         inputImage(response, text,  imageURL, imageName);
421                                                 }
422                                         }));
423                                 }
424                                 else{
425                                         inputImage(response, text, imageURL, imageName);
426                                 }
427                         }
428                 }));
429         }
430         else{
431                 text = createPostComment(text);
432                 setPropperLinking(text);
433         }
436 function inputImage(response, text, imageURL, imageName){
437                                 var blob;
438                                 var ext = ".jpg";
439                                 if(imageURL.indexOf(".jpg") > -1){
440                                         blob = new Blob([response.response], {type:"image/jpeg"});
441                                         ext = ".jpg";
442                                 }
443                                 else if(imageURL.indexOf(".png") > -1){
444                                         blob = new Blob([response.response], {type:"image/png"});
445                                         ext = ".png";
446                                 }
447                                 else if(imageURL.indexOf(".gif") > -1){
448                                         blob = new Blob([response.response], {type:"image/gif"});
449                                         ext = ".gif";
450                                 }
451                                 else if(imageURL.indexOf(".webm") > -1){
452                                         blob = new Blob([response.response], {type:"video/webm"});
453                                         ext = ".webm";
454                                 }
456                                 var name = imageName + ext;
458                                 //SEND RESULTING RESPONSE TO 4CHANX FILES === QRSetFile
459                                 var detail = {file:blob, name:name};
460                                 if (typeof cloneInto === 'function') {
461                                         detail  = cloneInto(detail , document.defaultView);
462                                 }
464                                 document.dispatchEvent(new CustomEvent('QRSetFile', {bubbles:true, detail}));
466                                 if(text !== "" && text !== undefined && text !== -1) {
467                                         text = createPostComment(text);
468                                         setPropperLinking(text);
469                                 }
470                                 else{
471                                         document.getElementById("add-post").click();
472                                         semaphore_posts++;
473                                 }
476 //4) CREATE POST QUEUE
477 var createPostComment = function(text){
478         text = text.replace(/<a href="\/[a-zA-Z]+\/" class="quotelink">/g, "");
479         text = text.replace(/<span class="deadlink">/g, "");
481         var quote_regex = /<a href="#p[0-9]+" class="quotelink">&gt;&gt;[0-9]+/g;
482         var find = text.match(quote_regex);
483         if(find){
484                 find.forEach(function(match){
485                         var index_start = text.indexOf(match);
486                         var match_len = match.length;
487                         var index_len = index_start + match_len;
488                         var first_quote = match.indexOf('"');
489                         var second_quote = match.indexOf('"', first_quote + 1);
490                         var post_no = match.substring(first_quote + 3, second_quote);
492                         match = ">>" + post_no;
494                         text = text.substr(0, index_start) + match +  text.substr(index_len);
495                 });
496         }
498         text = text.replace(/<span class="quote">/g, "");
499         text = text.replace(/<br>/g, "\n");
500         text = text.replace(/&#039;/g, "'");
501         text = text.replace(/&gt;/g, ">");
502         text = text.replace(/<\/a>/g, "");
503         text = text.replace(/<wbr>/g, "");
504         text = text.replace(/<\/span>/g, "");
506         return text;
509 var checked = false;
510 var timeListenerFunction = function(){
511         var time = document.getElementById("qr-filename-container").nextSibling.value.replace(/[a-zA-Z]+/g, "");
512         if(time  <= 5){
513                 checked = false;
514         }
515         else if(time > 5){
516                 checked = true;
517         }
520 document.addEventListener('QRPostSuccessful', function(e) {
521         if(in_sequence){
522                 document.getElementById("dump-list").childNodes[1].click();
523                 setPropperLinking(document.getElementById("qr").getElementsByTagName("TEXTAREA")[0].value);
524         }
525 }, false);
528 function killAll(){
529         thread_data_length = 0;
530         posts_created = 0;
531         stopRoutine();
532         postID = "";
533         semaphore = 1;
534         semaphore_posts = 1;
535         stopFillRoutine();
536         fillID  = "";
537         thread_data = [['Comment'], ['Image URLs'], ['Image Names'] ,['Post No.']];
538         //CLEAR DUMP LIST
539         var qr_dumplist = document.getElementById("dump-list").childNodes;
540         var qr_dumplist_len = qr_dumplist.length;
541         var current_preview = 0;
542         while(qr_dumplist_len - current_preview > 1){
543                 qr_dumplist[0].firstChild.click();
544                 current_preview++;
545         }