Bottom link bug
[4Free-FSE.git] / Danbooru-Image-Adder.user.js
blobe1da5c87a76331c3bf86ecd1d28952e7a9c1bd20
1 // ==UserScript==
2 // @name Danbooru-Image-Adder
3 // @namespace http://tampermonkey.net/
4 // @version 1.4
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
12 // ==/UserScript==
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;
24 var pageNo;
25 var JSONPage;
26 var JSONTag;
27 var pagesLoaded = 0;
28 var smallestTag;
30 var top_page_max = 10000000;
31 var top_page = top_page_max;
32 var attemptMax = 20;
33 var attemptCounter = attemptMax ;
35 var imgURL = "";
36 var sendURL = "";
37 var oldVal = "";
39 var timeout = false;
40 var fail_state = false;
41 var tag_incorrect_state = false;
42 var time_max = 10;
43 var time = time_max;
44 var intervalFunction;
45 var timeout_functions = [];
46 var tries = Array();
47 var previous_images = [];
48 var taggingFunction;
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;}";
70 //Alter 4chanX
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
78 clearImage();
80 var dButton = document.getElementById("dump-button");
81 if(dButton !== null){dButton.click();}
82 else{return;}
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.
88 var BGImg = "";
89 var oldBGImg = "";
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 == ""){
98 });
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!"),
120 topRowNodes.forEach(
121 function(node){
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(
159 function(node){
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", "");
176 var tags = "";
177 var tagNode = document.getElementById("tags");
178 var rightMost;
179 var leftMost;
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);},
192 500);
196 function buttonClickFunction(){
197 tries = Array();
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;
203 timeout = false;
204 document.getElementById("tags").setAttribute("disabled", 1);
205 document.getElementById("imageButton").setAttribute("disabled", 1);
206 time = time_max;
207 timeout_functions.push(setInterval(counterFunction, 1000));
208 setImage();
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;
219 if(oldVal !== tags){
220 previous_images = [];
222 var cursorPos = tagNode.selectionStart - 1;
223 var currentTag = (function(){
224 var currentChar = tags.charAt(cursorPos);
225 var i = 0;
226 rightMost = cursorPos;
227 while(currentChar != " " && currentChar != "" && currentChar !== undefined){
228 i++;
229 currentChar = tags.charAt(cursorPos + i);
230 if(currentChar != " " && currentChar != "") rightMost = cursorPos + i;
232 rightMost += 1;
233 currentChar = tags.charAt(cursorPos);
234 i = 0;
235 leftMost = cursorPos;
236 while(currentChar != " " && currentChar != "" && currentChar !== undefined){
237 i++;
238 currentChar = tags.charAt(cursorPos - i);
239 if(currentChar != " " && currentChar != "") leftMost = cursorPos - i;
241 return tags.substring(leftMost, rightMost);
242 })();
243 var xhr = new GM_xmlhttpRequest(({
244 method: "GET",
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);
265 a.appendChild(aTxt);
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(" ");
280 }}));
282 oldVal = tagNode.value;
285 var setImage = function(){
286 //Set image tags.
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(({
296 method: "GET",
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;
305 //set the end
306 var endURL = ratingURL(tags, JSONTag);
308 var URL = setPostAndPage(endURL, tags);
309 sendURL = URL;
310 //final check, sends final request after function or calls this function again
311 getJSON(URL, checkPageFromDanbooru, tags);
312 }}));
315 function verifyTags(data, tags){
316 data = data.response;
317 if(tags.length == 1 && tags[0] == "") JSONTag = [{"name":""}];
318 else JSONTag = data;
319 fail_state = false;
320 if(data.length == 0){
321 //TODO 4cx notification of error)
322 alert4ChanX("All tags incorrect", "error");
323 fail_state = true;
324 document.getElementById("timer").textContent = "";
325 document.getElementById("tags").removeAttribute("disabled");
326 document.getElementById("imageButton").removeAttribute("disabled");
327 return;
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){
339 //posts
340 if(number_of_posts > 0)
341 number_of_posts = 0;
342 //page
343 if(top_page != top_page_max) smallestTag = top_page * 20;
344 if(smallestTag == 0) smallestTag = 100;
346 escape_cond = true;
347 pageNo = ((Math.floor(Math.random() * 10000)) % Math.ceil(smallestTag / 20)) % 1000; //1000 is max page search limit
348 tries.forEach(function(page){
349 if(page == 0){
350 primed_for_fail = true;
351 escape_cond = true;
352 return;
354 else if(page == pageNo){
355 escape_cond = false;
356 return;
359 } while(!escape_cond);
360 tries.push(pageNo);
362 var URL = "https://danbooru.donmai.us/posts.json?page=" + pageNo + endURL;
364 loopOne = false;
365 loopPage = pageNo;
366 loopPost = number_of_posts;
367 sameTrigger = 0;
368 return URL;
371 var ratingURL = function(tags, data){
372 var URL = "";
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"];
379 else{
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"];
386 else{
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"];
394 else{
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"];
401 else{
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"];
405 //console.log(URL);
406 return URL;
409 //check if valid url location
410 var primed_for_fail = false;
411 var checkPageFromDanbooru = function(err, data, tags){
412 if (err != null) {
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");
420 pageNo = 0;
421 //number_of_posts = 0;
423 else {
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){
430 duplicate = true;
431 number_of_posts++;
432 return;
435 //console.log(number_of_posts);
437 while(duplicate == true || data.length < number_of_posts);
438 if(primed_for_fail){
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");
445 return;
447 //redo
448 else if((data.length < number_of_posts+1) && attemptCounter > 0) {
449 if(top_page > pageNo){
450 top_page = pageNo + number_of_posts / 20;
452 attemptCounter--;
453 document.getElementById("timer").textContent = attemptCounter + "|" + time;
454 setImage();
456 //process page
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);
462 else{
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");
469 return;
474 var setImageFromDanbooru = function(err, data, tags){
475 if (err != null) {
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");
485 else {
486 JSONPage = data;
487 var image_found = false;
488 for (number_of_posts = number_of_posts; number_of_posts < 20 ; number_of_posts++){
489 if(timeout){
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;
497 return;
499 else if(JSONPage["" + number_of_posts] == undefined){
500 top_page = pageNo;
501 attemptCounter--;
502 setImage();
503 return;
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)
509 URL = endURL;
511 urlContainterFunction(URL);
513 var fail = false;
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;
518 // attemptCounter--;
519 // setImage();
520 // return;
521 continue;
523 else{
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"]){
528 fail = true;
529 return;
532 else if(JSONPage["" + number_of_posts]["tag_string"].indexOf(tag) == -1){
533 fail = true;
534 return;
538 if(fail){
539 // top_page = pageNo;
540 // attemptCounter--;
541 // setImage();
542 // return;
543 continue;
545 else{
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)
550 URL = endURL;
553 document.getElementById("timer").textContent = "...";
554 imgURL = URL;
555 var xhr = new GM_xmlhttpRequest(({
556 method: "GET",
557 url: URL,
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");
565 loopOne = false;
566 clearInterval(intervalFunction);
567 time = time_max;
568 var counter = document.getElementById("timer");
569 while(counter.hasChildNodes())
570 document.getElementById("timer").removeChild(document.getElementById("timer").lastChild);
572 var blob;
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}));
593 }));
594 //end function;
595 image_found = true;
596 //SET PAGE&POST AS FOUND
597 previous_images.push([pageNo, number_of_posts]);
598 number_of_posts = 9001;
601 if(!image_found){
602 top_page = pageNo;
603 attemptCounter--;
604 setImage();
609 var urlContainterFunction = function(url){
610 var urlBox = document.getElementById("urlContainer");
611 urlBox.value = url;
614 var counterFunction = function(){
615 if(!timeout){
616 time--;
617 if(time < 0){
618 timeout = true;
619 time = time_max;
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;
630 if (status == 200) {
631 callback(null, xhr.response, extra);
632 } else {
633 callback(status);
636 xhr.send();