2 // @name Danbooru-Image-Adder
3 // @namespace http://tampermonkey.net/
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 // @run-at document-end
16 1) DO JSON SEARCH FOR TAGS:
17 search[name]=TAG1,TAG2 &&&&&& search[order]=count
18 2) PICK THE SMALLEST ONES AS BASE
19 3) GENERATE A RANDOM NUMBER BETWEEN 0 AND FINAL_PAGE-1
20 4) ITTERATE THROUGH POSTS WITH TAGS UNTIL:
22 B) IT GOES FROM START TO END AND NOT FOUND
23 5) DO A 4CHANX CreateNotification***
26 1) ON FIELD CHANGE READ THE CURSOR LEFT AND DO A search[name_matches]=n* & search[order]=count
27 2) PHONE STYLED AUTO COMPLETE
28 3) CLICK ON THE GIVEN ITEM TO ADD
29 4) GETS PLACED IN THE GIVEN FIELD
38 //update 0.8.9 single tag warning and number_of_posts bug.
41 function alert4ChanX(message, type){
42 var detail = {type: type, content: message, lifetime: 10};
43 if (typeof cloneInto === 'function') {
44 detail = cloneInto(detail, document.defaultView);
46 var event = new CustomEvent('CreateNotification', {bubbles: true, detail: detail});
47 document.dispatchEvent(event);
50 var number_of_posts = 0;
57 var top_page_max = 10000000;
58 var top_page = top_page_max;
60 var attemptCounter = attemptMax ;
67 var fail_state = false;
68 var tag_incorrect_state = false;
72 var timeout_functions = [];
77 var interfaceSet = false;
79 //set listener to build interface in 4chanX
80 window.onload = function(){
81 var len = document.links.length;
82 for(var i = 0 ; i < len ; i++){
83 document.links[i].addEventListener("click", enhance4ChanX);
86 //ENHANCE DUMP TABS (COVER, 482PX - 482PX)
87 //DUMP LIST MAX-HEIGHT TO 490
89 document.getElementById("fourchanx-css").textContent += ".qr-preview { height: 482px; width: 482px; background-size: cover;}";
90 document.getElementById("fourchanx-css").textContent += "#dump-list { min-height: 400px; width: 509px;}";
95 var enhance4ChanX = function(){
96 var qrWindow = document.getElementById("qr");
97 //check if elements already made upon opening a qr window
98 if(document.getElementById("qrImages") !== null){
99 qrWindow.removeChild(document.getElementById("qrImages"));
100 clearInterval(taggingFunction);
101 //4chanx autodeletes images
104 var dButton = document.getElementById("dump-button");
105 if(dButton !== null){dButton.click();}
108 var dList = document.getElementById("dump-list");
109 var filenamecontainer = document.getElementById("qr-filename-container");
111 //used for setting and unsetting high resolution thumbs for dump list.
114 var observer = new MutationObserver(function(mutate){
115 BGImg = dList.firstChild.style.backgroundImage;
116 if(BGImg !== oldBGImg && imgURL !== ""){
117 dList.firstChild.style.backgroundImage = "url(" + imgURL + ")";
118 oldBGImg = dList.firstChild.style.backgroundImage;
120 else if (imgURL == ""){
123 observer.observe(dList , {attributes: true,subtree:true, childList: true, characterData: true });
124 //make the image clear button clear images;
125 document.getElementById("qr-filerm").addEventListener("click", clearImage);
127 //image setting html elements.
128 var qrTable = document.createElement("TABLE");
129 qrTable.setAttribute("id", "qrImages");
130 qrTable.setAttribute("style", "text-align:center");
131 qrWindow.appendChild(qrTable);
132 qrWindow.appendChild(document.createElement("BR"));
134 var instructionRow = document.createElement("TR");
135 var topRowNodes = [document.createElement("BR"),
136 document.createTextNode("Insert Tags to search from danbooru bellow."),
137 document.createElement("BR"),
138 document.createTextNode("Do Not Use \"order:\" tags"),
139 document.createElement("BR"),
140 document.createTextNode("Do Not Use \"rating:\" tags"),
141 document.createElement("BR"),
142 document.createTextNode("For more speed uncheck all boxes!"),
146 instructionRow.appendChild(node);
148 qrTable.appendChild(instructionRow);
150 var optionsRow = document.createElement("TR");
151 optionsRow.setAttribute("ID", "or");
152 optionsRow.setAttribute("style", "margin:5px;");
153 qrTable.appendChild(optionsRow);
154 var checkSafe = document.createElement("INPUT");
155 checkSafe.setAttribute("id", "safe");
156 checkSafe.setAttribute("type", "checkbox");
157 var safeText = document.createTextNode("Safe");
158 var checkQuest= document.createElement("INPUT");
159 checkQuest.setAttribute("id", "questionable");
160 checkQuest.setAttribute("type", "checkbox");
161 var questText= document.createTextNode("Questionable");
162 var checkExplicit = document.createElement("INPUT");
163 checkExplicit.setAttribute("id", "explicit");
164 checkExplicit.setAttribute("type", "checkbox");
165 var explText = document.createTextNode("Explicit");
167 optionsRow.appendChild(safeText);
168 optionsRow.appendChild(checkSafe);
169 optionsRow.appendChild(questText);
170 optionsRow.appendChild(checkQuest);
171 optionsRow.appendChild(explText);
172 optionsRow.appendChild(checkExplicit);
174 var tagRow = document.createElement("TR");
175 var secondRowNodes = [
176 document.createTextNode("Tags: "),
177 document.createElement("INPUT"),
178 document.createElement("INPUT"),
179 document.createElement("A"),
180 document.createElement("INPUT"),
182 secondRowNodes.forEach(
184 tagRow.appendChild(node);
186 qrTable.appendChild(tagRow);
188 var autoCompleteRow = document.createElement("TR");
189 autoCompleteRow.setAttribute("ID", "acr");
190 autoCompleteRow.setAttribute("style", "margin:5px;");
191 qrTable.appendChild(autoCompleteRow);
193 secondRowNodes[1].setAttribute("ID", "tags");
194 secondRowNodes[1].setAttribute("style", "width:44.9%");
195 secondRowNodes[3].setAttribute("ID", "timer");
196 secondRowNodes[3].setAttribute("style", "width:20%;margin:0 5px");
197 secondRowNodes[4].setAttribute("ID", "urlContainer");
198 secondRowNodes[4].setAttribute("style", "width:20%;margin:0 5px");
199 secondRowNodes[4].setAttribute("disabled", "");
201 var tagNode = document.getElementById("tags");
205 secondRowNodes[2].setAttribute("ID", "imageButton");
206 secondRowNodes[2].setAttribute("type", "button");
207 secondRowNodes[2].setAttribute("value", "Set Image");
210 //event listener logic
211 secondRowNodes[2].addEventListener("click", buttonClickFunction);
213 //ping ever 0.5s for changes
214 taggingFunction = setInterval(
215 function(){setTagInterface(tagNode, autoCompleteRow, secondRowNodes);},
220 function buttonClickFunction(){
222 primed_for_fail = false;
223 for(var i = 0 ; i < timeout_functions.length; i++){
224 clearInterval(timeout_functions[i]);
226 tag_incorrect_state = false;
228 document.getElementById("tags").setAttribute("disabled", 1);
229 document.getElementById("imageButton").setAttribute("disabled", 1);
231 timeout_functions.push(setInterval(counterFunction, 1000));
235 function clearImage(){
236 var dList = document.getElementById("dump-list");
237 dList.firstChild.style.backgroundImage = "url()";//trigger mutation event
238 imgURL = ""; //get mutation to set to dead
241 var setTagInterface = function(tagNode, autoCompleteRow, secondRowNodes){
242 tags = tagNode.value;
244 var cursorPos = tagNode.selectionStart - 1;
245 var currentTag = (function(){
246 var currentChar = tags.charAt(cursorPos);
248 rightMost = cursorPos;
249 while(currentChar != " " && currentChar != "" && currentChar !== undefined){
251 currentChar = tags.charAt(cursorPos + i);
252 if(currentChar != " " && currentChar != "") rightMost = cursorPos + i;
255 currentChar = tags.charAt(cursorPos);
257 leftMost = cursorPos;
258 while(currentChar != " " && currentChar != "" && currentChar !== undefined){
260 currentChar = tags.charAt(cursorPos - i);
261 if(currentChar != " " && currentChar != "") leftMost = cursorPos - i;
263 return tags.substring(leftMost, rightMost);
265 var xhr = new GM_xmlhttpRequest(({
267 url: "https://danbooru.donmai.us/tags.json?search[name_matches]=" + currentTag + "*&search[order]=count",
268 responseType : "json",
269 onload: function(data){
270 data = data.response;
271 var tagArray = tags.split(" ");
272 while (autoCompleteRow.hasChildNodes()) {
273 autoCompleteRow.removeChild(autoCompleteRow.lastChild);
275 for (var i = 0 ; i < 5 ; i++){
276 var a = document.createElement("A");
277 a.setAttribute("style", "padding:5px;padding-top:0px;font-size:15px;font-weight:bold;border:1px solid black;");
278 var tagText = data["" + i];
279 if(tagText == "" || tagText === undefined) break;
280 tagText = tagText["name"];
282 var aTxt = document.createTextNode(data[i]["name"]);
284 autoCompleteRow.appendChild(a);
286 a.addEventListener("click", function(evt){
287 tagArray[tagArray.indexOf(currentTag)] = this.textContent;
288 secondRowNodes[1].value = tagArray.join(" ");
293 oldVal = tagNode.value;
296 var setImage = function(){
298 var tags = document.getElementById("tags").value;
300 //TODO 4cx notification of warning(no error)
301 if(tags.indexOf(":") > -1) {
302 alert4ChanX("Character ':' not used for file characteristic searches", "warning");
304 tags = tags.split(" ");
306 var xhr_image_load = new GM_xmlhttpRequest(({
308 //returns a list of all tags and their properties
309 url: "https://danbooru.donmai.us/tags.json?search[name]=" + tags.join() + "&search[order]=count",
310 responseType : "json",
311 onload: function(data)
313 verifyTags(data, tags);
314 if(fail_state) return;
317 var endURL = ratingURL(tags, JSONTag);
319 var URL = setPostAndPage(endURL, tags);
321 //final check, sends final request after function or calls this function again
322 getJSON(URL, checkPageFromDanbooru, tags);
326 function verifyTags(data, tags){
327 data = data.response;
328 if(tags.length == 1 && tags[0] == "") JSONTag = [{"name":""}];
331 if(data.length == 0){
332 //TODO 4cx notification of error)
333 alert4ChanX("All tags incorrect", "error");
335 document.getElementById("timer").textContent = "";
336 document.getElementById("tags").removeAttribute("disabled");
337 document.getElementById("imageButton").removeAttribute("disabled");
340 else if(data.length != tags.length && !tag_incorrect_state){
341 tag_incorrect_state = true;
342 if(document.getElementById("tags").value.trim() == "") alert4ChanX("No Tags", "info");
343 else alert4ChanX("One Tag Incorrect", "warning");
345 //tag size. Smallest tag is placed at bottom of JSON
346 smallestTag = parseInt(data[data.length-1]["post_count"]);
349 var setPostAndPage = function(endURL, tags){
351 if(number_of_posts > 0)
354 if(top_page != top_page_max) smallestTag = top_page * 20;
355 if(smallestTag == 0) smallestTag = 100;
358 pageNo = ((Math.floor(Math.random() * 10000)) % Math.ceil(smallestTag / 20)) % 1000; //1000 is max page search limit
359 tries.forEach(function(page){
361 primed_for_fail = true;
365 else if(page == pageNo){
370 } while(!escape_cond);
373 var URL = "https://danbooru.donmai.us/posts.json?page=" + pageNo + endURL;
377 loopPost = number_of_posts;
382 var ratingURL = function(tags, data){
384 if(document.getElementById("safe").checked){
385 if(document.getElementById("questionable").checked){
386 if(document.getElementById("explicit").checked){
387 if(data.length > 1) URL = "&utf8=%E2%9C%93&tags=" + data[data.length-2]["name"] + "+" + data[data.length-1]["name"];
388 else URL = "&utf8=%E2%9C%93&tags=" + data[data.length-1]["name"];
391 URL = "&utf8=%E2%9C%93&tags=" + "-rating%3Aexplicit" + "+" + data[data.length-1]["name"];
394 else if(document.getElementById("explicit").checked){
395 URL = "&utf8=%E2%9C%93&tags=" + "-rating%3Aquestionable" + "+" + data[data.length-1]["name"];
398 URL = "&utf8=%E2%9C%93&tags=" + "rating%3Asafe" + "+" + data[data.length-1]["name"];
401 else if(document.getElementById("questionable").checked){
402 if(document.getElementById("explicit").checked){
403 URL = "&utf8=%E2%9C%93&tags=" + "-rating%3Asafe" + "+" + data[data.length-1]["name"];
406 URL = "&utf8=%E2%9C%93&tags=" + "rating%3Aquestionable" + "+" + data[data.length-1]["name"];
409 else if(document.getElementById("explicit").checked){
410 URL = "&utf8=%E2%9C%93&tags=" + "rating%3Aexplicit" + "+" + data[data.length-1]["name"];
413 if(data.length > 1) URL = "&utf8=%E2%9C%93&tags=" + data[data.length-2]["name"] + "+" + data[data.length-1]["name"];
414 else URL = "&utf8=%E2%9C%93&tags=" + data[data.length-1]["name"];
419 //check if valid url location
420 var primed_for_fail = false;
421 var checkPageFromDanbooru = function(err, data, tags){
423 console.log('Something went wrong: ' + err);
424 alert4ChanX("Danbooru Server Did Not Perform request -- Error: " + err, "error");
425 top_page = top_page_max;
426 attemptCounter = attemptMax;
427 document.getElementById("timer").textContent = "";
428 document.getElementById("tags").removeAttribute("disabled");
429 document.getElementById("imageButton").removeAttribute("disabled");
431 //number_of_posts = 0;
435 alert4ChanX("No Results", "error");
436 top_page = top_page_max;
437 attemptCounter = attemptMax;
438 document.getElementById("timer").textContent = "";
439 document.getElementById("tags").removeAttribute("disabled");
440 document.getElementById("imageButton").removeAttribute("disabled");
444 else if(data.length < number_of_posts+1 && attemptCounter > 0) {
445 if(top_page > pageNo){
446 top_page = pageNo + number_of_posts / 20;
449 document.getElementById("timer").textContent = attemptCounter + "|" + time;
453 else if (attemptCounter > 0){
454 //ALL PARAMETERS WILL BE RESET INSIDE JSON
455 document.getElementById("timer").textContent = attemptCounter + "|" + time;
456 getJSON(sendURL, setImageFromDanbooru, tags);
459 alert4ChanX("Not found", "error");
460 top_page = top_page_max;
461 attemptCounter = attemptMax;
462 document.getElementById("timer").textContent = "";
463 document.getElementById("tags").removeAttribute("disabled");
464 document.getElementById("imageButton").removeAttribute("disabled");
470 var setImageFromDanbooru = function(err, data, tags){
472 console.log('Something went wrong: ' + err);
473 alert4ChanX("Danbooru Server Did Not Perform request -- Error: " + err, "error");
474 top_page = top_page_max;
475 attemptCounter = attemptMax;
476 document.getElementById("timer").textContent = "";
477 document.getElementById("tags").removeAttribute("disabled");
478 document.getElementById("imageButton").removeAttribute("disabled");
483 var image_found = false;
484 for (number_of_posts = 0; number_of_posts < 20 ; number_of_posts++){
486 alert4ChanX("timeout after " + time +" seconds", "error");
487 clearInterval(counterFunction);
488 document.getElementById("timer").textContent = "";
489 document.getElementById("tags").removeAttribute("disabled");
490 document.getElementById("imageButton").removeAttribute("disabled");
491 top_page = top_page_max;
492 attemptCounter = attemptMax;
495 else if(JSONPage["" + number_of_posts] == undefined){
502 var endURL = JSONPage["" + number_of_posts].file_url;
503 var URL = "https://danbooru.donmai.us" + endURL;
505 urlContainterFunction(URL);
509 if(endURL === undefined ||
510 endURL.indexOf(".mp4") > -1 || endURL.indexOf(".webm") > -1 || endURL.indexOf(".swf") > -1 || endURL.indexOf(".zip") > -1){
511 // top_page = pageNo;
518 tags.forEach(function(tag){
519 if(tag.indexOf("order:") > -1);
520 else if(tag.indexOf("rating:") > -1){
521 if(tag.charAt(7) !== JSONPage["" + number_of_posts]["rating"]){
526 else if(JSONPage["" + number_of_posts]["tag_string"].indexOf(tag) == -1){
533 // top_page = pageNo;
540 if(JSONPage["" + number_of_posts].file_size >= 4000000){
541 var endURL = JSONPage["" + number_of_posts].large_file_url;
542 var URL = "https://danbooru.donmai.us" + endURL;
544 document.getElementById("timer").textContent = "...";
546 var xhr = new GM_xmlhttpRequest(({
549 responseType : "arraybuffer",
550 onload: function(response)
552 top_page = top_page_max;
553 attemptCounter = attemptMax;
554 document.getElementById("tags").removeAttribute("disabled");
555 document.getElementById("imageButton").removeAttribute("disabled");
557 clearInterval(intervalFunction);
559 var counter = document.getElementById("timer");
560 while(counter.hasChildNodes())
561 document.getElementById("timer").removeChild(document.getElementById("timer").lastChild);
564 if(endURL.indexOf(".jpg") > -1)
565 blob = new Blob([response.response], {type:"image/jpeg"});
566 else if(endURL.indexOf(".png") > -1)
567 blob = new Blob([response.response], {type:"image/png"});
568 else if(endURL.indexOf(".gif") > -1)
569 blob = new Blob([response.response], {type:"image/gif"});
572 var name = endURL.replace(/(data|cached)/g, "");
573 name = name.replace(/\//g, "");
575 //SEND RESULTING RESPONSE TO 4CHANX FILES === QRSetFile
576 var detail = {file:blob, name:name};
577 if (typeof cloneInto === 'function') {
578 detail = cloneInto(detail , document.defaultView);
580 document.getElementById("dump-list").firstChild.click();
581 document.dispatchEvent(new CustomEvent('QRSetFile', {bubbles:true, detail}));
586 number_of_posts = 9001;
597 var urlContainterFunction = function(url){
598 var urlBox = document.getElementById("urlContainer");
602 var counterFunction = function(){
612 var getJSON = function(url, callback, extra) {
613 var xhr = new XMLHttpRequest();
614 xhr.open('GET', url, true);
615 xhr.responseType = 'json';
616 xhr.onload = function() {
617 var status = xhr.status;
619 callback(null, xhr.response, extra);