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

  1. // ==UserScript==
  2. // @name 4chan Archive Image Downloader
  3. // @namespace Violentmonkey Scripts
  4. // @match https://archive.4plebs.org/*/thread/*
  5. // @match https://desuarchive.org/*/thread/*
  6. // @match https://boards.fireden.net/*/thread/*
  7. // @match https://boards.fireden.net/*/thread/*
  8. // @match https://archived.moe/*/thread/*
  9. // @match https://thebarchive.com/*/thread/*
  10. // @match https://archiveofsins.com/*/thread/*
  11. // @match https://www.tokyochronos.net/*/thread/*
  12. // @match https://archive.wakarimasen.moe/*/thread/*
  13. // @match https://archive.alice.al/*/thread/*
  14. // @grant GM_download
  15. // @grant GM_registerMenuCommand
  16. // @version 1.1
  17. // @license The Unlicense
  18. // @author ImpatientImport
  19. // @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.
  20. // ==/UserScript==
  21.  
  22. (function() {
  23. 'use strict';
  24. // Constants for later reference
  25. const top_of_thread = document.getElementsByClassName("post_controls")[0];
  26. const thread_URL = document.URL;
  27. const archive_site = thread_URL.toString().split('/')[2];
  28. const url_path = new URL(thread_URL).pathname;
  29. const url_path_split = url_path.toString().split('/')
  30. const thread_board = url_path_split[1];
  31. const thread_num = url_path_split[3];
  32. // checking URL console
  33. /*
  34. console.log(url_path_split);
  35. console.log(url_path);
  36. console.log(thread_URL);
  37. console.log(thread_URL.toString().split('/')[2]);
  38. */
  39. const api_url = "https://" + archive_site + "/_/api/chan/thread/?board=" + thread_board + "&num=" + thread_num; // important
  40. //console.log(api_url)
  41. /* EDIT ABOVE THIS LINE */
  42. // User preferences
  43. var indiv_button_enabled = true;
  44. var api_button_enabled = false;
  45. var keep_original_filenames = true;
  46. var confirm_download = true;
  47. /* EDIT ABOVE THIS LINE */
  48.  
  49. // Individual thread image downloader button
  50. var indiv_dl_btn;
  51. var indiv_dlbtn_elem;
  52. var indivOriginalStyle;
  53. var indivOrigStyles;
  54. if (indiv_button_enabled){
  55. indiv_dl_btn = document.createElement('a');
  56. indiv_dl_btn.id = "indiv_btn";
  57. indiv_dl_btn.classList.add("btnr", "parent");
  58. indiv_dl_btn.innerText = "Indiv DL";
  59. top_of_thread.append(indiv_dl_btn);
  60.  
  61. indiv_dlbtn_elem = document.getElementById("indiv_btn");
  62. indivOriginalStyle = window.getComputedStyle(indiv_dl_btn);
  63. indivOrigStyles = {
  64. backgroundColor: indivOriginalStyle.backgroundColor,
  65. color: indivOriginalStyle.color,
  66. }
  67. }
  68. // API button for getting the JSON of a thread in a new tab
  69. var api_btn;
  70. var api_btn_elem;
  71. if (api_button_enabled){
  72. api_btn = document.createElement('a');
  73. api_btn.id = "api_btn";
  74. api_btn.href = api_url;
  75. api_btn.target = "new";
  76. api_btn.classList.add("btnr", "parent");
  77. api_btn.innerText = "Thread API";
  78. top_of_thread.append(api_btn);
  79.  
  80. api_btn_elem = document.getElementById("api_btn");
  81. }
  82. function displayButton (elem){
  83. console.log(elem);
  84.  
  85. var current_style = window.getComputedStyle(elem).backgroundColor;
  86. //console.log(current_style); // debug
  87. var next_style;
  88. const button_original_text = {"indiv_btn": "Indiv DL"};
  89. const button_original_styles = {"indiv_btn": indivOrigStyles};
  90.  
  91. const confirmStyles = {
  92. backgroundColor: 'rgb(255, 64, 64)', // Coral Red
  93. color:"white",
  94. }
  95. const processingStyles = {
  96. backgroundColor: 'rgb(238, 210, 2)', // Safety Yellow
  97. color:"black",
  98. }
  99.  
  100. const doneStyles = {
  101. backgroundColor: 'rgb(46, 139, 87)', // Sea Green
  102. color:"white",
  103.  
  104. }
  105. const originalStyles = {
  106. backgroundColor: button_original_styles[elem.id].backgroundColor, // Original, clear
  107. color: button_original_styles[elem.id].color,
  108.  
  109. }
  110. // Button style switcher
  111. switch (current_style) {
  112. case 'rgba(0, 0, 0, 0)': // Original color
  113. next_style = confirmStyles;
  114. elem.innerText = "Confirm?";
  115. break;
  116. case 'rgb(255, 64, 64)': // Confirm color
  117. next_style = processingStyles;
  118. elem.innerText = "Processing";
  119. break;
  120.  
  121. case 'rgb(238, 210, 2)': // Processing color
  122. next_style = doneStyles;
  123. elem.innerText = "Done";
  124. break;
  125.  
  126. case 'rgb(46, 139, 87)': // Done Color
  127. next_style = originalStyles;
  128. elem.innerText = button_original_text[elem.id];
  129. break;
  130.  
  131. }
  132. Object.assign(elem.style, next_style);
  133. }
  134. // Retrieves media from the thread (in JSON format)
  135. // If OP only, ignore posts, else get posts
  136. function retrieve_media(thread_obj) {
  137. var media_arr = [];
  138. var media_fnames = [];
  139. var return_value = [];
  140.  
  141. const OP = thread_obj[thread_num].op.media;
  142. //console.log(OP); // debug
  143.  
  144. media_arr.push(OP.media_link);
  145. media_fnames.push(OP.media_filename);
  146.  
  147. // Boolean, checks if posts are present in thread
  148. const posts_exist = thread_obj[thread_num].posts != undefined;
  149.  
  150. if (posts_exist) {
  151. const thread_posts = thread_obj[thread_num].posts;
  152. const post_nums = Object.keys(thread_posts);
  153. const posts_length = post_nums.length;
  154.  
  155. //Adds all post image urls and original filenames to the above arrays
  156. for (let i = 0; i < posts_length; i++) {
  157.  
  158. //equivalent to: thread[posts][post_num][media]
  159. var temp_media_post = thread_posts[post_nums[i]].media;
  160.  
  161. //if media exists,
  162. if (temp_media_post !== null) {
  163. //then push media to arrays
  164. media_arr.push(temp_media_post.media_link)
  165. if (keep_original_filenames){
  166. media_fnames.push(temp_media_post.media_filename);
  167. //console.log(temp_media_post.media_filename); //debug
  168. }
  169. else{
  170. media_fnames.push(temp_media_post.media_orig);
  171. //console.log(temp_media_post.media_orig); //debug
  172. }
  173.  
  174. }
  175. }
  176. }
  177.  
  178. // Adds the media link array with the media filenames array into the final return
  179. return_value[0] = media_arr;
  180. return_value[1] = media_fnames;
  181. for (var i=0; i<media_arr.length; i++){
  182. //console.log(media_fnames[i] + " "+ media_arr[i]); //debug
  183. GM_download(media_arr[i], media_fnames[i]);
  184. }
  185. displayButton(indiv_dlbtn_elem);
  186. setTimeout(displayButton(indiv_dlbtn_elem), 3000);
  187.  
  188. }
  189.  
  190. // Gets the JSON file for the 4plebs thread with the API
  191. async function get_archive_thread() {
  192. const API_response = await fetch(api_url);
  193. const JSON_file = await API_response.json();
  194. console.log(JSON_file); // debug
  195. retrieve_media(JSON_file);
  196. }
  197. // Controls what the individual download button does upon being clicked
  198. function indivDownload(){
  199. displayButton(indiv_dlbtn_elem);
  200. // Wait for user to confirm zip if didn't click fast enough for double-click
  201. setTimeout(function(){
  202. if (window.getComputedStyle(indiv_dl_btn).backgroundColor == 'rgb(255, 64, 64)'){
  203. indiv_dl_btn.removeEventListener("click", displayButton);
  204. indiv_dl_btn.addEventListener("click", get_archive_thread);
  205.  
  206. // If user does not confirm, reset the button back to original
  207. setTimeout(function(){
  208. indiv_dl_btn.removeEventListener("click", get_archive_thread);
  209. indiv_dl_btn.addEventListener("click", displayButton);
  210. Object.assign(indiv_dlbtn_elem.style, indivOrigStyles);
  211. indiv_dl_btn.innerText = "Indiv DL";
  212. }, 5000);
  213.  
  214. }
  215. }, 501);
  216. }
  217. GM_registerMenuCommand("Download all thread images individually", get_archive_thread);
  218. // Download thread button event listener(s)
  219. if(confirm_download){
  220. indiv_dlbtn_elem.addEventListener("click", indivDownload);
  221. indiv_dlbtn_elem.addEventListener("dblclick", get_archive_thread);
  222. }
  223. else{
  224. indiv_dlbtn_elem.addEventListener("click", get_archive_thread);
  225. }
  226. })();