Greasy Fork

4chan Archive Image Downloader

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.

目前为 2022-02-18 提交的版本。查看 最新版本

// ==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.
// ==/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<media_arr.length; i++){
      //console.log(media_fnames[i] + " "+ media_arr[i]); //debug
      
      GM_download(media_arr[i], media_fnames[i]);
    }
    
    displayButton(indiv_dlbtn_elem);
    setTimeout(displayButton(indiv_dlbtn_elem), 3000);

  }

  // Gets the JSON file for the 4plebs 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)
  
  indiv_dlbtn_elem.addEventListener("dblclick", get_archive_thread);
  indiv_dlbtn_elem.addEventListener("click", indivDownload);
  
})();