// ==UserScript== // @name 4chan Archive Image Downloader // @namespace Violentmonkey Scripts // @match https://archive.4plebs.org/*/thread/* // @match https://desuarchive.org/*/thread/* // @match https://boards.fireden.net/*/thread/* // @match https://boards.fireden.net/*/thread/* // @match https://archived.moe/*/thread/* // @match https://thebarchive.com/*/thread/* // @match https://archiveofsins.com/*/thread/* // @match https://www.tokyochronos.net/*/thread/* // @match https://archive.wakarimasen.moe/*/thread/* // @match https://archive.alice.al/*/thread/* // @grant GM_download // @grant GM_registerMenuCommand // @version 1.0 // @license The Unlicense // @author ImpatientImport // @description 4chan archive thread image downloader for general use across many foolfuuka based imageboards. Downloads all images individually in a thread with original filenames (by default). Optional thread API button, for development purposes. // @downloadURL none // ==/UserScript== (function() { 'use strict'; // Constants for later reference const top_of_thread = document.getElementsByClassName("post_controls")[0]; const thread_URL = document.URL; const archive_site = thread_URL.toString().split('/')[2]; const url_path = new URL(thread_URL).pathname; const url_path_split = url_path.toString().split('/') const thread_board = url_path_split[1]; const thread_num = url_path_split[3]; // checking URL console /* console.log(url_path_split); console.log(url_path); console.log(thread_URL); console.log(thread_URL.toString().split('/')[2]); */ const api_url = "https://" + archive_site + "/_/api/chan/thread/?board=" + thread_board + "&num=" + thread_num; // important //console.log(api_url) /* EDIT ABOVE THIS LINE */ // User preferences var indiv_button_enabled = true; var api_button_enabled = false; var keep_original_filenames = true; /* EDIT ABOVE THIS LINE */ // Individual thread image downloader button var indiv_dl_btn; var indiv_dlbtn_elem; var indivOriginalStyle; var indivOrigStyles; if (indiv_button_enabled){ indiv_dl_btn = document.createElement('a'); indiv_dl_btn.id = "indiv_btn"; indiv_dl_btn.classList.add("btnr", "parent"); indiv_dl_btn.innerText = "Indiv DL"; top_of_thread.append(indiv_dl_btn); indiv_dlbtn_elem = document.getElementById("indiv_btn"); indivOriginalStyle = window.getComputedStyle(indiv_dl_btn); indivOrigStyles = { backgroundColor: indivOriginalStyle.backgroundColor, color: indivOriginalStyle.color, } } // API button for getting the JSON of a thread in a new tab var api_btn; var api_btn_elem; if (api_button_enabled){ api_btn = document.createElement('a'); api_btn.id = "api_btn"; api_btn.href = api_url; api_btn.target = "new"; api_btn.classList.add("btnr", "parent"); api_btn.innerText = "Thread API"; top_of_thread.append(api_btn); api_btn_elem = document.getElementById("api_btn"); } function displayButton (elem){ console.log(elem); var current_style = window.getComputedStyle(elem).backgroundColor; //console.log(current_style); // debug var next_style; const button_original_text = {"indiv_btn": "Indiv DL"}; const button_original_styles = {"indiv_btn": indivOrigStyles}; const confirmStyles = { backgroundColor: 'rgb(255, 64, 64)', // Coral Red color:"white", } const processingStyles = { backgroundColor: 'rgb(238, 210, 2)', // Safety Yellow color:"black", } const doneStyles = { backgroundColor: 'rgb(46, 139, 87)', // Sea Green color:"white", } const originalStyles = { backgroundColor: button_original_styles[elem.id].backgroundColor, // Original, clear color: button_original_styles[elem.id].color, } // Button style switcher switch (current_style) { case 'rgba(0, 0, 0, 0)': // Original color next_style = confirmStyles; elem.innerText = "Confirm?"; break; case 'rgb(255, 64, 64)': // Confirm color next_style = processingStyles; elem.innerText = "Processing"; break; case 'rgb(238, 210, 2)': // Processing color next_style = doneStyles; elem.innerText = "Done"; break; case 'rgb(46, 139, 87)': // Done Color next_style = originalStyles; elem.innerText = button_original_text[elem.id]; break; } Object.assign(elem.style, next_style); } // Retrieves media from the thread (in JSON format) // If OP only, ignore posts, else get posts function retrieve_media(thread_obj) { var media_arr = []; var media_fnames = []; var return_value = []; const OP = thread_obj[thread_num].op.media; //console.log(OP); // debug media_arr.push(OP.media_link); media_fnames.push(OP.media_filename); // Boolean, checks if posts are present in thread const posts_exist = thread_obj[thread_num].posts != undefined; if (posts_exist) { const thread_posts = thread_obj[thread_num].posts; const post_nums = Object.keys(thread_posts); const posts_length = post_nums.length; //Adds all post image urls and original filenames to the above arrays for (let i = 0; i < posts_length; i++) { //equivalent to: thread[posts][post_num][media] var temp_media_post = thread_posts[post_nums[i]].media; //if media exists, if (temp_media_post !== null) { //then push media to arrays media_arr.push(temp_media_post.media_link) if (keep_original_filenames){ media_fnames.push(temp_media_post.media_filename); //console.log(temp_media_post.media_filename); //debug } else{ media_fnames.push(temp_media_post.media_orig); //console.log(temp_media_post.media_orig); //debug } } } } // Adds the media link array with the media filenames array into the final return return_value[0] = media_arr; return_value[1] = media_fnames; for (var i=0; i