- // ==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://archived.moe/*/thread/*
- // @match https://thebarchive.com/*/thread/*
- // @match https://archiveofsins.com/*/thread/*
- // @match https://archive.alice.al/*/thread/*
- // @match https://arch.b4k.co/*/thread/*
- // @match https://archive.palanq.win/*/thread/*
- // @grant GM_download
- // @grant GM_registerMenuCommand
- // @version 1.4.2
- // @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.
- // ==/UserScript==
-
- /* EDIT BELOW THIS LINE */
-
- // User preferences
- var indiv_button_enabled = true;
-
- var api_button_enabled = false;
-
- var keep_original_filenames = true; // Prioritized above archive_filenames
-
- var archive_filenames = true; // Only used if keep_original_filenames is false. If so, archive names will be saved; otherwise, 4chan names will be saved.
-
- var confirm_download = true;
-
- var download_limit = 3000; // speed in milliseconds to delay
-
- var named_poster_media_download_only = false;
-
- var named_poster_tag_in_filename = false; // Only used if named_poster_media_download_only is true
-
- /* EDIT ABOVE THIS LINE */
-
-
- (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)
-
- // 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
-
- //If OP is a massive OP,
- var OP_filename = (keep_original_filenames) ? OP.media_filename : OP.media_orig;
- OP_filename = (!keep_original_filenames && archive_filenames) ? OP.media : OP_filename;
- OP_filename = (named_poster_tag_in_filename && named_poster_media_download_only) ? String(thread_obj[thread_num].op.name+"_-_"+OP_filename) : OP_filename;
- var OP_media_link = (OP.media_link == null) ? OP.remote_media_link : OP.media_link;
- if (!named_poster_media_download_only || named_poster_media_download_only && thread_obj[thread_num].op.name != "Anonymous") {
- media_arr.push(OP_media_link);
- media_fnames.push(OP_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 (var 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 (and is from a named poster [non-anonymous poster] if option true),
- //then push media to arrays
- if (temp_media_post !== null && (!named_poster_media_download_only || named_poster_media_download_only && thread_posts[post_nums[i]].name != "Anonymous") ) {
-
- var working_media_link = (temp_media_post.media_link == null) ? temp_media_post.remote_media_link : temp_media_post.media_link
- var correct_media_fname = (keep_original_filenames) ? temp_media_post.media_filename : temp_media_post.media_orig;
- correct_media_fname = (!keep_original_filenames && archive_filenames) ? temp_media_post.media : correct_media_fname;
- correct_media_fname = (named_poster_tag_in_filename && named_poster_media_download_only) ? String(thread_posts[post_nums[i]].name+"_-_"+correct_media_fname) : correct_media_fname;
-
-
- //console.log(working_media_link); //debug
- //console.log(correct_media_fname); //debug
-
- media_arr.push(working_media_link)
- media_fnames.push(correct_media_fname);
-
- }
- }
- }
-
- // Adds the media link array with the media filenames array into the final return
- return_value[0] = media_arr;
- return_value[1] = media_fnames;
-
- /*
- var count;
-
- function download_with_scope(){
- GM_download(media_arr[count], media_fnames[count]) // downloads images
- }
- */
-
- function sleep(ms) {
- return new Promise(resolve => setTimeout(resolve, ms));
- }
-
- async function download_images(){
-
- for (var i=0; i<media_arr.length; i++){
- //console.log(media_fnames[i] + " "+ media_arr[i]); //debug
- //console.log(download_limit); //debug
-
- await sleep(download_limit);
-
- GM_download(media_arr[i], media_fnames[i]) // downloads images
-
- }
- }
-
- download_images();
-
-
- if(confirm_download){
- displayButton(indiv_dlbtn_elem);
- setTimeout(displayButton(indiv_dlbtn_elem), 3000);
- }
-
- }
-
- // Gets the JSON file for the thread with the API
- async function get_archive_thread() {
- const API_response = await fetch(api_url);
- const JSON_file = await API_response.json();
- console.log(JSON_file); // debug
- retrieve_media(JSON_file);
- }
-
- // Controls what the individual download button does upon being clicked
- function indivDownload(){
- displayButton(indiv_dlbtn_elem);
-
- // Wait for user to confirm zip if didn't click fast enough for double-click
- setTimeout(function(){
- if (window.getComputedStyle(indiv_dl_btn).backgroundColor == 'rgb(255, 64, 64)'){
- indiv_dl_btn.removeEventListener("click", displayButton);
- indiv_dl_btn.addEventListener("click", get_archive_thread);
-
- // If user does not confirm, reset the button back to original
- setTimeout(function(){
- indiv_dl_btn.removeEventListener("click", get_archive_thread);
- indiv_dl_btn.addEventListener("click", displayButton);
- Object.assign(indiv_dlbtn_elem.style, indivOrigStyles);
- indiv_dl_btn.innerText = "Indiv DL";
- }, 5000);
-
- }
- }, 501);
-
- }
-
- GM_registerMenuCommand("Download all thread images individually", get_archive_thread);
-
-
- // Download thread button event listener(s)
- if(confirm_download){
- indiv_dlbtn_elem.addEventListener("click", indivDownload);
- indiv_dlbtn_elem.addEventListener("dblclick", get_archive_thread);
- }
- else{
- indiv_dlbtn_elem.addEventListener("click", get_archive_thread);
- }
-
- })();