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
14 function alert4ChanX(message
, type
){
15 var detail
= {type
: type
, content
: message
, lifetime
: 10};
16 if (typeof cloneInto
=== 'function') {
17 detail
= cloneInto(detail
, document
.defaultView
);
19 var event
= new CustomEvent('CreateNotification', {bubbles
: true, detail
: detail
});
20 document
.dispatchEvent(event
);
23 var number_of_posts
= 0;
30 var top_page_max
= 10000000;
31 var top_page
= top_page_max
;
33 var attemptCounter
= attemptMax
;
40 var fail_state
= false;
41 var tag_incorrect_state
= false;
45 var timeout_functions
= [];
47 var previous_images
= [];
50 var interfaceSet
= false;
52 //set listener to build interface in 4chanX
53 window
.onload = function(){
54 var len
= document
.links
.length
;
55 for(var i
= 0 ; i
< len
; i
++){
56 var class_name
= document
.links
[i
].parentNode
.className
;
57 if(class_name
== "postNum desktop" || class_name
== "qr-link-container"
58 || class_name
== "brackets-wrap qr-link-container-bottom")
59 document
.links
[i
].addEventListener("click", enhance4ChanX
);
62 //ENHANCE DUMP TABS (COVER, 482PX - 482PX)
63 //DUMP LIST MAX-HEIGHT TO 490
65 document
.getElementById("fourchanx-css").textContent
+= ".qr-preview { height: 482px; width: 482px; background-size: cover;}";
66 document
.getElementById("fourchanx-css").textContent
+= "#dump-list { min-height: 400px; width: 509px;}";
71 var enhance4ChanX = function(){
72 var qrWindow
= document
.getElementById("qr");
73 //check if elements already made upon opening a qr window
74 if(document
.getElementById("qrImages") !== null){
75 qrWindow
.removeChild(document
.getElementById("qrImages"));
76 clearInterval(taggingFunction
);
77 //4chanx autodeletes images
80 var dButton
= document
.getElementById("dump-button");
81 if(dButton
!== null){dButton
.click();}
84 var dList
= document
.getElementById("dump-list");
85 var filenamecontainer
= document
.getElementById("qr-filename-container");
87 //used for setting and unsetting high resolution thumbs for dump list.
90 var observer
= new MutationObserver(function(mutate
){
91 BGImg
= dList
.firstChild
.style
.backgroundImage
;
92 if(BGImg
!== oldBGImg
&& imgURL
!== ""){
93 dList
.firstChild
.style
.backgroundImage
= "url(" + imgURL
+ ")";
94 oldBGImg
= dList
.firstChild
.style
.backgroundImage
;
96 else if (imgURL
== ""){
99 observer
.observe(dList
, {attributes
: true,subtree
:true, childList
: true, characterData
: true });
100 //make the image clear button clear images;
101 document
.getElementById("qr-filerm").addEventListener("click", clearImage
);
103 //image setting html elements.
104 var qrTable
= document
.createElement("TABLE");
105 qrTable
.setAttribute("id", "qrImages");
106 qrTable
.setAttribute("style", "text-align:center");
107 qrWindow
.appendChild(qrTable
);
108 //qrWindow.appendChild(document.createElement("BR"));
110 var instructionRow
= document
.createElement("TR");
111 var topRowNodes
= [document
.createElement("BR"),
112 document
.createTextNode("Insert Tags to search from danbooru bellow."),
113 document
.createElement("BR"),
114 document
.createTextNode("Do Not Use \"order:\" tags"),
115 document
.createElement("BR"),
116 document
.createTextNode("Do Not Use \"rating:\" tags"),
117 document
.createElement("BR"),
118 document
.createTextNode("For more speed uncheck all boxes!"),
122 instructionRow
.appendChild(node
);
124 qrTable
.appendChild(instructionRow
);
126 var optionsRow
= document
.createElement("TR");
127 optionsRow
.setAttribute("ID", "or");
128 optionsRow
.setAttribute("style", "margin:5px;");
129 qrTable
.appendChild(optionsRow
);
130 var checkSafe
= document
.createElement("INPUT");
131 checkSafe
.setAttribute("id", "safe");
132 checkSafe
.setAttribute("type", "checkbox");
133 var safeText
= document
.createTextNode("Safe");
134 var checkQuest
= document
.createElement("INPUT");
135 checkQuest
.setAttribute("id", "questionable");
136 checkQuest
.setAttribute("type", "checkbox");
137 var questText
= document
.createTextNode("Questionable");
138 var checkExplicit
= document
.createElement("INPUT");
139 checkExplicit
.setAttribute("id", "explicit");
140 checkExplicit
.setAttribute("type", "checkbox");
141 var explText
= document
.createTextNode("Explicit");
143 optionsRow
.appendChild(safeText
);
144 optionsRow
.appendChild(checkSafe
);
145 optionsRow
.appendChild(questText
);
146 optionsRow
.appendChild(checkQuest
);
147 optionsRow
.appendChild(explText
);
148 optionsRow
.appendChild(checkExplicit
);
150 var tagRow
= document
.createElement("TR");
151 var secondRowNodes
= [
152 document
.createTextNode("Tags: "),
153 document
.createElement("INPUT"),
154 document
.createElement("INPUT"),
155 document
.createElement("A"),
156 document
.createElement("INPUT"),
158 secondRowNodes
.forEach(
160 tagRow
.appendChild(node
);
162 qrTable
.appendChild(tagRow
);
164 var autoCompleteRow
= document
.createElement("TR");
165 autoCompleteRow
.setAttribute("ID", "acr");
166 autoCompleteRow
.setAttribute("style", "margin:5px;");
167 qrTable
.appendChild(autoCompleteRow
);
169 secondRowNodes
[1].setAttribute("ID", "tags");
170 secondRowNodes
[1].setAttribute("style", "width:44.9%");
171 secondRowNodes
[3].setAttribute("ID", "timer");
172 secondRowNodes
[3].setAttribute("style", "width:20%;margin:0 5px");
173 secondRowNodes
[4].setAttribute("ID", "urlContainer");
174 secondRowNodes
[4].setAttribute("style", "width:20%;margin:0 5px");
175 secondRowNodes
[4].setAttribute("disabled", "");
177 var tagNode
= document
.getElementById("tags");
181 secondRowNodes
[2].setAttribute("ID", "imageButton");
182 secondRowNodes
[2].setAttribute("type", "button");
183 secondRowNodes
[2].setAttribute("value", "Set Image");
186 //event listener logic
187 secondRowNodes
[2].addEventListener("click", buttonClickFunction
);
189 //ping ever 0.5s for changes
190 taggingFunction
= setInterval(
191 function(){setTagInterface(tagNode
, autoCompleteRow
, secondRowNodes
);},
196 function buttonClickFunction(){
198 primed_for_fail
= false;
199 for(var i
= 0 ; i
< timeout_functions
.length
; i
++){
200 clearInterval(timeout_functions
[i
]);
202 tag_incorrect_state
= false;
204 document
.getElementById("tags").setAttribute("disabled", 1);
205 document
.getElementById("imageButton").setAttribute("disabled", 1);
207 timeout_functions
.push(setInterval(counterFunction
, 1000));
211 function clearImage(){
212 var dList
= document
.getElementById("dump-list");
213 dList
.firstChild
.style
.backgroundImage
= "url()";//trigger mutation event
214 imgURL
= ""; //get mutation to set to dead
217 var setTagInterface = function(tagNode
, autoCompleteRow
, secondRowNodes
){
218 tags
= tagNode
.value
;
220 previous_images
= [];
222 var cursorPos
= tagNode
.selectionStart
- 1;
223 var currentTag
= (function(){
224 var currentChar
= tags
.charAt(cursorPos
);
226 rightMost
= cursorPos
;
227 while(currentChar
!= " " && currentChar
!= "" && currentChar
!== undefined){
229 currentChar
= tags
.charAt(cursorPos
+ i
);
230 if(currentChar
!= " " && currentChar
!= "") rightMost
= cursorPos
+ i
;
233 currentChar
= tags
.charAt(cursorPos
);
235 leftMost
= cursorPos
;
236 while(currentChar
!= " " && currentChar
!= "" && currentChar
!== undefined){
238 currentChar
= tags
.charAt(cursorPos
- i
);
239 if(currentChar
!= " " && currentChar
!= "") leftMost
= cursorPos
- i
;
241 return tags
.substring(leftMost
, rightMost
);
243 var xhr
= new GM_xmlhttpRequest(({
245 url
: "https://danbooru.donmai.us/tags.json?search[name_matches]=" + currentTag
+ "*&search[order]=count",
246 responseType
: "json",
247 onload: function(data
){
248 data
= data
.response
;
249 var tagArray
= tags
.split(" ");
250 while (autoCompleteRow
.hasChildNodes()) {
251 autoCompleteRow
.removeChild(autoCompleteRow
.lastChild
);
253 var qr_width
= document
.getElementById("qr").offsetWidth
;
254 //console.log(qr_width);
255 for (var i
= 0 ; i
< 5 ; i
++){
256 var a
= document
.createElement("A");
257 a
.setAttribute("style", "padding:5px;padding-top:0px;font-size:15px;font-weight:bold;border:1px solid black;");
258 var tagText
= data
["" + i
];
259 if(tagText
== "" || tagText
=== undefined) break;
260 tagText
= tagText
["name"];
262 var aTxt
= document
.createTextNode(data
[i
]["name"]);
264 autoCompleteRow
.appendChild(a
);
266 if(autoCompleteRow
.offsetWidth
> qr_width
){
267 //console.log(autoCompleteRow.offsetWidth);
268 autoCompleteRow
.removeChild(a
);
269 var br
= document
.createElement("BR");
270 br
.setAttribute("STYLE", "line-height:32px;");
271 autoCompleteRow
.appendChild(br
);
272 autoCompleteRow
.appendChild(a
);
275 a
.addEventListener("click", function(evt
){
276 tagArray
[tagArray
.indexOf(currentTag
)] = this.textContent
;
277 secondRowNodes
[1].value
= tagArray
.join(" ");
282 oldVal
= tagNode
.value
;
285 var setImage = function(){
287 var tags
= document
.getElementById("tags").value
;
289 //TODO 4cx notification of warning(no error)
290 if(tags
.indexOf(":") > -1) {
291 alert4ChanX("Character ':' not used for file characteristic searches", "warning");
293 tags
= tags
.split(" ");
295 var xhr_image_load
= new GM_xmlhttpRequest(({
297 //returns a list of all tags and their properties
298 url
: "https://danbooru.donmai.us/tags.json?search[name]=" + tags
.join() + "&search[order]=count",
299 responseType
: "json",
300 onload: function(data
)
302 verifyTags(data
, tags
);
303 if(fail_state
) return;
306 var endURL
= ratingURL(tags
, JSONTag
);
308 var URL
= setPostAndPage(endURL
, tags
);
310 //final check, sends final request after function or calls this function again
311 getJSON(URL
, checkPageFromDanbooru
, tags
);
315 function verifyTags(data
, tags
){
316 data
= data
.response
;
317 if(tags
.length
== 1 && tags
[0] == "") JSONTag
= [{"name":""}];
320 if(data
.length
== 0){
321 //TODO 4cx notification of error)
322 alert4ChanX("All tags incorrect", "error");
324 document
.getElementById("timer").textContent
= "";
325 document
.getElementById("tags").removeAttribute("disabled");
326 document
.getElementById("imageButton").removeAttribute("disabled");
329 else if(data
.length
!= tags
.length
&& !tag_incorrect_state
){
330 tag_incorrect_state
= true;
331 if(document
.getElementById("tags").value
.trim() == "") alert4ChanX("No Tags", "info");
332 else alert4ChanX("One Tag Incorrect", "warning");
334 //tag size. Smallest tag is placed at bottom of JSON
335 smallestTag
= parseInt(data
[data
.length
-1]["post_count"]);
338 var setPostAndPage = function(endURL
, tags
){
340 if(number_of_posts
> 0)
343 if(top_page
!= top_page_max
) smallestTag
= top_page
* 20;
344 if(smallestTag
== 0) smallestTag
= 100;
347 pageNo
= ((Math
.floor(Math
.random() * 10000)) % Math
.ceil(smallestTag
/ 20)) % 1000; //1000 is max page search limit
348 tries
.forEach(function(page
){
350 primed_for_fail
= true;
354 else if(page
== pageNo
){
359 } while(!escape_cond
);
362 var URL
= "https://danbooru.donmai.us/posts.json?page=" + pageNo
+ endURL
;
366 loopPost
= number_of_posts
;
371 var ratingURL = function(tags
, data
){
373 if(document
.getElementById("safe").checked
){
374 if(document
.getElementById("questionable").checked
){
375 if(document
.getElementById("explicit").checked
){
376 if(data
.length
> 1) URL
= "&utf8=%E2%9C%93&tags=" + data
[data
.length
-2]["name"] + "+" + data
[data
.length
-1]["name"];
377 else URL
= "&utf8=%E2%9C%93&tags=" + data
[data
.length
-1]["name"];
380 URL
= "&utf8=%E2%9C%93&tags=" + "-rating%3Aexplicit" + "+" + data
[data
.length
-1]["name"];
383 else if(document
.getElementById("explicit").checked
){
384 URL
= "&utf8=%E2%9C%93&tags=" + "-rating%3Aquestionable" + "+" + data
[data
.length
-1]["name"];
387 URL
= "&utf8=%E2%9C%93&tags=" + "rating%3Asafe" + "+" + data
[data
.length
-1]["name"];
390 else if(document
.getElementById("questionable").checked
){
391 if(document
.getElementById("explicit").checked
){
392 URL
= "&utf8=%E2%9C%93&tags=" + "-rating%3Asafe" + "+" + data
[data
.length
-1]["name"];
395 URL
= "&utf8=%E2%9C%93&tags=" + "rating%3Aquestionable" + "+" + data
[data
.length
-1]["name"];
398 else if(document
.getElementById("explicit").checked
){
399 URL
= "&utf8=%E2%9C%93&tags=" + "rating%3Aexplicit" + "+" + data
[data
.length
-1]["name"];
402 if(data
.length
> 1) URL
= "&utf8=%E2%9C%93&tags=" + data
[data
.length
-2]["name"] + "+" + data
[data
.length
-1]["name"];
403 else URL
= "&utf8=%E2%9C%93&tags=" + data
[data
.length
-1]["name"];
409 //check if valid url location
410 var primed_for_fail
= false;
411 var checkPageFromDanbooru = function(err
, data
, tags
){
413 console
.log('Something went wrong: ' + err
);
414 alert4ChanX("Danbooru Server Did Not Perform request -- Error: " + err
, "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");
421 //number_of_posts = 0;
424 //console.log(previous_images);
426 //console.log(data.length);
427 var duplicate
= false;
428 previous_images
.forEach(function(item
){
429 if(item
[0] == pageNo
&& item
[1] == number_of_posts
){
435 //console.log(number_of_posts);
437 while(duplicate
== true || data
.length
< number_of_posts
);
439 alert4ChanX("No Results: All found for tags \"" + document
.getElementById("tags").value
+ "\"", "error");
440 top_page
= top_page_max
;
441 attemptCounter
= attemptMax
;
442 document
.getElementById("timer").textContent
= "";
443 document
.getElementById("tags").removeAttribute("disabled");
444 document
.getElementById("imageButton").removeAttribute("disabled");
448 else if((data
.length
< number_of_posts
+1) && attemptCounter
> 0) {
449 if(top_page
> pageNo
){
450 top_page
= pageNo
+ number_of_posts
/ 20;
453 document
.getElementById("timer").textContent
= attemptCounter
+ "|" + time
;
457 else if (attemptCounter
> 0){
458 //ALL PARAMETERS WILL BE RESET INSIDE JSON
459 document
.getElementById("timer").textContent
= attemptCounter
+ "|" + time
;
460 getJSON(sendURL
, setImageFromDanbooru
, tags
);
463 alert4ChanX("Not found", "error");
464 top_page
= top_page_max
;
465 attemptCounter
= attemptMax
;
466 document
.getElementById("timer").textContent
= "";
467 document
.getElementById("tags").removeAttribute("disabled");
468 document
.getElementById("imageButton").removeAttribute("disabled");
474 var setImageFromDanbooru = function(err
, data
, tags
){
476 console
.log('Something went wrong: ' + err
);
477 alert4ChanX("Danbooru Server Did Not Perform request -- Error: " + err
, "error");
478 top_page
= top_page_max
;
479 attemptCounter
= attemptMax
;
480 document
.getElementById("timer").textContent
= "";
481 document
.getElementById("tags").removeAttribute("disabled");
482 document
.getElementById("imageButton").removeAttribute("disabled");
487 var image_found
= false;
488 for (number_of_posts
= number_of_posts
; number_of_posts
< 20 ; number_of_posts
++){
490 alert4ChanX("timeout after " + time
+" seconds", "error");
491 clearInterval(counterFunction
);
492 document
.getElementById("timer").textContent
= "";
493 document
.getElementById("tags").removeAttribute("disabled");
494 document
.getElementById("imageButton").removeAttribute("disabled");
495 top_page
= top_page_max
;
496 attemptCounter
= attemptMax
;
499 else if(JSONPage
["" + number_of_posts
] == undefined){
506 var endURL
= JSONPage
["" + number_of_posts
].file_url
;
507 var URL
= "https://danbooru.donmai.us" + endURL
;
508 if(endURL
.indexOf("raikou2.donmai.us") > -1)
511 urlContainterFunction(URL
);
515 if(endURL
=== undefined ||
516 endURL
.indexOf(".mp4") > -1 || endURL
.indexOf(".webm") > -1 || endURL
.indexOf(".swf") > -1 || endURL
.indexOf(".zip") > -1){
517 // top_page = pageNo;
524 tags
.forEach(function(tag
){
525 if(tag
.indexOf("order:") > -1);
526 else if(tag
.indexOf("rating:") > -1){
527 if(tag
.charAt(7) !== JSONPage
["" + number_of_posts
]["rating"]){
532 else if(JSONPage
["" + number_of_posts
]["tag_string"].indexOf(tag
) == -1){
539 // top_page = pageNo;
546 if(JSONPage
["" + number_of_posts
].file_size
>= 4000000){
547 var endURL
= JSONPage
["" + number_of_posts
].large_file_url
;
548 var URL
= "https://danbooru.donmai.us" + endURL
;
549 if(endURL
.indexOf("raikou2.donmai.us") > -1)
553 document
.getElementById("timer").textContent
= "...";
555 var xhr
= new GM_xmlhttpRequest(({
558 responseType
: "arraybuffer",
559 onload: function(response
)
561 top_page
= top_page_max
;
562 attemptCounter
= attemptMax
;
563 document
.getElementById("tags").removeAttribute("disabled");
564 document
.getElementById("imageButton").removeAttribute("disabled");
566 clearInterval(intervalFunction
);
568 var counter
= document
.getElementById("timer");
569 while(counter
.hasChildNodes())
570 document
.getElementById("timer").removeChild(document
.getElementById("timer").lastChild
);
573 if(endURL
.indexOf(".jpg") > -1)
574 blob
= new Blob([response
.response
], {type
:"image/jpeg"});
575 else if(endURL
.indexOf(".png") > -1)
576 blob
= new Blob([response
.response
], {type
:"image/png"});
577 else if(endURL
.indexOf(".gif") > -1)
578 blob
= new Blob([response
.response
], {type
:"image/gif"});
581 var name
= endURL
.replace(/(data|cached)/g, "");
582 name
= name
.replace(/\//g, "");
584 //SEND RESULTING RESPONSE TO 4CHANX FILES === QRSetFile
585 var detail
= {file
:blob
, name
:name
};
586 if (typeof cloneInto
=== 'function') {
587 detail
= cloneInto(detail
, document
.defaultView
);
589 document
.getElementById("dump-list").firstChild
.click();
590 document
.dispatchEvent(new CustomEvent('QRSetFile', {bubbles
:true, detail
}));
596 //SET PAGE&POST AS FOUND
597 previous_images
.push([pageNo
, number_of_posts
]);
598 number_of_posts
= 9001;
609 var urlContainterFunction = function(url
){
610 var urlBox
= document
.getElementById("urlContainer");
614 var counterFunction = function(){
624 var getJSON = function(url
, callback
, extra
) {
625 var xhr
= new XMLHttpRequest();
626 xhr
.open('GET', url
, true);
627 xhr
.responseType
= 'json';
628 xhr
.onload = function() {
629 var status
= xhr
.status
;
631 callback(null, xhr
.response
, extra
);