Threads Video Downloader

Download photos and videos from Threads quickly and easily!

当前为 2025-02-13 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @license MIT
  3. // @name Threads Video Downloader
  4. // @namespace http://tampermonkey.net/
  5. // @version 1.0
  6. // @description Download photos and videos from Threads quickly and easily!
  7. // @author Your Name
  8. // @match https://*.threads.net/*
  9. // @grant GM_download
  10. // ==/UserScript==
  11.  
  12. (() => {
  13. const t = {
  14. observeDom() {
  15. let self = this;
  16. document.querySelectorAll("video").forEach(video => {
  17. let root = self.findRoot(video);
  18. root.querySelector(".dw") || root.append(self.getBtn(video.src || null))
  19. });
  20. document.querySelectorAll("img").forEach(img => {
  21. if (!(img.width < 200 || img.height < 200 || img.parentElement.querySelector(".dw")))
  22. return img.parentElement.prepend(self.getBtn(img.src || null))
  23. })
  24. },
  25. initObserver() {
  26. if (typeof MutationObserver !== "undefined") {
  27. const observer = new MutationObserver(() => {
  28. this.observeDom();
  29. });
  30. observer.observe(document.body, { childList: true, subtree: true });
  31. } else {
  32. // Fallback for older browsers lacking MutationObserver support
  33. setInterval(() => {
  34. this.observeDom();
  35. }, 500);
  36. }
  37. // Initialize observation immediately on load
  38. this.observeDom();
  39. },
  40. getBtn(src) {
  41. let btn = document.createElement("button");
  42. btn.innerText = "Download";
  43. btn.className = "dw";
  44. let icon = document.createElement("span");
  45. icon.className = "icon";
  46. btn.append(icon);
  47. // Use data attribute instead of "src"
  48. btn.dataset.src = src;
  49. btn.addEventListener("click", this.dw);
  50. return btn;
  51. },
  52. findRoot(elem) {
  53. let parent = elem.parentNode;
  54. if (!parent) return;
  55. let visualDiv = parent.querySelector("div[data-visualcompletion]");
  56. return visualDiv || this.findRoot(parent);
  57. },
  58. dw(event) {
  59. event.preventDefault();
  60. event.stopPropagation();
  61. let btn = event.target.nodeName.toLowerCase() === "button" ? event.target : event.target.parentElement;
  62. let src = btn.dataset.src || null;
  63. src && GM_download(src, "downloaded_file");
  64. }
  65. };
  66. // Start observers
  67. t.initObserver();
  68. })();