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
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:
21 B) IT GOES FROM START TO END AND NOT FOUND
22 5) DO A 4CHANX CreateNotification***
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
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);
45 var event = new CustomEvent('CreateNotification', {bubbles: true, detail: detail});
46 document.dispatchEvent(event);
56 var top_page_max = 10000000;
57 var top_page = top_page_max;
59 var attemptCounter = attemptMax ;
66 var fail_state = false;
67 var tag_incorrect_state = false;
71 var timeout_functions = [];
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);
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;}";
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
102 var dButton = document.getElementById("dump-button");
103 if(dButton !== null){dButton.click();}
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.
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;
118 else if (imgURL == ""){
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!"),
144 instructionRow.appendChild(node);
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"),
180 secondRowNodes.forEach(
182 tagRow.appendChild(node);
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", "");
199 var tagNode = document.getElementById("tags");
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);},
218 function buttonClickFunction(){
219 for(var i = 0 ; i < timeout_functions.length; i++){
220 clearInterval(timeout_functions[i]);
222 tag_incorrect_state = false;
224 document.getElementById("tags").setAttribute("disabled", 1);
225 document.getElementById("imageButton").setAttribute("disabled", 1);
227 timeout_functions.push(setInterval(counterFunction, 1000));
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;
240 var cursorPos = tagNode.selectionStart - 1;
241 var currentTag = (function(){
242 var currentChar = tags.charAt(cursorPos);
244 rightMost = cursorPos;
245 while(currentChar != " " && currentChar != "" && currentChar !== undefined){
247 currentChar = tags.charAt(cursorPos + i);
248 if(currentChar != " " && currentChar != "") rightMost = cursorPos + i;
251 currentChar = tags.charAt(cursorPos);
253 leftMost = cursorPos;
254 while(currentChar != " " && currentChar != "" && currentChar !== undefined){
256 currentChar = tags.charAt(cursorPos - i);
257 if(currentChar != " " && currentChar != "") leftMost = cursorPos - i;
259 return tags.substring(leftMost, rightMost);
261 var xhr = new GM_xmlhttpRequest(({
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);
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);
282 a.addEventListener("click", function(evt){
283 tagArray[tagArray.indexOf(currentTag)] = this.textContent;
284 secondRowNodes[1].value = tagArray.join(" ");
289 oldVal = tagNode.value;
292 var setImage = function(){
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");
300 tags = tags.split(" ");
302 var xhr_image_load = new GM_xmlhttpRequest(({
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)
309 verifyTags(data, tags);
310 if(fail_state) return;
313 var endURL = ratingURL(tags, JSONTag);
315 var URL = setPostAndPage(endURL, tags);
317 //final check, sends final request after function or calls this function again
318 getJSON(URL, checkPageFromDanbooru, tags);
322 function verifyTags(data, tags){
323 data = data.response;
324 if(tags.length == 1 && tags[0] == "") JSONTag = [{"name":""}];
327 if(data.length == 0){
328 //TODO 4cx notification of error)
329 alert4ChanX("All tags incorrect", "error");
331 document.getElementById("timer").textContent = "";
332 document.getElementById("tags").removeAttribute("disabled");
333 document.getElementById("imageButton").removeAttribute("disabled");
336 else if(data.length != tags.length && !tag_incorrect_state){
337 tag_incorrect_state = true;
338 alert4ChanX("One Tag Incorrect", "warning");
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;
357 loopPost = number_of_posts;
362 var ratingURL = function(tags, data){
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"];
371 URL = "&utf8=%E2%9C%93&tags=" + "-rating%3Aexplicit" + "+" + data[data.length-1]["name"];
374 else if(document.getElementById("explicit").checked){
375 URL = "&utf8=%E2%9C%93&tags=" + "-rating%3Aquestionable" + "+" + data[data.length-1]["name"];
378 URL = "&utf8=%E2%9C%93&tags=" + "rating%3Asafe" + "+" + data[data.length-1]["name"];
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"];
386 URL = "&utf8=%E2%9C%93&tags=" + "rating%3Aquestionable" + "+" + data[data.length-1]["name"];
389 else if(document.getElementById("explicit").checked){
390 URL = "&utf8=%E2%9C%93&tags=" + "rating%3Aexplicit" + "+" + data[data.length-1]["name"];
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"];
399 //check if valid url location
400 var checkPageFromDanbooru = function(err, data, tags){
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");
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");
423 else if(data.length < number_of_posts+1 && attemptCounter > 0) {
424 if(top_page > pageNo){
425 top_page = pageNo + number_of_posts / 20;
428 document.getElementById("timer").textContent = attemptCounter + "|" + time;
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);
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");
449 var setImageFromDanbooru = function(err, data, tags){
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");
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;
473 else if(JSONPage["" + number_of_posts] == undefined){
480 var endURL = JSONPage["" + number_of_posts].file_url;
481 var URL = "https://danbooru.donmai.us" + endURL;
483 urlContainterFunction(URL);
487 if(endURL === undefined ||
488 endURL.indexOf(".mp4") > -1 || endURL.indexOf(".webm") > -1 || endURL.indexOf(".swf") > -1 || endURL.indexOf(".zip") > -1){
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"]){
503 else if(JSONPage["" + number_of_posts]["tag_string"].indexOf(tag) == -1){
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;
520 document.getElementById("timer").textContent = "...";
522 var xhr = new GM_xmlhttpRequest(({
525 responseType : "arraybuffer",
526 onload: function(response)
528 top_page = top_page_max;
529 attemptCounter = attemptMax;
530 document.getElementById("tags").removeAttribute("disabled");
531 document.getElementById("imageButton").removeAttribute("disabled");
533 clearInterval(intervalFunction);
535 var counter = document.getElementById("timer");
536 while(counter.hasChildNodes())
537 document.getElementById("timer").removeChild(document.getElementById("timer").lastChild);
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);
557 document.getElementById("dump-list").firstChild.click();
558 document.dispatchEvent(new CustomEvent('QRSetFile', {bubbles:true, detail}));
561 if(number_of_posts == 20){
573 var urlContainterFunction = function(url){
574 var urlBox = document.getElementById("urlContainer");
578 var counterFunction = function(){
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;
595 callback(null, xhr.response, extra);