Greasy Fork

来自缓存

Greasy Fork is available in English.

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-19 提交的版本,查看 最新版本

// ==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.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;

var confirm_download = true;

var download_limit = 3000; // speed in milliseconds to delay

/* 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

    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;
    
    /*
    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 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) 
  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);
  }
  
})();