single tag warning and number_of_posts bug.
[4Free-FSE.git] / Danbooru-Image-Adder.user.js
blobec8d4bad6ed587a10fd5c7e03575d2a0c70719fa
1 // ==UserScript==
2 // @name         Danbooru-Image-Adder
3 // @namespace    http://tampermonkey.net/
4 // @version      0.8.9
5 // @description  Add images to posts
6 // @author       ECHibiki /qa/
7 // @match *://boards.4chan.org/*
8 // @grant         GM_xmlhttpRequest
9 // @updateURL    https://github.com/ECHibiki/4chan-UserScripts/raw/master/Danbooru-Image-Adder.user.js
10 // @downloadURL  https://github.com/ECHibiki/4chan-UserScripts/raw/master/Danbooru-Image-Adder.user.js
11 // ==/UserScript==
14 I(CREATING THE POSTS)
15      1) DO JSON SEARCH FOR TAGS:
16             search[name]=TAG1,TAG2 &&&&&& search[order]=count
17      2) PICK THE SMALLEST ONES AS BASE
18      3) GENERATE A RANDOM NUMBER BETWEEN 0 AND FINAL_PAGE-1
19      4) ITTERATE THROUGH POSTS WITH TAGS UNTIL:
20             A) IT TIMESOUT
21             B) IT GOES FROM START TO END AND NOT FOUND
22      5) DO A 4CHANX CreateNotification***
24 II(AUTO COMPLETE)
25     1) ON FIELD CHANGE READ THE CURSOR LEFT AND DO A  search[name_matches]=n* & search[order]=count
26     2) PHONE STYLED AUTO COMPLETE
27     3) CLICK ON THE GIVEN ITEM TO ADD
28     4) GETS PLACED IN THE GIVEN FIELD
30 III(CHECKBOXES)
31    1) CHECKS FOR RATINGS
32    2) DROPDOWN FOR ORDER
34    3)***   (TODO)
37 //update 0.8.9 single tag warning and number_of_posts bug.
40 function alert4ChanX(message, type){
41     var detail = {type: type, content: message, lifetime: 10};
42     if (typeof cloneInto === 'function') {
43         detail = cloneInto(detail, document.defaultView);
44     }
45     var event = new CustomEvent('CreateNotification', {bubbles: true, detail: detail});
46     document.dispatchEvent(event);
49 var number_of_posts;
50 var pageNo;
51 var JSONPage;
52 var JSONTag;
53 var pagesLoaded = 0;
54 var smallestTag;
56 var top_page_max =  10000000;
57 var top_page = top_page_max;
58 var attemptMax = 20;
59 var attemptCounter = attemptMax ;
61 var imgURL = "";
62 var sendURL = "";
63 var oldVal = "";
65 var timeout = false;
66 var fail_state = false;
67 var tag_incorrect_state = false;
68 var time_max = 10;
69 var time = time_max;
70 var intervalFunction;
71 var timeout_functions = [];
73 var taggingFunction;
75 var interfaceSet = false;
77 //set listener to build interface in 4chanX
78 window.onload = function(){
79     var len = document.links.length;
80     for(var i = 0 ; i < len ; i++){
81         document.links[i].addEventListener("click", enhance4ChanX);
82     }
84     //ENHANCE DUMP TABS (COVER, 482PX - 482PX)
85     //DUMP LIST MAX-HEIGHT TO 490
87     document.getElementById("fourchanx-css").textContent += ".qr-preview { height: 482px; width: 482px; background-size: cover;}";
88     document.getElementById("fourchanx-css").textContent += "#dump-list { min-height: 400px; width: 509px;}";
92 //Alter 4chanX
93 var enhance4ChanX = function(){
94     var qrWindow = document.getElementById("qr");
95     //check if elements already made upon opening a qr window
96     if(document.getElementById("qrImages") !== null){
97         qrWindow.removeChild(document.getElementById("qrImages"));
98         clearInterval(taggingFunction);
99         //4chanx autodeletes images
100         clearImage();
101     }
102     var dButton = document.getElementById("dump-button");
103     if(dButton !== null){dButton.click();}
104     else{return;}
106     var dList = document.getElementById("dump-list");
107     var filenamecontainer = document.getElementById("qr-filename-container");
109     //used for setting and unsetting high resolution thumbs for dump list.
110     var BGImg = "";
111     var oldBGImg = "";
112     var observer = new MutationObserver(function(mutate){
113         BGImg = dList.firstChild.style.backgroundImage;
114         if(BGImg !== oldBGImg && imgURL !== ""){
115             dList.firstChild.style.backgroundImage = "url(" + imgURL + ")";
116             oldBGImg = dList.firstChild.style.backgroundImage;
117         }
118         else if (imgURL == ""){
119         }
120     });
121     observer.observe(dList , {attributes: true,subtree:true, childList: true, characterData: true });
122     //make the image clear button clear images;
123     document.getElementById("qr-filerm").addEventListener("click", clearImage);
125     //image setting html elements.
126     var qrTable = document.createElement("TABLE");
127     qrTable.setAttribute("id", "qrImages");
128     qrTable.setAttribute("style", "text-align:center");
129     qrWindow.appendChild(qrTable);
130     qrWindow.appendChild(document.createElement("BR"));
132     var instructionRow = document.createElement("TR");
133     var topRowNodes = [document.createElement("BR"),
134                        document.createTextNode("Insert Tags to search from danbooru bellow."),
135                        document.createElement("BR"),
136                        document.createTextNode("Do Not Use \"order:\" tags"),
137                        document.createElement("BR"),
138                        document.createTextNode("Do Not Use \"rating:\" tags"),
139                        document.createElement("BR"),
140                        document.createTextNode("For more speed uncheck all boxes!"),
141                       ];
142     topRowNodes.forEach(
143         function(node){
144             instructionRow.appendChild(node);
145         });
146     qrTable.appendChild(instructionRow);
148     var optionsRow = document.createElement("TR");
149     optionsRow.setAttribute("ID", "or");
150     optionsRow.setAttribute("style", "margin:5px;");
151     qrTable.appendChild(optionsRow);
152     var checkSafe = document.createElement("INPUT");
153     checkSafe.setAttribute("id", "safe");
154     checkSafe.setAttribute("type", "checkbox");
155     var safeText  = document.createTextNode("Safe");
156     var checkQuest= document.createElement("INPUT");
157     checkQuest.setAttribute("id", "questionable");
158     checkQuest.setAttribute("type", "checkbox");
159     var questText= document.createTextNode("Questionable");
160     var checkExplicit = document.createElement("INPUT");
161     checkExplicit.setAttribute("id", "explicit");
162     checkExplicit.setAttribute("type", "checkbox");
163     var explText = document.createTextNode("Explicit");
165     optionsRow.appendChild(safeText);
166     optionsRow.appendChild(checkSafe);
167     optionsRow.appendChild(questText);
168     optionsRow.appendChild(checkQuest);
169     optionsRow.appendChild(explText);
170     optionsRow.appendChild(checkExplicit);
172     var tagRow = document.createElement("TR");
173     var secondRowNodes = [
174         document.createTextNode("Tags: "),
175         document.createElement("INPUT"),
176         document.createElement("INPUT"),
177         document.createElement("A"),
178         document.createElement("INPUT"),
179     ];
180     secondRowNodes.forEach(
181         function(node){
182             tagRow.appendChild(node);
183         });
184     qrTable.appendChild(tagRow);
186     var autoCompleteRow = document.createElement("TR");
187     autoCompleteRow.setAttribute("ID", "acr");
188     autoCompleteRow.setAttribute("style", "margin:5px;");
189     qrTable.appendChild(autoCompleteRow);
191     secondRowNodes[1].setAttribute("ID", "tags");
192     secondRowNodes[1].setAttribute("style", "width:44.9%");
193     secondRowNodes[3].setAttribute("ID", "timer");
194     secondRowNodes[3].setAttribute("style", "width:20%;margin:0 5px");
195     secondRowNodes[4].setAttribute("ID", "urlContainer");
196     secondRowNodes[4].setAttribute("style", "width:20%;margin:0 5px");
197     secondRowNodes[4].setAttribute("disabled", "");
198     var tags = "";
199     var tagNode = document.getElementById("tags");
200     var rightMost;
201     var leftMost;
203     secondRowNodes[2].setAttribute("ID", "imageButton");
204     secondRowNodes[2].setAttribute("type", "button");
205     secondRowNodes[2].setAttribute("value", "Set Image");
208     //event listener logic
209     secondRowNodes[2].addEventListener("click", buttonClickFunction);
211     //ping ever 0.5s for changes
212     taggingFunction = setInterval(
213         function(){setTagInterface(tagNode, autoCompleteRow, secondRowNodes);},
214         500);
218 function buttonClickFunction(){
219         for(var i = 0 ; i < timeout_functions.length; i++){
220                 clearInterval(timeout_functions[i]);
221         }
222         tag_incorrect_state = false;
223         timeout = false;
224         document.getElementById("tags").setAttribute("disabled", 1);
225         document.getElementById("imageButton").setAttribute("disabled", 1);
226         time = time_max;
227         timeout_functions.push(setInterval(counterFunction, 1000));
228         setImage();
231 function clearImage(){
232     var dList = document.getElementById("dump-list");
233     dList.firstChild.style.backgroundImage = "url()";//trigger mutation event
234     imgURL = ""; //get mutation to set to dead
237 var setTagInterface =  function(tagNode, autoCompleteRow, secondRowNodes){
238     tags = tagNode.value;
239     if(oldVal !== tags){
240         var cursorPos = tagNode.selectionStart - 1;
241         var currentTag =  (function(){
242             var currentChar = tags.charAt(cursorPos);
243             var i = 0;
244             rightMost = cursorPos;
245             while(currentChar != " " && currentChar != "" && currentChar !== undefined){
246                 i++;
247                 currentChar = tags.charAt(cursorPos + i);
248                 if(currentChar != " " && currentChar != "") rightMost = cursorPos + i;
249             }
250             rightMost += 1;
251             currentChar = tags.charAt(cursorPos);
252             i = 0;
253             leftMost = cursorPos;
254             while(currentChar != " " && currentChar != ""  && currentChar !== undefined){
255                 i++;
256                 currentChar = tags.charAt(cursorPos - i);
257                 if(currentChar != " " && currentChar != "") leftMost = cursorPos - i;
258             }
259             return tags.substring(leftMost, rightMost);
260         })();
261         var xhr = new GM_xmlhttpRequest(({
262             method: "GET",
263             url: "https://danbooru.donmai.us/tags.json?search[name_matches]=" + currentTag + "*&search[order]=count",
264             responseType : "json",
265             onload: function(data){
266                 data = data.response;
267                 var tagArray = tags.split(" ");
268                 while (autoCompleteRow.hasChildNodes()) {
269                     autoCompleteRow.removeChild(autoCompleteRow.lastChild);
270                 }
271                 for (var i = 0 ; i < 5 ; i++){
272                     var a  = document.createElement("A");
273                     a.setAttribute("style", "padding:5px;padding-top:0px;font-size:15px;font-weight:bold;border:1px solid black;");
274                     var tagText = data["" + i];
275                     if(tagText == "" || tagText === undefined) break;
276                     tagText = tagText["name"];
278                     var aTxt  = document.createTextNode(data[i]["name"]);
280                     autoCompleteRow.appendChild(a);
281                     a.appendChild(aTxt);
282                     a.addEventListener("click", function(evt){
283                         tagArray[tagArray.indexOf(currentTag)] = this.textContent;
284                         secondRowNodes[1].value = tagArray.join(" ");
285                     });
286                 }
287             }}));
288     }
289     oldVal =  tagNode.value;
292 var setImage = function(){
293     //Set image tags.
294     var tags = document.getElementById("tags").value;
296     //TODO 4cx notification of warning(no error)
297     if(tags.indexOf(":") > -1) {
298         alert4ChanX("Character ':' not used for file characteristic searches", "warning");
299     }
300     tags = tags.split(" ");
302     var xhr_image_load = new GM_xmlhttpRequest(({
303         method: "GET",
304         //returns a list of all tags and their properties
305         url: "https://danbooru.donmai.us/tags.json?search[name]=" + tags.join() + "&search[order]=count",
306         responseType : "json",
307         onload: function(data)
308         {
309             verifyTags(data, tags);
310                         if(fail_state) return;
311                         
312             //set the end
313             var endURL = ratingURL(tags, JSONTag);
315             var URL = setPostAndPage(endURL, tags);
316             sendURL = URL;
317             //final check, sends final request after function or calls this function again
318             getJSON(URL, checkPageFromDanbooru, tags);
319         }}));
322 function verifyTags(data, tags){
323     data = data.response;
324     if(tags.length == 1 && tags[0] == "") JSONTag = [{"name":""}];
325     else JSONTag = data;
326         fail_state = false;
327     if(data.length == 0){
328         //TODO 4cx notification of error)
329         alert4ChanX("All tags incorrect", "error");
330                 fail_state = true;
331                 document.getElementById("timer").textContent = "";
332                 document.getElementById("tags").removeAttribute("disabled");
333                 document.getElementById("imageButton").removeAttribute("disabled");
334         return;
335     }
336         else if(data.length != tags.length && !tag_incorrect_state){
337                 tag_incorrect_state = true;
338                 alert4ChanX("One Tag Incorrect", "warning");
339         }
340     //tag size. Smallest tag is placed at bottom of JSON
341     smallestTag = parseInt(data[data.length-1]["post_count"]);
344 var setPostAndPage = function(endURL, tags){
345         console.log(smallestTag);
346     number_of_posts = Math.floor(Math.random() * 100) % (smallestTag % 20 + 1);
347         console.log(number_of_posts);
348     if(top_page != top_page_max) smallestTag = top_page * 20;
349     //1000 is max page search limit
350     if(smallestTag == 0) smallestTag = 100;
351     pageNo = ((Math.floor(Math.random() * 10000)) % Math.ceil(smallestTag / 20)) % 1000;
353     var URL = "https://danbooru.donmai.us/posts.json?page=" + pageNo + endURL;
355     loopOne = false;
356     loopPage = pageNo;
357     loopPost = number_of_posts;
358     sameTrigger = 0;
359     return URL;
362 var ratingURL = function(tags, data){
363     var URL = "";
364     if(document.getElementById("safe").checked){
365         if(document.getElementById("questionable").checked){
366             if(document.getElementById("explicit").checked){
367                 if(data.length > 1)  URL =  "&utf8=%E2%9C%93&tags=" + data[data.length-2]["name"] + "+" + data[data.length-1]["name"];
368                 else  URL =  "&utf8=%E2%9C%93&tags=" + data[data.length-1]["name"];
369             }
370             else{
371                 URL =  "&utf8=%E2%9C%93&tags=" + "-rating%3Aexplicit" + "+" + data[data.length-1]["name"];
372             }
373         }
374         else if(document.getElementById("explicit").checked){
375             URL = "&utf8=%E2%9C%93&tags=" + "-rating%3Aquestionable" + "+" + data[data.length-1]["name"];
376         }
377         else{
378             URL = "&utf8=%E2%9C%93&tags=" + "rating%3Asafe" + "+" + data[data.length-1]["name"];
379         }
380     }
381     else if(document.getElementById("questionable").checked){
382         if(document.getElementById("explicit").checked){
383             URL =  "&utf8=%E2%9C%93&tags=" + "-rating%3Asafe" + "+" + data[data.length-1]["name"];
384         }
385         else{
386             URL =  "&utf8=%E2%9C%93&tags=" + "rating%3Aquestionable" + "+" + data[data.length-1]["name"];
387         }
388     }
389     else if(document.getElementById("explicit").checked){
390         URL =  "&utf8=%E2%9C%93&tags=" + "rating%3Aexplicit" + "+" + data[data.length-1]["name"];
391     }
392     else{
393         if(data.length > 1)  URL =  "&utf8=%E2%9C%93&tags=" + data[data.length-2]["name"] + "+" + data[data.length-1]["name"];
394         else  URL = "&utf8=%E2%9C%93&tags=" + data[data.length-1]["name"];
395     }
396     return URL;
399 //check if valid url location
400 var checkPageFromDanbooru = function(err, data, tags){
401     if (err != null) {
402         console.log('Something went wrong: ' + err);
403         alert4ChanX("Danbooru Server Did Not Perform request -- Error: "  + err, "error");
404         top_page = top_page_max;
405         attemptCounter = attemptMax;
406         document.getElementById("timer").textContent = "";
407         document.getElementById("tags").removeAttribute("disabled");
408         document.getElementById("imageButton").removeAttribute("disabled");
409         pageNo = 1;
410         number_of_posts = 0;
411     }
412     else {
413         if(smallestTag == 0 && attemptCounter <= 0){
414             alert4ChanX("No Results", "error");
415             top_page = top_page_max;
416             attemptCounter = attemptMax;
417             document.getElementById("timer").textContent = "";
418             document.getElementById("tags").removeAttribute("disabled");
419             document.getElementById("imageButton").removeAttribute("disabled");
420             return;
421         }
422         //redo
423         else if(data.length < number_of_posts+1 && attemptCounter > 0) {
424             if(top_page > pageNo){
425                 top_page = pageNo + number_of_posts / 20;
426             }
427             attemptCounter--;
428             document.getElementById("timer").textContent = attemptCounter + "|" + time;
429             setImage();
430         }
431         //process page
432         else if (attemptCounter > 0){
433             //ALL PARAMETERS WILL BE RESET INSIDE JSON
434             document.getElementById("timer").textContent =  attemptCounter + "|" + time;
435             getJSON(sendURL, setImageFromDanbooru, tags);
436         }
437         else{
438             alert4ChanX("Not found", "error");
439             top_page = top_page_max;
440             attemptCounter = attemptMax;
441             document.getElementById("timer").textContent = "";
442             document.getElementById("tags").removeAttribute("disabled");
443             document.getElementById("imageButton").removeAttribute("disabled"); 
444             return;
445         }
446     }
449 var setImageFromDanbooru = function(err, data, tags){
450     if (err != null) {
451         console.log('Something went wrong: ' + err);
452         alert4ChanX("Danbooru Server Did Not Perform request -- Error: "  + err, "error");
453         top_page = top_page_max;
454         attemptCounter = attemptMax;
455         document.getElementById("timer").textContent = "";
456         document.getElementById("tags").removeAttribute("disabled");
457         document.getElementById("imageButton").removeAttribute("disabled");
459     }
460     else {
461         JSONPage = data;
462         if(timeout){
463             alert4ChanX("timeout after " + time +" seconds", "error");
464             clearInterval(counterFunction);
465             document.getElementById("timer").textContent = "";
466             document.getElementById("tags").removeAttribute("disabled");
467             document.getElementById("imageButton").removeAttribute("disabled");
468             top_page = top_page_max;
469             attemptCounter = attemptMax;
471             return;
472         }
473         else if(JSONPage["" + number_of_posts] == undefined){
474             top_page = pageNo;
475             attemptCounter--;
476             setImage();
477             return;
478         }
480         var endURL = JSONPage["" + number_of_posts].file_url;
481         var URL = "https://danbooru.donmai.us" + endURL;
483         urlContainterFunction(URL);
485         var fail = false;
487         if(endURL === undefined ||
488            endURL.indexOf(".mp4") > -1 || endURL.indexOf(".webm") > -1 || endURL.indexOf(".swf") > -1 || endURL.indexOf(".zip") > -1){
489             top_page = pageNo;
490             attemptCounter--;
491             setImage();
492             return;
493         }
494         else{
495             tags.forEach(function(tag){
496                 if(tag.indexOf("order:") > -1);
497                 else if(tag.indexOf("rating:") > -1){
498                     if(tag.charAt(7) !== JSONPage["" + number_of_posts]["rating"]){
499                         fail = true;
500                         return;
501                     }
502                 }
503                 else if(JSONPage["" + number_of_posts]["tag_string"].indexOf(tag) == -1){
504                     fail = true;
505                     return;
506                 }
507             });
508         }
509         if(fail){
510             top_page = pageNo;
511             attemptCounter--;
512             setImage();
513             return;
514         }
515         else{
516             if(JSONPage["" + number_of_posts].file_size >= 4000000){
517                 var endURL = JSONPage["" + number_of_posts].large_file_url;
518                 var URL = "https://danbooru.donmai.us" + endURL;
519             }
520             document.getElementById("timer").textContent = "...";
521             imgURL = URL;
522             var xhr = new GM_xmlhttpRequest(({
523                 method: "GET",
524                 url: URL,
525                 responseType : "arraybuffer",
526                 onload: function(response)
527                 {
528                     top_page = top_page_max;
529                     attemptCounter = attemptMax;
530                     document.getElementById("tags").removeAttribute("disabled");
531                     document.getElementById("imageButton").removeAttribute("disabled");
532                     loopOne = false;
533                     clearInterval(intervalFunction);
534                     time = time_max;
535                     var counter = document.getElementById("timer");
536                     while(counter.hasChildNodes())
537                         document.getElementById("timer").removeChild(document.getElementById("timer").lastChild);
540                     var blob;
541                     if(endURL.indexOf(".jpg") > -1)
542                         blob = new Blob([response.response], {type:"image/jpeg"});
543                     else if(endURL.indexOf(".png") > -1)
544                         blob = new Blob([response.response], {type:"image/png"});
545                     else if(endURL.indexOf(".gif") > -1)
546                         blob = new Blob([response.response], {type:"image/gif"});
549                     var name = endURL.replace(/(data|cached)/g, "");
550                     name = name.replace(/\//g, "");
552                     //SEND RESULTING RESPONSE TO 4CHANX FILES === QRSetFile
553                     var detail = {file:blob, name:name};
554                     if (typeof cloneInto === 'function') {
555                         detail  = cloneInto(detail , document.defaultView);
556                     }
557                     document.getElementById("dump-list").firstChild.click();
558                     document.dispatchEvent(new CustomEvent('QRSetFile', {bubbles:true, detail}));
560                     number_of_posts++;
561                     if(number_of_posts == 20){
562                         number_of_posts = 0;
563                         pageNo++;
564                     }
565                 }
566             }));
567             //break condition
568             return;
569         }
570     }
573 var urlContainterFunction = function(url){
574     var urlBox = document.getElementById("urlContainer");
575     urlBox.value = url;
578 var counterFunction  = function(){
579     if(!timeout){
580         time--;
581         if(time < 0){
582             timeout = true;
583             time = time_max;
584         }
585     }
588 var getJSON = function(url, callback, extra) {
589     var xhr = new XMLHttpRequest();
590     xhr.open('GET', url, true);
591     xhr.responseType = 'json';
592     xhr.onload = function() {
593         var status = xhr.status;
594         if (status == 200) {
595             callback(null, xhr.response, extra);
596         } else {
597             callback(status);
598         }
599     };
600     xhr.send();