// ==UserScript== // @name All-in-One Video Downloader – HD (YouTube, TikTok, Instagram, X & More) // @name:ar مُحمِّل فيديو متكامل – HD (يوتيوب، تيك توك، إنستغرام، إكس وغيرها) // @name:bg Универсален видеоизтегляч – HD (YouTube, TikTok, Instagram, X и др.) // @name:cs Univerzální stahovač videí – HD (YouTube, TikTok, Instagram, X a další) // @name:da Alt-i-ét-video-downloader – HD (YouTube, TikTok, Instagram, X m.m.) // @name:de All-in-One-Video-Downloader – HD (YouTube, TikTok, Instagram, X u. a.) // @name:el Ολοκληρωμένη λήψη βίντεο – HD (YouTube, TikTok, Instagram, X και άλλα) // @name:en All-in-One Video Downloader – HD (YouTube, TikTok, Instagram, X & More) // @name:eo Ĉio-en-unu video-elŝutilo – HD (YouTube, TikTok, Instagram, X kaj pli) // @name:es Descargador de vídeo todo en uno – HD (YouTube, TikTok, Instagram, X y más) // @name:es-419 Descargador de video todo en uno – HD (YouTube, TikTok, Instagram, X y más) // @name:fi Kaikki yhdessä -videolataaja – HD (YouTube, TikTok, Instagram, X jne.) // @name:fr Téléchargeur vidéo tout-en-un – HD (YouTube, TikTok, Instagram, X et plus) // @name:fr-CA Téléchargeur de vidéos tout-en-un – HD (YouTube, TikTok, Instagram, X et plus) // @name:he מוריד וידאו הכול-באחד – HD (YouTube, TikTok, Instagram, X ועוד) // @name:hr Sve-u-jednom preuzimanje videa – HD (YouTube, TikTok, Instagram, X i više) // @name:hu Minden-egyben videóletöltő – HD (YouTube, TikTok, Instagram, X és más) // @name:id Pengunduh video serba ada – HD (YouTube, TikTok, Instagram, X, dll.) // @name:it Scaricatore video tutto-in-uno – HD (YouTube, TikTok, Instagram, X e altro) // @name:ja オールインワン動画ダウンローダー – HD(YouTube、TikTok、Instagram、X など) // @name:ka ყველაფერი-ერთში ვიდეოს ჩამოტვირთვა – HD (YouTube, TikTok, Instagram, X და სხვა) // @name:ko 올인원 동영상 다운로더 – HD(YouTube, TikTok, Instagram, X 등) // @name:nb Alt-i-ett-videonedlaster – HD (YouTube, TikTok, Instagram, X m.m.) // @name:nl Alles-in-één video-downloader – HD (YouTube, TikTok, Instagram, X en meer) // @name:pl Uniwersalny pobieracz wideo – HD (YouTube, TikTok, Instagram, X i inne) // @name:pt-BR Baixador de vídeo tudo-em-um – HD (YouTube, TikTok, Instagram, X e mais) // @name:ro Descărcător video totul-într-unul – HD (YouTube, TikTok, Instagram, X și altele) // @name:ru Универсальный загрузчик видео – HD (YouTube, TikTok, Instagram, X и др.) // @name:sv Allt-i-ett-videonedladdare – HD (YouTube, TikTok, Instagram, X m.m.) // @name:th ดาวน์โหลดวิดีโอครบในที่เดียว – HD (YouTube, TikTok, Instagram, X และอื่นๆ) // @name:tr Hepsi bir arada video indirici – HD (YouTube, TikTok, Instagram, X ve daha fazlası) // @name:uk Універсальний завантажувач відео – HD (YouTube, TikTok, Instagram, X тощо) // @name:ug بارلىق ئىقتىدار بىر يەردە: HD ۋىدىئو چۈشۈرگۈچ (YouTube، TikTok، Instagram، X ۋە باشقىلار) // @name:vi Trình tải video tất cả trong một – HD (YouTube, TikTok, Instagram, X và hơn thế nữa) // @name:zh-CN 多合一视频下载器(支持:YouTube, TikTok, Instagram等) // @name:zh-TW 多合一視頻下載器(支援:YouTube, TikTok, Instagram等) // @description An all-in-one, fast, and free HD video downloader. Supports video downloads from YouTube, TikTok, Douyin, Instagram, Threads, XiaoHongShu, TED, Facebook, X (formerly Twitter), and more. Enjoy seamless, high-quality downloads with no watermarks. // @description:ar أداة شاملة وسريعة ومجانية لتحميل مقاطع الفيديو بدقة عالية. يدعم تحميل مقاطع الفيديو من يوتيوب، تيك توك، Douyin، إنستغرام، Facebook، ثريدز، XiaoHongShu، TED، إكس (تويتر سابقًا)، والمزيد. استمتع بتحميلات عالية الجودة بدون علامات مائية. // @description:bg Всичко в едно, бързо и безплатно изтегляне на видео с висока резолюция. Поддържа изтегляне на видеа от YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (бивш Twitter) и други. Насладете се на безпроблемни, висококачествени изтегляния без водни знаци. // @description:cs Vše v jednom, rychlý a bezplatný downloader videí ve vysoké kvalitě. Podporuje stahování videí z YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (dříve Twitter) a dalších. Užijte si plynulé, kvalitní stahování bez vodoznaků. // @description:da En alt-i-en, hurtig og gratis HD video downloader. Understøtter video downloads fra YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (tidligere Twitter) og mere. Nyd problemfri, høj kvalitet downloads uden vandmærker. // @description:de Ein All-in-One, schneller und kostenloser HD-Video-Downloader. Unterstützt das Herunterladen von Videos von YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (ehemals Twitter) und mehr. Genießen Sie nahtlose, hochqualitative Downloads ohne Wasserzeichen. // @description:el Ένας ολοκληρωμένος, γρήγορος και δωρεάν downloader HD βίντεο. Υποστηρίζει λήψη βίντεο από YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (πρώην Twitter) και άλλα. Απολαύστε απρόσκοπτες, υψηλής ποιότητας λήψεις χωρίς υδατογραφήματα. // @description:en An all-in-one, fast, and free HD video downloader. Supports video downloads from YouTube, TikTok, Douyin, Instagram, Threads, XiaoHongShu, TED, Facebook, X (formerly Twitter), and more. Enjoy seamless, high-quality downloads with no watermarks. // @description:eo Ĉio-en-unu, rapida kaj senpaga HD-video-elŝutilo. Subtenas video-elŝutojn de YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (antaŭe Twitter), kaj aliaj. Ŝatu seninterrompajn, altkvalitajn elŝutojn sen akvamaĵoj. // @description:es Un descargador de video todo en uno, rápido y gratuito en HD. Admite descargas de videos de YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (anteriormente Twitter) y más. Disfruta de descargas de alta calidad sin marcas de agua. // @description:fi Kaikki yhdessä, nopea ja ilmainen HD-videoiden lataaja. Tukee videoiden lataamista YouTubesta, TikTokista, Douyinista, Instagramista, Facebookista, Threadsista, XiaoHongShusta, X:stä (entinen Twitter) ja muista. Nauti saumatonta, korkealaatuista lataamista ilman vesileimoja. // @description:fr Un téléchargeur vidéo HD tout-en-un, rapide et gratuit. Prend en charge les téléchargements de vidéos depuis YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (anciennement Twitter) et plus encore. Profitez de téléchargements fluides et de haute qualité sans filigranes. // @description:fr-CA Un téléchargeur vidéo HD tout-en-un, rapide et gratuit. Prend en charge les téléchargements de vidéos depuis YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (anciennement Twitter) et plus encore. Profitez de téléchargements fluides et de haute qualité sans filigranes. // @description:he מנוע הורדות וידאו HD מהיר, חינמי ומקיף. תומך בהורדת וידאו מיוטיוב, טיקטוק, אינסטגרם, Facebook, Threads, XiaoHongShu, TED, X (בעבר טוויטר) ועוד. תהנה מהורדות באיכות גבוהה ללא סימני מים. // @description:hr Svi-u-jednom, brzi i besplatni HD downloader za video. Podržava preuzimanje videa s YouTubea, TikToka, Instagrama, Facebooka, Threads, XiaoHongShu, TED, X (bivši Twitter) i još mnogo toga. Uživajte u besprijekornim, visokokvalitetnim preuzimanjima bez vodmarks. // @description:hu Egy minden-egyben, gyors és ingyenes HD videó letöltő. Támogatja a videók letöltését YouTube-ról, TikTok-ról, Instagram-ról, Facebookról, Threads, XiaoHongShu, TED, X (régen Twitter) és másról. Élvezze a zökkenőmentes, magas minőségű letöltéseket vízjel nélkül. // @description:id Downloader video HD serba ada, cepat, dan gratis. Mendukung unduhan video dari YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (sebelumnya Twitter), dan banyak lagi. Nikmati unduhan berkualitas tinggi tanpa tanda air. // @description:it Un downloader video HD completo, veloce e gratuito. Supporta il download di video da YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (precedentemente Twitter) e altri. Goditi download fluidi e di alta qualità senza filigrane. // @description:ja YouTube の視聴体験を向上させるための高速で無料な HD 動画ダウンローダー。YouTube、TikTok、Douyin、Instagram、Facebook、Threads、XiaoHongShu、X(旧 Twitter)などからの動画ダウンロードをサポート。シームレスで高品質なダウンロードをウォーターマークなしでお楽しみください。 // @description:ka ყველაფერი ერთში, სწრაფი და უფასო HD ვიდეო ჩამოტვირთვა. მხარს უჭერს ვიდეოს ჩამოტვირთვას YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (ყოფილი Twitter) და სხვა პლატფორმებიდან. დატკბით უარყოფითი ნიშნების გარეშე, უმაღლესი ხარისხის ჩამოტვირთვებით. // @description:ko YouTube 시청 경험을 향상시키는 모든 기능을 갖춘, 빠르고 무료인 HD 비디오 다운로드기. YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X(이전 Twitter) 등에서 비디오 다운로드를 지원합니다. 워터마크 없이 원활한 고품질 다운로드를 즐기세요. // @description:nb En alt-i-en, rask og gratis HD video nedlaster. Støtter video nedlastinger fra YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (tidligere Twitter), og mer. Nyt sømløse, høy kvalitet nedlastinger uten vannmerker. // @description:nl Een alles-in-één, snelle en gratis HD-video downloader. Ondersteunt video-downloads van YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (voorheen Twitter) en meer. Geniet van naadloze, hoogwaardige downloads zonder watermerken. // @description:pl Jedno w jednym, szybki i darmowy pobieracz wideo HD. Obsługuje pobieranie wideo z YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (dawniej Twitter) i innych. Ciesz się płynne, wysokiej jakości pobieranie bez znaków wodnych. // @description:pt-BR Um downloader de vídeo HD completo, rápido e gratuito. Suporta downloads de vídeos do YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (anteriormente Twitter) e muito mais. Aproveite downloads de alta qualidade sem marcas d'água. // @description:ro Un downloader video HD rapid și gratuit, totul într-un singur pachet. Suportă descărcarea de videoclipuri de pe YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (fost Twitter) și altele. Bucurați-vă de descărcări de înaltă calitate fără filigrane. // @description:ru Все-в-одном, быстрый и бесплатный HD видеозагрузчик. Поддерживает загрузку видео с YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (бывший Twitter) и других. Наслаждайтесь безупречными загрузками высокого качества без водяных знаков. // @description:sv En allt-i-ett, snabb och gratis HD-video nedladdare. Stöder video nedladdningar från YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (tidigare Twitter) och mer. Njut av sömlösa, högkvalitativa nedladdningar utan vattenmärken. // @description:th เครื่องมือดาวน์โหลดวิดีโอ HD ที่รวดเร็วและฟรีแบบครบวงจร รองรับการดาวน์โหลดวิดีโอจาก YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (เดิม Twitter) และอื่นๆ เพลิดเพลินกับการดาวน์โหลดที่มีคุณภาพสูงโดยไม่มีลายน้ำ // @description:tr Birçok platformdan video indirmenizi destekleyen, hızlı ve ücretsiz HD video indirme aracıdır. YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (eski Twitter) ve daha fazlasını destekler. Su işareti olmadan kesintisiz yüksek kaliteli indirmelerin keyfini çıkarın. // @description:uk Всі в одному, швидкий та безкоштовний HD відео-завантажувач. Підтримує завантаження відео з YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (колишній Twitter) та інших. Насолоджуйтесь безперешкодними високоякісними завантаженнями без водяних знаків. // @description:ug YouTube كۆرۈش تەجرىبەڭىزنى ياخشىلايدىغان، تىز ۋە ھەقسىز HD ۋىدىئو چۈشۈرگۈچى. يۇتۇب، تيك توك، دۇيىن، ئىنستاگرام، Facebook، Threads، XiaoHongShu، TED، X (ئالدىنقى Twitter) ۋە باشقا پلاتفورمالاردىن ۋىدىئو چۈشۈرۈشنى قوللايدۇ. سۇ ئىشاراتسىز ئېغىزدىن سۈپەتلىك چۈشۈرۈشتىن لەززەتلىنىڭ. // @description:vi Trình tải video HD tất cả trong một, nhanh chóng và miễn phí. Hỗ trợ tải video từ YouTube, TikTok, Douyin, Instagram, Facebook, Threads, XiaoHongShu, TED, X (trước đây là Twitter) và nhiều nền tảng khác. Thưởng thức tải xuống chất lượng cao mượt mà mà không có watermark. // @description:zh-CN 多合一在线视频下载器。支持 YouTube/TikTok/Instagram/X/Facebook/Threads/TED/小红书等;并扩展中国平台:哔哩哔哩(浏览器内真解析直链),及爱奇艺/优酷/腾讯视频/芒果TV/央视频/PPTV(借第三方 jx 解析站,按钮弹出后端选择面板,在新标签页打开解析页) // @description:zh-TW 一款多合一、快速且免費的線上視頻下載器。支援從 YouTube、TikTok、抖音、Instagram、Facebook、Threads、TED、小紅書、X(Twitter)等平台下載視頻。享受無水印、高質量、流暢便捷的下載體驗 // @namespace AllInOneDownloader_Daniel // @version 1.0.20 // @author Daniel // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAwCAYAAABe6Vn9AAACZElEQVR4AeyZbVKDMBCGgYuJ/9WT2CNoqx6hnkT9T72YuA+WDg0M2SSblnFwWCfNfuR9EhqatiqM/+72bX3/3jZirccaYo2HL8yByrLYishazHfVx1hfXJDfHEhG18BIWHeFxHYJvn85gHxjZvWvQFmn16D4ukIGk5i1xLpCWafXoPi6QgaTmLXEukJZp9eg+LpCBpOYtYR6hTi7yPnGe84JVSs1feemlrG1ddVAx7OL+cd9jdDj2JpQ+wOeatTwIPVEqleobYuXcB3zGVqvjH2rjVUDfW3KQ3kFKGAY2xyIgh+bcndJqFAYNKpXiGDsUlAxMOgLBiIpN1QsDNqigEjMBZUCg65oIJKtoVJh0JQERAErKAsY9CQDUSQVygoGLSZAFIqFsoRBhxkQxUKheKaFPDQZw2emQAymhQKGWHIszRwIcQhFMO0pw0fMlC+1rwN62Lc7zblkLsY9syAY4a5A+vAN+8mdq63xwUDNikb795sOr6NNziyjH7AQDgAmhQ9sAPRJ+3QBQ+6pI7IBg4A/VzQia4zSEIbAoQMA7POxvHU3AGLJGcYntp+6Wy6xyFk6AhF61jnxghhiJ1xJXeZAqEEotzLtKcsFw1hZgCjMrQwUBkBvcp83ABOTw7IBIRYoDIDepF/9/YDEBl9qoODKV0pYIlC3vcuuWMqcHMTca9a/OCB5Zn3327s8t0bfNPn8iwP6KYrTqoi40ftt6C8m/iRnoveKXbJ5bPudUTaUG1fK0E/b9S8OSATWArIVsQ1tMfea9S8RyAUIel3Jm2z0xguqsKzgt4oPjv8BCgbZ6l9/AQAA//9HnEu0AAAABklEQVQDAI1HmgBXKvxdAAAAAElFTkSuQmCC // @include https://www.ted.com/* // @include https://*.youtube.com/* // @include https://x.com/* // @include https://twitter.com/* // @include https://www.threads.com/* // @include https://www.instagram.com/* // @include https://www.tikTok.com/* // @include https://www.douyin.com/* // @include https://www.xiaohongshu.com/* // @include https://www.facebook.com/* // @include https://www.snapchat.com/* // @include https://www.pinterest.com/* // @include https://*.googlevideo.com/* // @include https://*.tiktokcdn.com/* // @include https://*.douyinvod.com/* // @include https://*.fbcdn.net/* // @include https://video.twimg.com/* // @include https://*.zjcdn.com/* // @include https://*.snssdk.com/* // @include https://*.tiktokv.com/* // @include https://*.douyinstatic.com/* // @include https://*.xhscdn.com/* // @include https://*.amemv.com/* // @include https://*.tikwm.com/* // @include https://*.tiktokcdn-eu.com/* // @include https://*.tiktokcdn-us.com/* // @include https://*.douyinpic.com/* // @include https://*.douyinmusicpromotion.com/* // @include https://*.cdninstagram.com/* // @include https://*.licdn.com/* // @include https://*.sc-cdn.net/* // @include https://*.pinimg.com/* // @include https://open.spotify.com/* // @include https://*.ted.com/* // @include https://*.tedcdn.com/* // @include https://*.dmcdn.net/* // @include https://www.iqiyi.com/* // @include https://*.iqiyi.com/* // @include https://v.youku.com/* // @include https://*.youku.com/* // @include https://v.qq.com/* // @include https://*.v.qq.com/* // @include https://www.mgtv.com/* // @include https://*.mgtv.com/* // @include https://www.yangshipin.cn/* // @include https://*.yangshipin.cn/* // @include https://www.pptv.com/* // @include https://*.pptv.com/* // @include https://www.bilibili.com/* // @include https://*.bilibili.com/* // @include https://player.bilibili.com/* // @connect googlevideo.com // @connect tiktokcdn.com // @connect snssdk.com // @connect douyinvod.com // @connect tiktokv.com // @connect fbcdn.net // @connect zjcdn.com // @connect twimg.com // @connect douyinstatic.com // @connect xhscdn.com // @connect amemv.com // @connect tikwm.com // @connect tiktokcdn-eu.com // @connect tiktokcdn-us.com // @connect douyinpic.com // @connect douyinmusicpromotion.com // @connect cdninstagram.com // @connect ted.com // @connect licdn.com // @connect sc-cdn.net // @connect pinimg.com // @connect tedcdn.com // @connect dmcdn.net // @noframes // @license MIT // @run-at document-start // @antifeature referral-link // @grant GM_openInTab // @grant GM.openInTab // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_download // @grant unsafeWindow // ==/UserScript== (function () { 'use strict'; /*! * Copyright (c) 2026 - 2026, Daniel. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ const CommonUtils = { getSupportedLang: function() { const lang = navigator.language || navigator.userLanguage; const supportedLanguages = { "en": "en", "es": "es", "fr": "fr", "pt": "pt", "ru": "ru", "ja": "ja", "de": "de", "ko": "ko", "it": "it", "id": "id", "tr": "tr", "pl": "pl", "uk": "uk", "nl": "nl", "vi": "vi", "th": "th", "ar": "ar", "fa": "fa", "hi": "hi", "ms": "ms", "zh-CN": "zh-CN", "zh-TW": "zh-TW" }; const langCode = lang.split("-")[0]; if (langCode === "zh") { return lang === "zh-CN" ? "zh-CN" : "zh-TW"; } return supportedLanguages[langCode] || "en"; }, openInTab: function(url, options = { active: true, insert: true, setParent: true }) { if (typeof GM_openInTab === "function") { GM_openInTab(url, options); } else { GM.openInTab(url, options); } }, genrateDownloadSvg: function(color = "#FFF", width = 25, height = 25) { const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("t", "1768806429307"); svg.setAttribute("class", "icon"); svg.setAttribute("viewBox", "0 0 1024 1024"); svg.setAttribute("version", "1.1"); svg.setAttribute("xmlns", "http://www.w3.org/2000/svg"); svg.setAttribute("p-id", "21520"); svg.setAttribute("width", width); svg.setAttribute("height", height); const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute( "d", "M684.5 512H573.1V389.8c0-11.2-9.1-20.4-20.4-20.4h-81.5c-11.3 0-20.4 9.1-20.4 20.4v122.4l-112.4 0.6c-4 0-7.1 2.3-8.5 5.5 0 0.1-0.1 0.1-0.1 0.2-0.3 0.6-0.3 1.3-0.4 2-0.1 0.6-0.3 1.2-0.2 1.8 0 0.1-0.1 0.3-0.1 0.4 0 0.4 0.2 0.7 0.3 1.1 0.2 0.8 0.3 1.6 0.7 2.4 0.2 0.4 0.4 0.7 0.6 1 0.3 0.6 0.6 1.2 1 1.7l168.2 188c0.4 0.4 0.8 0.6 1.2 1 0.2 0.2 0.3 0.5 0.6 0.7 0.2 0.2 0.5 0.2 0.8 0.4 0.7 0.4 1.4 0.7 2.1 1 0.2 0.1 0.5 0.2 0.7 0.2 2.9 0.9 6 0.6 8.3-1.3 0.5-0.4 0.8-1.1 1.2-1.6 0.3-0.2 0.6-0.4 0.9-0.7l175.2-187.8c0.5-0.6 0.8-1.3 1.2-1.9 0.2-0.3 0.4-0.5 0.5-0.9 0.4-0.8 0.6-1.6 0.7-2.3 0.1-0.4 0.3-0.6 0.3-1v-1c0.6-5.4-3.6-9.7-9.1-9.7zM471.3 349.1h81.5c11.3 0 20.4-9.1 20.4-20.4v-20.4c0-11.2-9.1-20.4-20.4-20.4h-81.5c-11.3 0-20.4 9.1-20.4 20.4v20.4c0 11.3 9.1 20.4 20.4 20.4zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64z m0 814.6c-202.4 0-366.5-164.1-366.5-366.6 0-202.4 164.1-366.5 366.5-366.5S878.5 309.6 878.5 512 714.4 878.6 512 878.6z" ); path.setAttribute("fill", color); svg.appendChild(path); return svg; }, formatBytes: function(bytes, decimals = 2) { const size = Number(bytes); if (!Number.isFinite(size) || size < 0) { return "0 KB"; } const kb = 1024; const mb = kb * 1024; const gb = mb * 1024; if (size >= gb) { return `${(size / gb).toFixed(decimals)} GB`; } if (size >= mb) { return `${(size / mb).toFixed(decimals)} MB`; } return `${(size / kb).toFixed(decimals)} KB`; }, downloaderEvent: function(url) { const durl = "https://www.tool77.com/" + CommonUtils.getSupportedLang() + "/v/downloader?url=" + encodeURIComponent(url); CommonUtils.openInTab(durl); }, findParentByClassContains: function(el, classPart, maxLevel = 5) { let level = 0; let cur = el; while (cur && level < maxLevel) { if (cur.className && typeof cur.className === "string" && cur.className.includes(classPart)) { return cur; } cur = cur.parentElement; level++; } return null; }, onLocationChange: function(callback) { if (typeof callback !== "function") { return; } if (!this._locationChangeListeners) { this._locationChangeListeners = /* @__PURE__ */ new Set(); } this._locationChangeListeners.add(callback); if (window.__tm_history_patch_v1__) { return; } window.__tm_history_patch_v1__ = true; const utils = this; let lastHref = location.href; let scheduled = false; const notify = () => { if (scheduled) { return; } scheduled = true; requestAnimationFrame(() => { scheduled = false; const href = location.href; if (href === lastHref) { return; } lastHref = href; utils._locationChangeListeners.forEach((cb) => { try { cb(href); } catch (e) { } }); }); }; const patch = (type) => { const raw = history[type]; if (raw.__tm_patched__) { return; } function wrappedState() { const ret = raw.apply(this, arguments); notify(); return ret; } wrappedState.__tm_patched__ = true; wrappedState.__original__ = raw; history[type] = wrappedState; }; patch("pushState"); patch("replaceState"); window.addEventListener("popstate", notify); window.addEventListener("hashchange", notify); } }; const DownloadHud = { instances: /* @__PURE__ */ new Map(), createStyleText: function(id, zIndex, buttonSize) { return ` #${id} { position: fixed; z-index: ${zIndex} !important; overflow: hidden; background: rgba(0, 0, 0, 0.3); width: ${buttonSize}px; height: ${buttonSize}px; border-radius: 50%; display: none; align-items: center; justify-content: center; cursor: pointer; pointer-events: auto !important; font-size: 16px; } #${id}::before { content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 2px; background: linear-gradient(90deg, red, orange, yellow, green, cyan, blue, violet, red); background-size: 200% 200%; opacity: 0; transition: opacity .25s; -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor; mask-composite: exclude; } #${id}.all-in-one-downloader-daniel-hover { opacity: 1; } #${id}.all-in-one-downloader-daniel-hover::before { opacity: 1; animation: all-in-one-downloader-rainbow-move 3s linear infinite; } #${id} svg { fill: currentColor; pointer-events: none; } @keyframes all-in-one-downloader-rainbow-move { 0% { background-position: 0% 50%; } 100% { background-position: 200% 50%; } } `; }, isPointInRect: function(x, y, rect) { return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom; }, isVisibleElement: function(element, minTargetSize) { const rect = element.getBoundingClientRect(); if (rect.width <= minTargetSize || rect.height <= minTargetSize) { return false; } const computedStyle = window.getComputedStyle(element); if (computedStyle.display === "none" || computedStyle.visibility === "hidden" || computedStyle.opacity === "0") { return false; } return true; }, createHoverDownloadButton: function(options = {}) { const { id = "all-in-one-downloader-daniel", zIndex = 2147483647, buttonSize = 40, buttonPosition = "top-center", buttonEdgeOffset = 15, iconSize = 25, iconColor = "#fff", minTargetSize = 50, targetSelector = "video", extraTargetSelector = "", getAnchorRect = null, onClick = null } = options; if (this.instances.has(id)) { return this.instances.get(id).api; } const state = { activeTarget: null, mouseX: -500, mouseY: -500, destroyed: false, disabled: false, buttonPosition, buttonEdgeOffset }; const style = GM_addStyle(this.createStyleText(id, zIndex, buttonSize)); const button = document.createElement("div"); button.id = id; button.style.cursor = "pointer!important"; const svgIcon = CommonUtils.genrateDownloadSvg(iconColor, iconSize, iconSize); button.appendChild(svgIcon); (document.body || document.documentElement).appendChild(button); const updateButtonPos = () => { if (state.destroyed) { return; } if (!state.activeTarget || button.style.display === "none") { return; } let rect = null; if (typeof getAnchorRect === "function") { rect = getAnchorRect(state.activeTarget); } if (!rect) { rect = state.activeTarget.getBoundingClientRect(); } if (!rect) { return; } const edgeOffset = Number.isFinite(Number(state.buttonEdgeOffset)) ? Number(state.buttonEdgeOffset) : 15; let top = rect.top + edgeOffset; let left = rect.left + rect.width / 2 - buttonSize / 2; switch (state.buttonPosition) { case "bottom-center": top = rect.bottom - buttonSize - edgeOffset; left = rect.left + rect.width / 2 - buttonSize / 2; break; case "left-center": top = rect.top + rect.height / 2 - buttonSize / 2; left = rect.left + edgeOffset; break; case "right-center": top = rect.top + rect.height / 2 - buttonSize / 2; left = rect.right - buttonSize - edgeOffset; break; case "right-top-quarter": top = rect.top + rect.height * 0.25 - buttonSize / 2; left = rect.right - buttonSize - edgeOffset; break; case "right-three-quarter": top = rect.top + rect.height * 0.75 - buttonSize / 2; left = rect.right - buttonSize - edgeOffset; break; case "left-top-quarter": top = rect.top + rect.height * 0.25 - buttonSize / 2; left = rect.left + edgeOffset; break; case "left-three-quarter": top = rect.top + rect.height * 0.75 - buttonSize / 2; left = rect.left + edgeOffset; break; case "top-left-quarter": top = rect.top + edgeOffset; left = rect.left + rect.width * 0.25 - buttonSize / 2; break; case "top-three-quarter": top = rect.top + edgeOffset; left = rect.left + rect.width * 0.75 - buttonSize / 2; break; case "bottom-left-quarter": top = rect.bottom - buttonSize - edgeOffset; left = rect.left + rect.width * 0.25 - buttonSize / 2; break; case "bottom-three-quarter": top = rect.bottom - buttonSize - edgeOffset; left = rect.left + rect.width * 0.75 - buttonSize / 2; break; case "top-center": default: top = rect.top + edgeOffset; left = rect.left + rect.width / 2 - buttonSize / 2; break; } if (left < 5) left = 5; if (top < 5) top = 5; if (top > window.innerHeight - buttonSize - 5) { top = window.innerHeight - buttonSize - 5; } if (left > window.innerWidth - buttonSize - 5) { left = window.innerWidth - buttonSize - 5; } button.style.top = `${top}px`; button.style.left = `${left}px`; button.style.zIndex = zIndex + "!important"; }; const pickTarget = () => { const targets = document.querySelectorAll(targetSelector); for (const element of targets) { const rect = element.getBoundingClientRect(); if (!this.isPointInRect(state.mouseX, state.mouseY, rect)) continue; if (!this.isVisibleElement(element, minTargetSize)) continue; if (targetSelector === "video" && element.readyState < 3) continue; return element; } if (extraTargetSelector) { const extraTargets = document.querySelectorAll(extraTargetSelector); for (const element of extraTargets) { const rect = element.getBoundingClientRect(); if (this.isPointInRect(state.mouseX, state.mouseY, rect)) { return element; } } } return null; }; const checkHover = () => { if (state.destroyed) return; if (state.disabled) { button.style.display = "none"; state.activeTarget = null; return; } if (state.mouseX < 0 || state.mouseY < 0) return; const buttonRect = button.getBoundingClientRect(); const isHoveringButton = button.style.display === "flex" && this.isPointInRect(state.mouseX, state.mouseY, buttonRect); if (isHoveringButton) { button.classList.add("all-in-one-downloader-daniel-hover"); return; } button.classList.remove("all-in-one-downloader-daniel-hover"); const found = pickTarget(); if (found) { state.activeTarget = found; button.style.display = "flex"; updateButtonPos(); } else { button.style.display = "none"; state.activeTarget = null; } }; const onMouseMove = (event) => { if (state.destroyed) return; state.mouseX = event.clientX; state.mouseY = event.clientY; checkHover(); }; const onMouseDown = (event) => { if (state.destroyed) return; if (state.disabled) return; if (button.style.display !== "flex") return; const rect = button.getBoundingClientRect(); if (!this.isPointInRect(event.clientX, event.clientY, rect)) return; event.preventDefault(); event.stopPropagation(); if (typeof onClick === "function") { Promise.resolve(onClick(state.activeTarget)).catch((error) => { }); } }; const onScroll = () => { if (state.destroyed) return; if (state.disabled) return; if (state.activeTarget && button.style.display === "flex") { updateButtonPos(); } }; window.addEventListener("mousemove", onMouseMove, true); window.addEventListener("mousedown", onMouseDown, true); window.addEventListener("scroll", onScroll, { passive: true, capture: true }); const timer = setInterval(() => { if (state.destroyed) return; if (state.mouseX >= 0 && state.mouseY >= 0) { checkHover(); } }, 300); const destroy = () => { if (state.destroyed) { return; } state.destroyed = true; clearInterval(timer); window.removeEventListener("mousemove", onMouseMove, true); window.removeEventListener("mousedown", onMouseDown, true); window.removeEventListener("scroll", onScroll, true); button.remove(); style.remove(); this.instances.delete(id); }; const hideButton = () => { button.style.display = "none"; }; const disable = () => { state.disabled = true; button.style.display = "none"; state.activeTarget = null; }; const enable = () => { state.disabled = false; checkHover(); }; const showButtonForTarget = (target) => { if (!target || state.disabled) return; state.activeTarget = target; button.style.display = "flex"; updateButtonPos(); }; const setButtonPosition = (position, edgeOffset) => { const validPositions = /* @__PURE__ */ new Set([ "top-center", "bottom-center", "left-center", "right-center", "top-left-quarter", "top-three-quarter", "bottom-left-quarter", "bottom-three-quarter", "right-top-quarter", "right-three-quarter", "left-top-quarter", "left-three-quarter" ]); if (validPositions.has(position)) { state.buttonPosition = position; } if (edgeOffset != null && Number.isFinite(Number(edgeOffset))) { state.buttonEdgeOffset = Number(edgeOffset); } if (state.activeTarget && button.style.display === "flex") { updateButtonPos(); } }; const getCurrentTargetElement = () => state.activeTarget; const api = { getCurrentTargetElement, hideButton, disable, enable, showButtonForTarget, setButtonPosition, destroy }; this.instances.set(id, { api }); return api; }, mountVideoHoverDownload: function(spec = {}) { const { getDownloadUrl, invokeDownload = CommonUtils.downloaderEvent, ...hoverButtonOptions } = spec; if (typeof getDownloadUrl !== "function") { return null; } return this.createHoverDownloadButton({ ...hoverButtonOptions, onClick: async (target) => { if (!target) { return; } try { const url = await Promise.resolve(getDownloadUrl(target)); if (url && !String(url).includes("undefined")) { invokeDownload(url); } } catch (error) { } } }); }, createHoverButton: function(options = {}) { return this.createHoverDownloadButton(options); } }; const YouTubeDownloader = { downloadVideo: function() { const url = "https://www.tool77.com/" + CommonUtils.getSupportedLang() + "/v/downloader?url=" + encodeURIComponent(window.location.href); CommonUtils.openInTab(url); }, genrate: function() { return new Promise((resolve) => { const buttonId = "all-in-one-downloader"; const boxContainer = document.createElement("div"); boxContainer.className = "ytp-button"; boxContainer.id = buttonId; boxContainer.setAttribute("style", `position: relative;display: inline-block;width: 48px;height: 100%;`); const boxInner = document.createElement("div"); boxInner.setAttribute("style", `position: absolute;width: 100%;height: 100%; `); const boxActiveButton = document.createElement("button"); boxActiveButton.setAttribute("style", `background-color: transparent;width: 100%;height: 100%;outline: none;flex: 1 1 0%;display: flex;-webkit-box-align: center;align-items: center;-webkit-box-pack: center;justify-content: center;border: none;padding: 0px;cursor: pointer;`); boxContainer.appendChild(boxInner); boxInner.appendChild(boxActiveButton); boxActiveButton.appendChild(CommonUtils.genrateDownloadSvg("#FFF")); boxContainer.addEventListener("click", () => { this.downloadVideo(); }); const genrateHtml = () => { const player = document.querySelector("#player-container-outer .html5-video-player"); if (player) { const rightControls = player.querySelector(".ytp-right-controls"); if (rightControls) { rightControls.prepend(boxContainer); } } }; const interval = setInterval(() => { if (!document.querySelector("#" + buttonId)) { genrateHtml(); } else { resolve(); clearInterval(interval); } }, 777); }); }, genrateShorts: function() { let downloadHud = DownloadHud.mountVideoHoverDownload({ buttonSize: 48, iconSize: 25, buttonEdgeOffset: 15, getDownloadUrl: () => { return window.location.href; } }); setInterval(() => { if (window.location.href.indexOf("/shorts/") != -1) { downloadHud.enable(); } else { downloadHud.disable(); } }, 800); }, run: function() { this.genrate(); this.genrateShorts(); }, start: function() { const isRun = /youtube\.com/.test(window.location.host); if (isRun) { this.run(); } } }; const TiktokDownloader = { extractLastDashNumber: function(str) { if (!str) return null; const match = str.match(/-(\d+)$/); return match ? match[1] : null; }, getPlayUrl: function(target) { const container = CommonUtils.findParentByClassContains(target, "xgplayer-container", 5); if (container && container.getAttribute("id")) { const playId = this.extractLastDashNumber(container.getAttribute("id")); if (playId) { return "https://www.tiktok.com/@_/video/" + playId; } } return window.location.href.split("?")[0]; }, run: function() { const downloadHud = DownloadHud.mountVideoHoverDownload({ getDownloadUrl: (target) => this.getPlayUrl(target) }); const updateButtonPosition = () => { const pathname = window.location.pathname; if (/^\/@[^/]+$/.test(pathname)) { downloadHud.setButtonPosition("bottom-center"); } else if (/\/@[^/]+\/video\/.+$/.test(pathname)) { if (document.querySelector("[aria-label='Close']")) { downloadHud.setButtonPosition("right-three-quarter"); } else { downloadHud.setButtonPosition("top-center"); } } else { downloadHud.setButtonPosition("top-center"); } }; CommonUtils.onLocationChange(() => { updateButtonPosition(); }); updateButtonPosition(); }, start: function() { const isRun = /tiktok\.com/.test(window.location.host); if (isRun) { this.run(); } } }; const ThreadsDownloader = { downloadVideo: function(url) { const openUrl = "https://www.tool77.com/" + CommonUtils.getSupportedLang() + "/v/downloader?url=" + encodeURIComponent(url); CommonUtils.openInTab(openUrl); }, run: function() { setInterval(() => { document.querySelectorAll("*[data-pressable-container='true']:not([detectvs='true'])").forEach((element) => { if (element.querySelector("video") === null) { return; } element.setAttribute("detectvs", "true"); const buttons = element.querySelectorAll("*[role='button']"); const btnLen = buttons.length; if (btnLen <= 1) { return; } let buttonsWithNumber = Array.from(buttons).filter((btn) => /\d/.test(btn.textContent || "")); if (buttonsWithNumber.length === 0) { buttonsWithNumber = buttons; } const lastButton = buttonsWithNumber[buttonsWithNumber.length - 1]; const shareButton = lastButton.parentElement; const downloadButton = shareButton.cloneNode(false); downloadButton.appendChild(CommonUtils.genrateDownloadSvg("#505050", 20, 20)); downloadButton.style.cursor = "pointer"; shareButton.insertAdjacentElement("afterend", downloadButton); downloadButton.addEventListener("click", (e) => { const links = element.querySelectorAll("a[role='link']"); e.stopPropagation(); e.preventDefault(); let link = ""; for (let i = 0; i < links.length; i++) { if (links[i].getAttribute("href").indexOf("/post/") != -1) { link = "https://www.threads.com" + links[i].getAttribute("href"); } } if (!!link) { this.downloadVideo(link); } else { this.downloadVideo(window.location.href); } }); }); }, 777); }, start: function() { const isYoutube = /threads\.com/.test(window.location.host); if (isYoutube) { this.run(); } } }; const TwitterDownloader = { svg: ` `, showSensitive: true, isTweetdeck: () => window.location.host.includes("tweetdeck"), extractStatusId: (url) => url ? (url.match(/\/status\/(\d+)/) || [null, null])[1] : null, downloadVideo: function(statusIds) { const url = "https://www.tool77.com/" + CommonUtils.getSupportedLang() + "/v/downloader?url=https%3A%2F%2Fx.com/_/status/" + statusIds; CommonUtils.openInTab(url); }, addButtonTo: function(article) { if (article.dataset.detected) return; article.dataset.detected = "true"; const statusIds = Array.from(article.querySelectorAll('a[href*="/status/"]')).map((el) => this.extractStatusId(el.href)).filter((id) => id); if (statusIds.length === 0) return; const mediaSelector = [ 'div[role="progressbar"]', 'button[data-testid="playButton"]', 'div[data-testid="videoComponent"]', 'a[href="/settings/content_you_see"]', "div.media-image-container", "div.media-preview-container", 'div[aria-labelledby]>div:first-child>div[role="button"][tabindex="0"]' ]; const hasMedia = article.querySelector(mediaSelector.join(",")); if (hasMedia) { const btnGroup = article.querySelector('div[role="group"]:last-of-type, ul.tweet-actions, ul.tweet-detail-actions'); if (btnGroup) { const btnShare = Array.from(btnGroup.querySelectorAll(":scope>div>div, li.tweet-action-item>a, li.tweet-detail-action-item>a")).pop().parentNode; const btnDownload = btnShare.cloneNode(true); btnDownload.classList.add("aio-twitter-down"); btnDownload.style.marginLeft = "10px"; btnDownload.style.cursor = "pointer"; btnDownload.querySelector("button")?.removeAttribute("disabled"); const svgContainer = this.isTweetdeck() ? btnDownload.firstElementChild : btnDownload.querySelector("svg"); if (svgContainer) { if (this.isTweetdeck()) { svgContainer.innerHTML = `${this.svg}`; svgContainer.removeAttribute("rel"); btnDownload.classList.replace("pull-left", "pull-right"); } else { svgContainer.innerHTML = this.svg; } } btnGroup.insertBefore(btnDownload, btnShare.nextSibling); btnDownload.onclick = () => { this.downloadVideo(statusIds); }; if (this.showSensitive) { article.querySelector('div[aria-labelledby] div[role="button"][tabindex="0"]:not([data-testid]) > div[dir] > span > span')?.click(); } } } }, addButtonToMedia: function(listitems) { listitems.forEach((li) => { if (li.dataset.detected) return; li.dataset.detected = "true"; const statusElement = li.querySelector('a[href*="/status/"]'); const statusId = statusElement ? this.extractStatusId(statusElement.href) : null; if (!statusId) return; const btnDownload = document.createElement("div"); btnDownload.classList.add("aio-twitter-down", "aio-twitter-media"); btnDownload.style.cursor = "pointer"; btnDownload.innerHTML = `
${this.svg}
`; li.style.position = li.style.position || "relative"; li.appendChild(btnDownload); btnDownload.onclick = () => { this.downloadVideo(statusId); }; }); }, detect: function(node) { let article = node.tagName == "ARTICLE" && node || node.tagName == "DIV" && (node.querySelector("article") || node.closest("article")); if (article) { this.addButtonTo(article); } let listitems = node.tagName == "LI" && node.getAttribute("role") == "listitem" && [node] || node.tagName == "DIV" && node.querySelectorAll('li[role="listitem"]'); if (listitems) { this.addButtonToMedia(listitems); } }, injectStyles: function() { if (document.getElementById("aio-twitter-style")) return; const style = document.createElement("style"); style.id = "aio-twitter-style"; style.textContent = ` .aio-twitter-down { margin-left: 12px; order: 99; } .aio-twitter-down:hover > div > div > div > div { color: rgba(29, 161, 242, 1); } .aio-twitter-down:hover > div > div > div > div > div { background-color: rgba(29, 161, 242, 0.1); } .aio-twitter-down:active > div > div > div > div > div { background-color: rgba(29, 161, 242, 0.2); } .aio-twitter-down:hover svg { color: rgba(29, 161, 242, 1); } .aio-twitter-down:hover div:first-child:not(:last-child) { background-color: rgba(29, 161, 242, 0.1); } .aio-twitter-down:active div:first-child:not(:last-child) { background-color: rgba(29, 161, 242, 0.2); } .aio-twitter-down.aio-twitter-media { position: absolute; right: 0; } .aio-twitter-down.aio-twitter-media > div { display: flex; border-radius: 99px; margin: 2px; } .aio-twitter-down.aio-twitter-media > div > div { display: flex; margin: 6px; color: #fff; } .aio-twitter-down.aio-twitter-media:hover > div { background-color: rgba(255, 255, 255, 0.6); } .aio-twitter-down.aio-twitter-media:hover > div > div { color: rgba(29, 161, 242, 1); } .aio-twitter-down.aio-twitter-media:not(:hover) > div > div { filter: drop-shadow(0 0 1px #000); }`; document.head.appendChild(style); }, run: function() { this.injectStyles(); const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType === 1) { this.detect(node); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); }, start: function() { const isRun = /(twitter|x)\.com/.test(window.location.host); if (isRun) { this.run(); } } }; const InstagramDownloader = { getPlayUrl: function(target) { const clean = (url) => String(url).split("?")[0]; const article = target?.closest?.("article"); if (article) { const selector = ['a[href*="/p/"]', 'a[href*="/reels/"]', 'a[href*="/reel/"]'].join(", "); const href = article.querySelector(selector)?.getAttribute("href"); if (href) return clean(window.location.origin + href); } return clean(window.location.href); }, run: function() { DownloadHud.mountVideoHoverDownload({ getDownloadUrl: (target) => this.getPlayUrl(target) }); }, start: function() { const isRun = /instagram\.com/.test(window.location.host); if (isRun) { this.run(); } } }; const DouyinDownloader = { downloadVideo: function() { CommonUtils.openInTab("https://www.tool77.com/zh-CN/v/downloader?url=" + encodeURIComponent(window.location.href)); }, genrate: function() { const controllers = document.querySelectorAll(".xg-inner-controls:not([downloaderx='true'])"); if (controllers.length === 0) return; controllers.forEach((controller) => { controller.setAttribute("downloaderx", "true"); const rightGrid = controller.querySelector(".xg-right-grid"); if (rightGrid) { const fullscreen = rightGrid.querySelector(".xgplayer-fullscreen"); if (fullscreen) { const donwload = fullscreen.cloneNode(false); donwload.style.display = "flex"; donwload.style.alignItems = "center"; donwload.style.justifyContent = "center"; donwload.style.margin = "0 7px"; donwload.appendChild(CommonUtils.genrateDownloadSvg("#FFF", 20, 20)); rightGrid.before(donwload); donwload.addEventListener("click", () => { this.downloadVideo(); }); } } }); }, start: function() { const isRun = /douyin\.com/.test(window.location.host); if (isRun) { setInterval(() => { this.genrate(); }, 777); } } }; const XiaoHongShuDownloader = { downloadVideo: function() { CommonUtils.openInTab("https://www.tool77.com/zh-CN/v/downloader?url=" + encodeURIComponent(window.location.href)); }, genrate: function() { if (!/www\.xiaohongshu\.com\/explore\//.test(window.location.href)) { return; } const noteContainer = document.querySelector("#noteContainer"); if (!noteContainer) { return; } if (noteContainer.querySelector("*[x-add='true']")) { return; } const mediaContainer = noteContainer.querySelector(".media-container"); if (!mediaContainer) { return; } const randomId = Math.floor(1e5 + Math.random() * 9e5); GM_addStyle(` .sc-download-` + randomId + `{ cursor:pointer; justify-content:center; align-items:center; width:40px; height:40px; z-index:99999; position:absolute; right:55px; top:10px; border-radius:50%; display: flex; border-radius:50%; } .sc-download-` + randomId + `::before { content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 2px; background: linear-gradient(90deg,red, orange, yellow, green, cyan, blue, violet, red); background-size: 200% 200%; opacity: 0; transition: opacity .25s; -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor; mask-composite: exclude; } .sc-download-` + randomId + `:hover::before { opacity: 1; animation: rainbowMove 3s linear infinite; } @keyframes rainbowMove { 0% { background-position: 0% 50%; } 100% { background-position: 200% 50%; } } `); const downloadElement = document.createElement("div"); downloadElement.setAttribute("x-add", "true"); downloadElement.classList.add("sc-download-" + randomId); downloadElement.appendChild(CommonUtils.genrateDownloadSvg("#FFF")); downloadElement.style.cursor = "pointer"; mediaContainer.appendChild(downloadElement); downloadElement.addEventListener("click", () => { this.downloadVideo(); }); }, start: function() { const isRun = /xiaohongshu\.com/.test(window.location.host); if (isRun) { setInterval(() => { this.genrate(); }, 777); } } }; const Downloader = { cssInjected: false, isRun: function() { const hosts = [ /googlevideo\.com/, /tiktokcdn\.com/, /douyinvod\.com/, /fbcdn\.net/, /video\.twimg\.com/, /zjcdn\.com/, /snssdk\.com/, /tiktokv\.com/, /douyinstatic\.com/, /twimg\.com/, /xhscdn\.com/, /amemv\.com/, /tikwm\.com/, /tiktokcdn-eu\.com/, /tiktokcdn-us\.com/, /douyinpic\.com/, /douyinmusicpromotion\.com/, /cdninstagram\.com/, /ted\.com/, /licdn\.com/, /sc-cdn\.net/, /pinimg\.com/, /tedcdn\.com/, /dmcdn\.net/ ]; const currentHost = window.location.host; return hosts.some((rule) => rule.test(currentHost)) && document.contentType !== "text/html"; }, getLanguage: function() { const lang = CommonUtils.getSupportedLang(); const langMap = { "en": { startDownload: "Start Download", cancelDownload: "Cancel Download", downloadProgress: "Download Progress:", downloadCompleted: "Download completed", downloadCompletedToast: "Download completed!", reDownload: "Download Again", downloadFailed: "Download failed", downloadFailedToast: "Download failed, please check console", downloadCanceledToast: "Download canceled", downloaderTip: "* If download fails, save manually: right click -> Save video as.
* If the file is too large (over 200MB), manual saving is recommended." }, "es": { startDownload: "Iniciar descarga", cancelDownload: "Cancelar descarga", downloadProgress: "Progreso de descarga:", downloadCompleted: "Descarga completada", downloadCompletedToast: "¡Descarga completada!", reDownload: "Descargar de nuevo", downloadFailed: "Error de descarga", downloadFailedToast: "La descarga falló, revise la consola", downloadCanceledToast: "Descarga cancelada", downloaderTip: "* Si la descarga falla, guarda manualmente: clic derecho -> Guardar video como.
* Si el archivo es demasiado grande (mas de 200MB), se recomienda guardar manualmente." }, "fr": { startDownload: "Demarrer le telechargement", cancelDownload: "Annuler le telechargement", downloadProgress: "Progression du telechargement :", downloadCompleted: "Telechargement termine", downloadCompletedToast: "Telechargement termine !", reDownload: "Telecharger a nouveau", downloadFailed: "Echec du telechargement", downloadFailedToast: "Le telechargement a echoue, verifiez la console", downloadCanceledToast: "Telechargement annule", downloaderTip: "* Si le telechargement echoue, enregistrez manuellement : clic droit -> Enregistrer la video sous.
* Si le fichier est trop volumineux (plus de 200MB), l'enregistrement manuel est recommande." }, "pt": { startDownload: "Iniciar download", cancelDownload: "Cancelar download", downloadProgress: "Progresso do download:", downloadCompleted: "Download concluido", downloadCompletedToast: "Download concluido!", reDownload: "Baixar novamente", downloadFailed: "Falha no download", downloadFailedToast: "Falha no download, verifique o console", downloadCanceledToast: "Download cancelado", downloaderTip: "* Se o download falhar, salve manualmente: clique direito -> Salvar video como.
* Se o arquivo for muito grande (acima de 200MB), recomenda-se salvar manualmente." }, "ru": { startDownload: "Начать загрузку", cancelDownload: "Отменить загрузку", downloadProgress: "Прогресс загрузки:", downloadCompleted: "Загрузка завершена", downloadCompletedToast: "Загрузка завершена!", reDownload: "Скачать снова", downloadFailed: "Ошибка загрузки", downloadFailedToast: "Сбой загрузки, проверьте консоль", downloadCanceledToast: "Загрузка отменена", downloaderTip: "* Если загрузка не удалась, сохраните вручную: правый клик -> Сохранить видео как.
* Если файл слишком большой (более 200MB), рекомендуется сохранять вручную." }, "ja": { startDownload: "ダウンロード開始", cancelDownload: "ダウンロードをキャンセル", downloadProgress: "ダウンロード進行状況:", downloadCompleted: "ダウンロード完了", downloadCompletedToast: "ダウンロードが完了しました!", reDownload: "再ダウンロード", downloadFailed: "ダウンロード失敗", downloadFailedToast: "ダウンロードに失敗しました。コンソールを確認してください", downloadCanceledToast: "ダウンロードをキャンセルしました", downloaderTip: "* ダウンロードに失敗した場合は手動保存: 右クリック -> 動画を名前を付けて保存。
* ファイルが大きすぎる場合(200MB超)、手動保存を推奨します。" }, "de": { startDownload: "Download starten", cancelDownload: "Download abbrechen", downloadProgress: "Download-Fortschritt:", downloadCompleted: "Download abgeschlossen", downloadCompletedToast: "Download abgeschlossen!", reDownload: "Erneut herunterladen", downloadFailed: "Download fehlgeschlagen", downloadFailedToast: "Download fehlgeschlagen, bitte Konsole pruefen", downloadCanceledToast: "Download abgebrochen", downloaderTip: "* Wenn der Download fehlschlaegt, manuell speichern: Rechtsklick -> Video speichern unter.
* Wenn die Datei zu gross ist (uber 200MB), wird manuelles Speichern empfohlen." }, "ko": { startDownload: "다운로드 시작", cancelDownload: "다운로드 취소", downloadProgress: "다운로드 진행률:", downloadCompleted: "다운로드 완료", downloadCompletedToast: "다운로드가 완료되었습니다!", reDownload: "다시 다운로드", downloadFailed: "다운로드 실패", downloadFailedToast: "다운로드에 실패했습니다. 콘솔을 확인하세요", downloadCanceledToast: "다운로드가 취소되었습니다", downloaderTip: "* 다운로드 실패 시 수동 저장: 마우스 오른쪽 클릭 -> 동영상을 다른 이름으로 저장.
* 파일이 너무 큰 경우(200MB 이상) 수동 저장을 권장합니다." }, "it": { startDownload: "Avvia download", cancelDownload: "Annulla download", downloadProgress: "Avanzamento download:", downloadCompleted: "Download completato", downloadCompletedToast: "Download completato!", reDownload: "Scarica di nuovo", downloadFailed: "Download non riuscito", downloadFailedToast: "Download non riuscito, controlla la console", downloadCanceledToast: "Download annullato", downloaderTip: "* Se il download fallisce, salva manualmente: clic destro -> Salva video con nome.
* Se il file e troppo grande (oltre 200MB), e consigliato il salvataggio manuale." }, "id": { startDownload: "Mulai unduh", cancelDownload: "Batalkan unduhan", downloadProgress: "Progres unduhan:", downloadCompleted: "Unduhan selesai", downloadCompletedToast: "Unduhan selesai!", reDownload: "Unduh lagi", downloadFailed: "Unduhan gagal", downloadFailedToast: "Unduhan gagal, periksa konsol", downloadCanceledToast: "Unduhan dibatalkan", downloaderTip: "* Jika unduhan gagal, simpan manual: klik kanan -> Simpan video sebagai.
* Jika file terlalu besar (lebih dari 200MB), disarankan simpan manual." }, "tr": { startDownload: "Indirmeyi baslat", cancelDownload: "Indirmeyi iptal et", downloadProgress: "Indirme ilerlemesi:", downloadCompleted: "Indirme tamamlandi", downloadCompletedToast: "Indirme tamamlandi!", reDownload: "Tekrar indir", downloadFailed: "Indirme basarisiz", downloadFailedToast: "Indirme basarisiz, lutfen konsolu kontrol edin", downloadCanceledToast: "Indirme iptal edildi", downloaderTip: "* Indirme basarisiz olursa manuel kaydedin: saga tik -> Videoyu farkli kaydet.
* Dosya cok buyukse (200MB ustu), manuel kaydetmeniz onerilir." }, "pl": { startDownload: "Rozpocznij pobieranie", cancelDownload: "Anuluj pobieranie", downloadProgress: "Postep pobierania:", downloadCompleted: "Pobieranie zakonczone", downloadCompletedToast: "Pobieranie zakonczone!", reDownload: "Pobierz ponownie", downloadFailed: "Pobieranie nieudane", downloadFailedToast: "Pobieranie nieudane, sprawdz konsole", downloadCanceledToast: "Pobieranie anulowane", downloaderTip: "* Jesli pobieranie sie nie powiedzie, zapisz recznie: prawy przycisk -> Zapisz wideo jako.
* Jesli plik jest zbyt duzy (powyej 200MB), zaleca sie zapis reczny." }, "uk": { startDownload: "Почати завантаження", cancelDownload: "Скасувати завантаження", downloadProgress: "Прогрес завантаження:", downloadCompleted: "Завантаження завершено", downloadCompletedToast: "Завантаження завершено!", reDownload: "Завантажити знову", downloadFailed: "Помилка завантаження", downloadFailedToast: "Не вдалося завантажити, перевірте консоль", downloadCanceledToast: "Завантаження скасовано", downloaderTip: "* Якщо завантаження не вдалося, збережіть вручну: правий клік -> Зберегти відео як.
* Якщо файл занадто великий (понад 200MB), рекомендовано зберігати вручну." }, "nl": { startDownload: "Download starten", cancelDownload: "Download annuleren", downloadProgress: "Downloadvoortgang:", downloadCompleted: "Download voltooid", downloadCompletedToast: "Download voltooid!", reDownload: "Opnieuw downloaden", downloadFailed: "Download mislukt", downloadFailedToast: "Download mislukt, controleer de console", downloadCanceledToast: "Download geannuleerd", downloaderTip: "* Als downloaden mislukt, handmatig opslaan: rechtermuisknop -> Video opslaan als.
* Als het bestand te groot is (meer dan 200MB), wordt handmatig opslaan aanbevolen." }, "vi": { startDownload: "Bat dau tai xuong", cancelDownload: "Huy tai xuong", downloadProgress: "Tien do tai xuong:", downloadCompleted: "Tai xuong hoan tat", downloadCompletedToast: "Tai xuong hoan tat!", reDownload: "Tai lai", downloadFailed: "Tai xuong that bai", downloadFailedToast: "Tai xuong that bai, vui long kiem tra console", downloadCanceledToast: "Da huy tai xuong", downloaderTip: "* Neu tai xuong that bai, hay luu thu cong: nhap chuot phai -> Luu video thanh.
* Neu tep qua lon (tren 200MB), khuyen nghi luu thu cong." }, "th": { startDownload: "เริ่มดาวน์โหลด", cancelDownload: "ยกเลิกการดาวน์โหลด", downloadProgress: "ความคืบหน้าการดาวน์โหลด:", downloadCompleted: "ดาวน์โหลดเสร็จสิ้น", downloadCompletedToast: "ดาวน์โหลดเสร็จสิ้น!", reDownload: "ดาวน์โหลดอีกครั้ง", downloadFailed: "ดาวน์โหลดล้มเหลว", downloadFailedToast: "ดาวน์โหลดล้มเหลว โปรดตรวจสอบคอนโซล", downloadCanceledToast: "ยกเลิกการดาวน์โหลดแล้ว", downloaderTip: "* หากดาวน์โหลดล้มเหลว ให้บันทึกด้วยตนเอง: คลิกขวา -> บันทึกวิดีโอเป็น.
* หากไฟล์มีขนาดใหญ่เกินไป (มากกว่า 200MB) แนะนำให้บันทึกด้วยตนเอง." }, "ar": { startDownload: "بدء التنزيل", cancelDownload: "إلغاء التنزيل", downloadProgress: "تقدم التنزيل:", downloadCompleted: "اكتمل التنزيل", downloadCompletedToast: "اكتمل التنزيل!", reDownload: "تنزيل مرة اخرى", downloadFailed: "فشل التنزيل", downloadFailedToast: "فشل التنزيل، يرجى التحقق من وحدة التحكم", downloadCanceledToast: "تم إلغاء التنزيل", downloaderTip: "* إذا فشل التنزيل، احفظ يدويًا: انقر بزر الماوس الأيمن -> حفظ الفيديو باسم.
* إذا كان الملف كبيرًا جدًا (أكثر من 200MB)، يوصى بالحفظ اليدوي." }, "fa": { startDownload: "شروع دانلود", cancelDownload: "لغو دانلود", downloadProgress: "پیشرفت دانلود:", downloadCompleted: "دانلود کامل شد", downloadCompletedToast: "دانلود کامل شد!", reDownload: "دانلود دوباره", downloadFailed: "دانلود ناموفق بود", downloadFailedToast: "دانلود ناموفق بود، لطفا کنسول را بررسی کنید", downloadCanceledToast: "دانلود لغو شد", downloaderTip: "* اگر دانلود ناموفق بود، به صورت دستی ذخیره کنید: راست کلیک -> ذخیره ویدیو با نام.
* اگر فایل خیلی بزرگ است (بیش از 200MB)، ذخیره دستی توصیه می شود." }, "hi": { startDownload: "डाउनलोड शुरू करें", cancelDownload: "डाउनलोड रद्द करें", downloadProgress: "डाउनलोड प्रगति:", downloadCompleted: "डाउनलोड पूरा हुआ", downloadCompletedToast: "डाउनलोड पूरा हुआ!", reDownload: "फिर से डाउनलोड करें", downloadFailed: "डाउनलोड असफल रहा", downloadFailedToast: "डाउनलोड असफल रहा, कृपया कंसोल जांचें", downloadCanceledToast: "डाउनलोड रद्द कर दिया गया", downloaderTip: "* यदि डाउनलोड असफल हो, मैन्युअली सेव करें: राइट क्लिक -> वीडियो को इस नाम से सेव करें.
* यदि फाइल बहुत बड़ी है (200MB से अधिक), मैन्युअल सेव करना सुझाया जाता है." }, "ms": { startDownload: "Mula muat turun", cancelDownload: "Batal muat turun", downloadProgress: "Kemajuan muat turun:", downloadCompleted: "Muat turun selesai", downloadCompletedToast: "Muat turun selesai!", reDownload: "Muat turun semula", downloadFailed: "Muat turun gagal", downloadFailedToast: "Muat turun gagal, sila semak konsol", downloadCanceledToast: "Muat turun dibatalkan", downloaderTip: "* Jika muat turun gagal, simpan secara manual: klik kanan -> Simpan video sebagai.
* Jika fail terlalu besar (lebih 200MB), simpan secara manual adalah disyorkan." }, "zh-CN": { startDownload: "开始下载", cancelDownload: "取消下载", downloadProgress: "下载进度:", downloadCompleted: "下载完成", downloadCompletedToast: "下载完成!", reDownload: "重新下载", downloadFailed: "下载失败", downloadFailedToast: "下载失败,请查看控制台", downloadCanceledToast: "下载已取消", downloaderTip: "* 如果下载失败,请手动保存:鼠标右键 -> 视频另存为。
* 如果文件太大(200MB)以上,推荐使用手动保存。" }, "zh-TW": { startDownload: "開始下載", cancelDownload: "取消下載", downloadProgress: "下載進度:", downloadCompleted: "下載完成", downloadCompletedToast: "下載完成!", reDownload: "重新下載", downloadFailed: "下載失敗", downloadFailedToast: "下載失敗,請查看控制台", downloadCanceledToast: "下載已取消", downloaderTip: "* 如果下載失敗,請手動儲存:滑鼠右鍵 -> 影片另存為。
* 如果檔案太大(200MB)以上,建議使用手動儲存。" } }; return langMap[lang] || langMap["en"]; }, generateCss: function() { if (this.cssInjected) { return; } GM_addStyle(` .downloader-gui { position: fixed; top: 20px; right: 20px; width: 250px; padding: 10px; background: rgba(0,0,0,0.8); color: #fff; font-family: Arial, sans-serif; border-radius: 8px; z-index: 99999; font-size: 15px; } .downloader-btn { width: 100%; padding: 6px 0; margin-bottom: 6px; color: #fff; border: none; border-radius: 4px; cursor: pointer; } .downloader-download-btn { background: #1a73e8; } .downloader-cancel-btn { background: #e53935; display: none; } .downloader-progress-container { display: none; } .downloader-progress-label { margin-bottom: 6px; } .downloader-progress-wrap { background: #555; height: 10px; border-radius: 4px; overflow: hidden; } .downloader-progress-bar { width: 0%; height: 100%; background: #0f0; } .downloader-progress-text { margin-top: 4px; font-size: 12px; } .downloader-tip{ font-size:11px; margin-top: 10px; } .downloader-toast { font-size: 15px; position: fixed; top: 20px; left: 50%; transform: translateX(-50%); padding: 10px 20px; background: rgba(0,0,0,0.8); color: #fff; border-radius: 6px; font-family: Arial, sans-serif; z-index: 100000; opacity: 0; transition: opacity 0.3s ease; } `); this.cssInjected = true; }, generateGui: function(language) { const mountNode = document.body || document.documentElement; if (!mountNode) { return null; } this.generateCss(); const gui = document.createElement("div"); gui.className = "downloader-gui"; gui.innerHTML = `
${language.downloadProgress}
0%
${language.downloaderTip}
`; mountNode.appendChild(gui); const downloadBtn = gui.querySelector('[data-role="download-btn"]'); const cancelBtn = gui.querySelector('[data-role="cancel-btn"]'); const progressContainer = gui.querySelector('[data-role="progress-container"]'); const progressBar = gui.querySelector('[data-role="progress-bar"]'); const progressText = gui.querySelector('[data-role="progress-text"]'); return { downloadBtn, cancelBtn, progressContainer, progressBar, progressText }; }, showToast: function(message) { const mountNode = document.body || document.documentElement; if (!mountNode) { return; } this.generateCss(); const toast = document.createElement("div"); toast.className = "downloader-toast"; toast.textContent = message; mountNode.appendChild(toast); requestAnimationFrame(() => toast.style.opacity = 1); setTimeout(() => { toast.style.opacity = 0; setTimeout(() => toast.remove(), 300); }, 2e3); }, setDownloadingState: function(doms) { const { downloadBtn, cancelBtn, progressContainer, progressBar, progressText } = doms; downloadBtn.style.display = "none"; cancelBtn.style.display = "block"; progressContainer.style.display = "block"; progressBar.style.width = "0%"; progressText.textContent = "0%"; }, setIdleState: function(doms, language) { const { downloadBtn, cancelBtn, progressContainer } = doms; progressContainer.style.display = "none"; cancelBtn.style.display = "none"; downloadBtn.style.display = "block"; downloadBtn.textContent = language.reDownload; }, currentRequest: null, addEvent: function(downloadData, doms, language) { const { downloadBtn, cancelBtn, progressContainer, progressBar, progressText } = doms; downloadBtn.addEventListener("click", () => { const onstart = () => { this.setDownloadingState(doms); }; const onprogress = (percent, loaded, total) => { progressBar.style.width = percent + "%"; progressText.textContent = `${percent}% | ${CommonUtils.formatBytes(loaded)}/${CommonUtils.formatBytes(total)}`; }; const onload = () => { this.currentRequest = null; progressBar.style.width = "100%"; progressText.textContent = language.downloadCompleted; this.showToast(language.downloadCompletedToast); this.setIdleState(doms, language); }; const onerror = (err) => { this.currentRequest = null; progressText.textContent = language.downloadFailed; this.showToast(language.downloadFailedToast); this.setIdleState(doms, language); }; this.download(downloadData.url, downloadData.filename, onstart, onprogress, onload, onerror); }); cancelBtn.addEventListener("click", () => { if (this.currentRequest) { this.currentRequest.abort(); this.currentRequest = null; this.showToast(language.downloadCanceledToast); this.setIdleState(doms, language); } }); }, download: function(url, filename, onstart, onprogress, onload, onerror) { onstart(); this.currentRequest = GM_xmlhttpRequest({ method: "GET", url, responseType: "blob", onprogress: function(event) { if (event.lengthComputable) { const percent = (event.loaded / event.total * 100).toFixed(2); onprogress(percent, event.loaded, event.total); } }, onload: function(response) { if (response.status < 200 || response.status >= 300 || !response.response) { onerror(new Error(`Request failed with status ${response.status}`)); return; } const blob = response.response; const a = document.createElement("a"); const objectUrl = URL.createObjectURL(blob); a.href = objectUrl; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(objectUrl), 1e3); onload(); }, onerror: function(err) { onerror(err); }, onabort: function() { onerror(new Error("Request aborted")); }, ontimeout: function() { onerror(new Error("Request timeout")); } }); }, getFileExtension: function(url) { try { const videoSource = document.querySelector("video source"); const imageSource = document.querySelector("img"); let type = ""; if (typeof videoSource === "string" && videoSource) { url = videoSource; } else if (videoSource instanceof HTMLSourceElement && (videoSource.src || videoSource.type)) { url = videoSource.src || ""; type = videoSource.type || ""; } if (!(videoSource instanceof HTMLSourceElement) && !videoSource && imageSource) { url = String(url || ""); } const typeMap = { "audio/mpeg": "mp3", "audio/mp4": "mp4", "audio/ogg": "ogg", "audio/wav": "wav", "audio/webm": "webm", "audio/aac": "aac", "audio/flac": "flac", "audio/x-wav": "wav", "audio/x-ms-wma": "wma", "video/mp4": "mp4", "video/webm": "webm", "video/ogg": "ogv", "video/quicktime": "mov", "video/x-msvideo": "avi", "video/x-ms-wmv": "wmv", "video/mpeg": "mpeg", "video/3gpp": "3gp", "image/jpeg": "jpg", "image/jpg": "jpg", "image/png": "png", "image/gif": "gif", "image/webp": "webp", "image/svg+xml": "svg", "image/bmp": "bmp", "image/tiff": "tiff", "image/x-icon": "ico" }; const normalizedType = String(type).split(";")[0].trim().toLowerCase(); if (normalizedType && typeMap[normalizedType]) { return typeMap[normalizedType]; } if (!url) { return null; } const cleanUrl = String(url).split("?")[0].split("#")[0]; const fileName = cleanUrl.split("/").pop(); if (!fileName) { return null; } const match = fileName.match(/\.([a-zA-Z0-9]+)$/); return match ? match[1].toLowerCase() : null; } catch (error) { } return null; }, generateFilename: function(url) { let ext = ""; try { const parsedUrl = new URL(String(url)); ext = parsedUrl.searchParams.get("____ext_") || ""; } catch (err) { const match = String(url).match(/[?&]____ext_=([^&#]*)/); ext = match ? decodeURIComponent(match[1]) : ""; } ext = String(ext).trim().toLowerCase().replace(/^\./, ""); if (!ext || ext === "unknown") { ext = this.getFileExtension(url); } ext = String(ext || "").trim().toLowerCase().replace(/^\./, "").replace(/[^a-z0-9]/g, ""); if (!ext || ext === "unknown") { ext = "mp4"; } return `${Date.now()}.${ext}`; }, start: function() { if (!this.isRun()) { return; } const url = window.location.href; const downloadData = { filename: this.generateFilename(url), url }; const language = this.getLanguage(); const doms = this.generateGui(language); if (!doms) { return; } this.addEvent(downloadData, doms, language); } }; const TedDownloader = { createDownloadButtonClass: function() { return "sc-download-" + Math.floor(1e5 + Math.random() * 9e5); }, injectStyle: function(buttonClass) { GM_addStyle(` .${buttonClass}{ cursor:pointer; justify-content:center; align-items:center; width:40px; height:40px; z-index:999999; position:relative; border-radius:50%; display:flex; } .${buttonClass}::before { content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 2px; background: linear-gradient(90deg,red, orange, yellow, green, cyan, blue, violet, red); background-size: 200% 200%; opacity: 0; transition: opacity .25s; -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor; mask-composite: exclude; } .${buttonClass}:hover::before { opacity: 1; animation: rainbowMove 3s linear infinite; } @keyframes rainbowMove { 0% { background-position: 0% 50%; } 100% { background-position: 200% 50%; } } `); }, createDownloadButton: function(buttonClass) { const downloadElement = document.createElement("div"); downloadElement.setAttribute("x-add", "true"); downloadElement.classList.add(buttonClass); downloadElement.style.cursor = "pointer"; downloadElement.appendChild(CommonUtils.genrateDownloadSvg("#000")); downloadElement.addEventListener("click", () => { const url = "https://www.tool77.com/" + CommonUtils.getSupportedLang() + "/v/downloader?url=" + encodeURIComponent(window.location.href); CommonUtils.openInTab(url); }); return downloadElement; }, isTalkPage: function() { return /www\.ted\.com\/talks\//.test(window.location.href); }, injectDownloaderIfNeeded: function(buttonClass) { if (!this.isTalkPage()) { return; } if (document.querySelector("." + buttonClass)) { return; } const transcript = document.querySelector("#talk-title"); const actionContainer = transcript?.parentNode?.querySelector(".items-center"); if (!actionContainer) { return; } actionContainer.appendChild(this.createDownloadButton(buttonClass)); }, setupPageObserver: function(buttonClass) { let rafId = null; const scheduleInject = () => { if (rafId !== null) { return; } rafId = requestAnimationFrame(() => { rafId = null; this.injectDownloaderIfNeeded(buttonClass); }); }; const observer = new MutationObserver(() => { scheduleInject(); }); observer.observe(document.documentElement, { childList: true, subtree: true }); CommonUtils.onLocationChange(scheduleInject); scheduleInject(); }, generateHtml: function() { const buttonClass = this.createDownloadButtonClass(); this.injectStyle(buttonClass); this.setupPageObserver(buttonClass); }, start: function() { const isRun = /ted\.com/.test(window.location.host); if (isRun) { this.generateHtml(); } } }; const FacebookDownloader = { getPlayUrl: function(target) { const clean = (url) => String(url).split("&")[0]; const extractId = (url) => String(url).match(/(?:v=|\/videos\/|\/reel\/|\/reels\/)(\d+)/)?.[1]; const toReelUrl = (id2) => "https://www.facebook.com/reel/" + id2; const href = window.location.href; const id = extractId(href); if (id) { return toReelUrl(id); } return clean(href); }, run: function() { const downloadHud = DownloadHud.mountVideoHoverDownload({ buttonEdgeOffset: 10, getDownloadUrl: (target) => this.getPlayUrl(target) }); setInterval(() => { if (/\.facebook\.com\/(reel|reels|videos)\/.*?/.test(window.location.href)) { downloadHud.enable(); } else { downloadHud.disable(); } }, 700); }, start: function() { const isRun = /facebook\.com/.test(window.location.host); if (isRun) { this.run(); } } }; const PinterestDownloader = { getPlayUrl: function(target) { const aElement = target.closest('a[href*="/pin/"]'); if (aElement) { return aElement.href; } return window.location.href; }, run: function() { DownloadHud.mountVideoHoverDownload({ getDownloadUrl: (target) => this.getPlayUrl(target) }); }, start: function() { const isRun = /pinterest\.com/.test(window.location.host); if (isRun) { this.run(); } } }; const SnapchatDownloader = { getPlayUrl: async function(target) { return window.location.href; }, run: function() { const downloadHud = DownloadHud.mountVideoHoverDownload({ getDownloadUrl: (target) => this.getPlayUrl(target) }); const patterns = [ /^https:\/\/www\.snapchat\.com\/@[^/]+\/[^/?]+(?:\?.*)?$/i, /^https:\/\/www\.snapchat\.com\/(?:@[^/]+\/)?spotlight\/[^/?]+(?:\?.*)?$/i, /^https:\/\/www\.snapchat\.com\/spotlight\/[^/]+(?:\?.*)?$/i, /^https:\/\/www\.snapchat\.com\/p\/[^/]+\/[^/?]+(?:\?.*)?$/i ]; const isSnapchatUrl = (url) => patterns.some((re) => re.test(url)); const updateHud = () => { if (isSnapchatUrl(window.location.href)) { downloadHud.enable(); } else { downloadHud.disable(); } }; CommonUtils.onLocationChange(updateHud); updateHud(); }, start: function() { const isRun = /snapchat\.com/.test(window.location.host); if (isRun) { this.run(); } } }; const SpotifyDownloader = { _debounceTimer: null, _playerAnchorPollTimer: null, _pendingPlayerAnchor: null, extractTrackIds: function() { const ids = /* @__PURE__ */ new Set(); document.querySelectorAll("aside a[href]").forEach((link) => { const href = link.getAttribute("href"); if (!href) { return; } const uriMatch = href.match(/spotify:track:([a-zA-Z0-9]+)/); const webMatch = href.match(/track\/([a-zA-Z0-9]+)/); if (uriMatch) { ids.add(uriMatch[1]); } if (webMatch) { ids.add(webMatch[1]); } }); return [...ids]; }, buildDownloadUrl: function(trackId) { return `https://www.spotpluck.com/en?url=https://open.spotify.com/track/${trackId}`; }, createDownloadSvg: function(color = "#1ed760", size = 20) { const svgNS = "http://www.w3.org/2000/svg"; const svg = document.createElementNS(svgNS, "svg"); svg.setAttribute("class", "icon"); svg.setAttribute("viewBox", "0 0 1045 1024"); svg.setAttribute("xmlns", svgNS); svg.setAttribute("width", String(size)); svg.setAttribute("height", String(size)); [ "M503.806 583.233c2.47 3.347 5.795 5.422 9.469 5.422s6.998-2.075 9.469-5.422l198.018-268.933c1.328-1.81 1.849-4.715 1.142-7.332-0.731-2.595-2.454-4.194-4.279-4.194h-128.113v-273.353c0-5.11-1.351-10.221-4.047-14.059-2.702-3.922-6.252-5.881-9.787-5.881h-124.78c-3.534 0-7.084 1.949-9.787 5.881-2.704 3.844-4.047 8.956-4.047 14.059v273.353h-128.121c-1.825 0-3.55 1.6-4.279 4.194-0.706 2.618-0.208 5.508 1.142 7.332l198.002 268.933z", "M881.309 622.062l-119.507-82.941h-74.672l125.271 101.382-137.726-1.804c-3.992 0-9.729 3.849-11.538 7.051l-38.258 92.528h-224.016l-38.258-92.528c-1.793-3.201-7.531-7.051-11.539-7.051h-136.904l124.469-99.578h-74.705l-119.49 82.941c-18.657 11.196-29.868 36.961-24.864 57.311l22.155 121.494c5.036 20.335 27.247 36.962 49.375 36.962h643.554c22.112 0 44.34-16.627 49.375-36.962l22.128-121.494c5.020-20.35-6.175-46.117-24.848-57.311v0 0z" ].forEach((d) => { const path = document.createElementNS(svgNS, "path"); path.setAttribute("d", d); path.setAttribute("fill", color); svg.appendChild(path); }); return svg; }, createDownloadButton: function(sourceButton, removeAttributes = false, svgSize = 20, onClick) { const button = sourceButton.cloneNode(false); if (removeAttributes) { const className = button.getAttribute("class"); [...button.attributes].forEach((attr) => { button.removeAttribute(attr.name); }); if (className) { button.setAttribute("class", className); } } button.style.cursor = "pointer"; button.setAttribute("userscript-v", "true"); button.appendChild(this.createDownloadSvg("#1ed760", svgSize)); button.addEventListener("click", onClick); return button; }, isVisible: function(el) { if (!el?.isConnected) { return false; } const { width, height } = el.getBoundingClientRect(); if (width === 0 || height === 0) { return false; } const style = getComputedStyle(el); return style.display !== "none" && style.visibility !== "hidden"; }, findPlayerAnchor: function() { const selectors = [ '[data-testid="fullscreen-mode-button"]', '[data-testid="pip-toggle-button"]' ]; for (const selector of selectors) { const anchor = document.querySelector(selector); if (anchor && this.isVisible(anchor)) { return anchor; } } return null; }, stopPlayerAnchorPoll: function() { if (!this._playerAnchorPollTimer) { return; } clearTimeout(this._playerAnchorPollTimer); this._playerAnchorPollTimer = null; }, getPlayerAnchor: function() { const pollMs = 500; const timeoutMs = 1e4; const anchor = this.findPlayerAnchor(); if (anchor) { return Promise.resolve(anchor); } if (this._pendingPlayerAnchor) { return this._pendingPlayerAnchor; } this._pendingPlayerAnchor = new Promise((resolve) => { let elapsed = 0; const poll = () => { this._playerAnchorPollTimer = null; const found = this.findPlayerAnchor(); if (found) { this._pendingPlayerAnchor = null; resolve(found); return; } elapsed += pollMs; if (elapsed >= timeoutMs) { this._pendingPlayerAnchor = null; resolve(null); return; } this._playerAnchorPollTimer = setTimeout(poll, pollMs); }; this.stopPlayerAnchorPoll(); this._playerAnchorPollTimer = setTimeout(poll, pollMs); }); return this._pendingPlayerAnchor; }, injectPlayer: async function() { const anchor = await this.getPlayerAnchor(); if (!anchor?.parentElement) { return; } if (anchor.parentElement.querySelector('button[userscript-v="true"]')) { return; } const button = this.createDownloadButton(anchor, true, 30, () => { const ids = this.extractTrackIds(); const trackId = ids.length ? ids[0] : null; if (!trackId) { return; } CommonUtils.openInTab(this.buildDownloadUrl(trackId)); }); anchor.parentElement.insertBefore(button, anchor.parentElement.firstChild); this.stopPlayerAnchorPoll(); }, extractTrackIdFromElement: function(el) { const scope = el.closest('[role="row"]') || el; for (const link of scope.querySelectorAll("a[href]")) { const href = link.getAttribute("href"); if (!href) { continue; } const uriMatch = href.match(/spotify:track:([a-zA-Z0-9]+)/); if (uriMatch) { return uriMatch[1]; } const webMatch = href.match(/track\/([a-zA-Z0-9]+)/); if (webMatch) { return webMatch[1]; } } return null; }, injectTracklist: function() { document.querySelectorAll('[data-testid="tracklist-row"]:not([track-userscript-v="true"])').forEach((tracklist) => { tracklist.setAttribute("track-userscript-v", "true"); const gridcells = tracklist.querySelectorAll('[role="gridcell"]'); const gridcell = gridcells[gridcells.length - 1]; if (!gridcell) { return; } const sourceButton = gridcell.querySelector("button"); if (!sourceButton) { return; } const button = this.createDownloadButton(sourceButton, false, 18, (e) => { e.stopPropagation(); e.preventDefault(); const trackId = this.extractTrackIdFromElement(gridcell); if (!trackId) { return; } CommonUtils.openInTab(this.buildDownloadUrl(trackId)); }); gridcell.insertBefore(button, gridcell.firstChild); }); }, scheduleInject: function() { clearTimeout(this._debounceTimer); this._debounceTimer = setTimeout(() => { this.injectPlayer(); this.injectTracklist(); }, 300); }, start: function() { const isRun = /spotify\.com/.test(window.location.host); if (isRun) { new MutationObserver(() => { this.scheduleInject(); }).observe(document.body, { childList: true, subtree: true }); this.scheduleInject(); } } }; // ===== 中国视频平台下载器(爱奇艺/优酷/腾讯视频/芒果TV/央视频/PPTV)===== // 这些平台是强 DRM 加密,纯前端(userscript)无法解密,必须借助「服务器侧解析」的第三方 jx 解析站。 // 做法:在播放页挂「⬇ 解析下载」悬浮按钮,点击弹出后端选择面板,每个后端把当前页 URL 拼成 // <后端>?url=<编码后的视频页地址> 在新标签页打开(jx 站标准用法,由站点服务端做 DRM 绕过)。 // 实测结论(务必看):免费 jx 站极不稳定、随时失效,下列 BACKENDS 为「多源兜底」——某几个挂了就换下一个。 // 已用真实浏览器(2026-08-23)实测可解析爱奇艺的 6 个:ckplayer.vip、jx.playerjy.com、jx.2s0.cn、bd.jx.cn、pouyun.com、pangujiexi.com(均成功注入播放器 iframe)。 // 你(用户)指定追加:jx.hls.one、jiexi.789jiexi.com。jx.xmflv.com 为最初需求站,部分平台可能可用,置于末位兜底。 // 8090g.cn/jiexi 是【私有】解析器(页面明示“仅供内部学习使用,禁止公开使用”),已移除,不建议公开使用。在你自己的浏览器里可能成功也可能失败,以本地实测为准。 // 哔哩哔哩由下方 BilibiliDownloader 专属模块【真解析】处理——浏览器内直接取直链,无需后端、无需 MD5。 const CnVideoDownloader = { BACKENDS: [ // —— 实测可解析爱奇艺(2026-08-23 真实浏览器验证,均成功注入播放器 iframe)—— "https://www.ckplayer.vip/jiexi/", // ckplayer:注入 jx.php 播放器 ✓ "https://jx.playerjy.com/", // PlayerJY:注入播放器 ✓ "https://jx.2s0.cn/player/", // 极速解析 ✓ "https://bd.jx.cn/", // 冰豆解析 ✓ "https://www.pouyun.com/", // 剖云解析 ✓ "https://www.pangujiexi.com/jiexi/", // 盘古解析 ✓ // —— 用户指定追加 —— "https://jx.hls.one/", // HLS解析(虾米系) "https://jiexi.789jiexi.com/", // 789解析 // —— 最初需求站,部分平台可能可用,末位兜底 —— "https://jx.xmflv.com/" ], PLATFORMS: [ { name: "爱奇艺", host: /iqiyi\.com/ }, { name: "优酷", host: /youku\.com/ }, { name: "腾讯视频", host: /v\.qq\.com/ }, { name: "芒果TV", host: /mgtv\.com/ }, { name: "央视频", host: /yangshipin\.cn/ }, { name: "PPTV", host: /pptv\.com/ } ], getDownloadUrl: function(target) { return window.location.href; }, openBackend: function(backend, url) { var target = backend + "?url=" + encodeURIComponent(url); window.open(target, "_blank"); }, invokeDownload: function(url) { if (!url || String(url).includes("undefined")) return; var self = this; var old = document.getElementById("aiod-cn-panel"); if (old) old.remove(); var box = document.createElement("div"); box.id = "aiod-cn-panel"; box.style.cssText = "position:fixed;top:16px;right:16px;width:330px;z-index:2147483647;background:#fff;color:#222;border:1px solid #ff7a00;border-radius:10px;box-shadow:0 8px 30px rgba(0,0,0,.25);font:13px/1.5 system-ui,'Microsoft YaHei',sans-serif;"; box.innerHTML = '
' + '解析下载(选后端)×
' + '
点下方后端 → 新标签页打开解析页。
免费 jx 站可能不稳定/限流,多试几个或换你信任的站。
' + '
'; document.body.appendChild(box); box.querySelector("#aiod-cn-close").addEventListener("click", function(){ box.remove(); }); var list = box.querySelector("#aiod-cn-list"); self.BACKENDS.forEach(function(b){ var name = b.replace(/^https?:\/\//, "").replace(/\/$/, ""); var btn = document.createElement("button"); btn.textContent = "▶ " + name; btn.style.cssText = "display:block;width:100%;margin:6px 0;padding:9px 10px;text-align:left;background:#fff;border:1px solid #ff7a00;color:#ff7a00;border-radius:8px;cursor:pointer;font:13px system-ui;"; btn.addEventListener("click", function(){ self.openBackend(b, url); box.remove(); }); list.appendChild(btn); }); }, run: function() { DownloadHud.mountVideoHoverDownload({ getDownloadUrl: this.getDownloadUrl, invokeDownload: (u) => this.invokeDownload(u) }); }, start: function() { const host = window.location.host; const matched = this.PLATFORMS.find((p) => p.host.test(host)); if (matched) { console.log("[AIO-DL] 已为中国平台挂载下载按钮:" + matched.name); this.run(); } } }; // ===== 哔哩哔哩 真解析下载器(浏览器内借用户登录态直接取直链,无需后端、无需 MD5)===== // 原理:非 wbi 的 x/player/playurl 在登录态下可直接返回直链(fnval=0 给单文件 MP4,fnval=16 给 DASH 音视频流)。 // 实测 tool77/allinonedownloader 均不支持 B 站,故这里完全自研解析。其余 6 个 DRM 平台仍需后端(见 CnVideoDownloader)。 const BilibiliDownloader = { HOSTS: ["www.bilibili.com", "bilibili.com", "m.bilibili.com", "player.bilibili.com", "b23.tv"], QN: { 127: "8K", 126: "杜比视界", 125: "HDR", 120: "4K", 116: "1080P+", 112: "1080P+", 80: "1080P", 74: "720P60", 64: "720P", 48: "720P", 32: "480P", 16: "360P" }, getBvid: function() { var m = window.location.href.match(/BV[0-9A-Za-z]+/); if (m) return m[0]; var q = new URLSearchParams(window.location.search).get("bvid"); return q || null; }, getPnum: function() { var m = window.location.href.match(/[?&]p=(\d+)/); return m ? parseInt(m[1], 10) : 1; }, api: function(url) { return fetch(url, { credentials: "include", headers: { "Referer": "https://www.bilibili.com" } }).then(function(r) { return r.json(); }); }, getView: function(bvid) { return this.api("https://api.bilibili.com/x/web-interface/view?bvid=" + encodeURIComponent(bvid)); }, getPlayurl: function(bvid, cid, fnval) { var url = "https://api.bilibili.com/x/player/playurl?bvid=" + encodeURIComponent(bvid) + "&cid=" + cid + "&qn=120&fnval=" + fnval + "&fourk=1"; return this.api(url); }, safeName: function(s) { return (s || "bilibili").replace(/[\/\\:*?"<>|\n\r\t]/g, "_").slice(0, 60); }, openUrl: function(url) { window.open(url, "_blank"); }, downloadStream: function(url, filename) { var self = this; return fetch(url, { credentials: "include", headers: { "Referer": "https://www.bilibili.com" } }).then(function(r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.blob(); }).then(function(blob) { var obj = URL.createObjectURL(blob); var a = document.createElement("a"); a.href = obj; a.download = filename || "stream"; document.body.appendChild(a); a.click(); a.remove(); setTimeout(function() { URL.revokeObjectURL(obj); }, 60000); }).catch(function() { self.openUrl(url); }); }, buildPanel: function() { var box = document.getElementById("aiod-bili-panel"); if (box) box.remove(); box = document.createElement("div"); box.id = "aiod-bili-panel"; box.style.cssText = "position:fixed;top:16px;right:16px;width:380px;max-height:82vh;overflow:auto;z-index:2147483647;background:#fff;color:#222;border:1px solid #00a1d6;border-radius:10px;box-shadow:0 8px 30px rgba(0,0,0,.25);font:13px/1.5 system-ui,'Microsoft YaHei',sans-serif;"; box.innerHTML = '
' + '哔哩哔哩 下载' + '×
' + '
' + '
'; document.body.appendChild(box); box.querySelector("#aiod-bili-close").addEventListener("click", function() { box.remove(); }); return { status: function(t) { var el = box.querySelector("#aiod-bili-status"); if (el) el.textContent = t; }, body: box.querySelector("#aiod-bili-body") }; }, render: function(panel, viewData, dash, prog) { var self = this; var title = viewData.title || self.getBvid(); var base = self.safeName(title); var body = panel.body; body.innerHTML = ""; if (prog && prog.durl && prog.durl.length) { var sec = document.createElement("div"); sec.style.marginBottom = "14px"; var h = document.createElement("div"); h.textContent = "① 直链 MP4(单文件,一键下载)"; h.style.cssText = "font-weight:700;margin-bottom:6px;color:#00a1d6;"; sec.appendChild(h); prog.durl.forEach(function(d, i) { var row = document.createElement("div"); row.style.cssText = "display:flex;gap:8px;align-items:center;margin:4px 0;"; var btn = document.createElement("button"); btn.textContent = "下载 " + (prog.durl.length > 1 ? ("片段" + (i + 1)) : "MP4"); btn.style.cssText = "background:#00a1d6;color:#fff;border:0;border-radius:6px;padding:5px 10px;cursor:pointer;"; var fn = base + (prog.durl.length > 1 ? ("_" + (i + 1)) : "") + ".mp4"; btn.addEventListener("click", function() { self.downloadStream(d.url, fn); }); var link = document.createElement("a"); link.href = d.url; link.target = "_blank"; link.textContent = "打开链接"; link.style.cssText = "color:#888;font-size:12px;"; row.appendChild(btn); row.appendChild(link); sec.appendChild(row); }); body.appendChild(sec); } if (dash && dash.video && dash.video.length) { var bestAudio = null, bestBw = -1; (dash.audio || []).forEach(function(a) { if (a.bandwidth > bestBw) { bestBw = a.bandwidth; bestAudio = a; } }); var seen = {}; var vlist = dash.video.filter(function(v) { if (seen[v.id]) return false; seen[v.id] = 1; return true; }); var sec2 = document.createElement("div"); var h2 = document.createElement("div"); h2.textContent = "② 高清 DASH(视频流 + 音频流,需 ffmpeg 合并)"; h2.style.cssText = "font-weight:700;margin-bottom:6px;color:#00a1d6;"; sec2.appendChild(h2); vlist.forEach(function(v) { var qlabel = self.QN[v.id] || (v.height ? v.height + "P" : ("qn" + v.id)); var vurl = v.baseUrl || v.base_url || (v.backupUrl && v.backupUrl[0]) || (v.backup_url && v.backup_url[0]); var aurl = bestAudio ? (bestAudio.baseUrl || bestAudio.base_url) : null; var row = document.createElement("div"); row.style.cssText = "border:1px solid #eee;border-radius:6px;padding:6px 8px;margin:6px 0;"; var t = document.createElement("div"); t.textContent = qlabel + " (" + (v.codecs || "") + ")"; t.style.fontWeight = "600"; row.appendChild(t); var btns = document.createElement("div"); btns.style.cssText = "display:flex;gap:6px;flex-wrap:wrap;margin-top:4px;"; var bv = document.createElement("button"); bv.textContent = "下载视频流"; bv.style.cssText = "background:#fb7299;color:#fff;border:0;border-radius:5px;padding:4px 8px;cursor:pointer;"; bv.addEventListener("click", function() { self.downloadStream(vurl, base + "_" + qlabel + ".m4s"); }); btns.appendChild(bv); if (aurl) { var ba = document.createElement("button"); ba.textContent = "下载音频流"; ba.style.cssText = "background:#23ade5;color:#fff;border:0;border-radius:5px;padding:4px 8px;cursor:pointer;"; ba.addEventListener("click", function() { self.downloadStream(aurl, base + "_audio.m4s"); }); btns.appendChild(ba); } row.appendChild(btns); if (aurl) { var cmd = document.createElement("div"); cmd.style.cssText = "margin-top:4px;color:#666;font-size:11px;word-break:break-all;"; cmd.textContent = "合并:ffmpeg -i " + base + "_" + qlabel + ".m4s -i " + base + "_audio.m4s -c copy " + base + "_" + qlabel + ".mp4"; row.appendChild(cmd); } sec2.appendChild(row); }); body.appendChild(sec2); } if (!body.children.length) { panel.status("未能获取到可下载直链(可能版权限制或需登录后刷新本页)。"); } else { panel.status("解析完成:" + title); } }, parse: function() { var self = this; var bvid = this.getBvid(); if (!bvid) { window.alert("未在当前页面检测到 B 站视频(BV 号)。\n请在 bilibili.com 的视频播放页使用本按钮。"); return; } var panel = this.buildPanel(); panel.status("正在解析 " + bvid + " …"); this.getView(bvid).then(function(view) { if (!view || view.code !== 0 || !view.data) throw new Error((view && view.message) || "view 接口返回异常"); var data = view.data; var p = self.getPnum(); var cid = (data.pages && data.pages.length > 1 && data.pages[p - 1]) ? data.pages[p - 1].cid : data.cid; if (!cid) throw new Error("无法获取 cid"); panel.status("已获取视频信息,拉取播放地址…"); return Promise.all([ self.getPlayurl(bvid, cid, 16), self.getPlayurl(bvid, cid, 0) ]).then(function(r) { self.render(panel, data, r[0].data, r[1].data); }); }).catch(function(e) { panel.status("解析失败:" + (e && e.message ? e.message : e)); }); }, injectFixedButton: function() { var self = this; if (window.location.pathname.indexOf("/video/") === -1 && !this.getBvid()) return; if (document.getElementById("aiod-bili-fab")) return; if (!document.body) { setTimeout(function() { self.injectFixedButton(); }, 500); return; } var btn = document.createElement("div"); btn.id = "aiod-bili-fab"; btn.textContent = "⬇ B站下载"; btn.style.cssText = "position:fixed;right:16px;bottom:84px;z-index:2147483647;background:#00a1d6;color:#fff;padding:8px 14px;border-radius:20px;cursor:pointer;box-shadow:0 4px 14px rgba(0,0,0,.3);font:14px system-ui,'Microsoft YaHei',sans-serif;font-weight:600;"; btn.addEventListener("click", function() { self.parse(); }); document.body.appendChild(btn); }, mountButton: function() { DownloadHud.mountVideoHoverDownload({ getDownloadUrl: function() { return "bili-local"; }, invokeDownload: function() { BilibiliDownloader.parse(); } }); }, start: function() { var host = window.location.host; var hit = this.HOSTS.some(function(h) { return host === h || host.endsWith("." + h); }); if (hit) { console.log("[AIO-DL] 哔哩哔哩真解析下载器已挂载"); this.mountButton(); this.injectFixedButton(); } } }; [ CnVideoDownloader, BilibiliDownloader, YouTubeDownloader, TiktokDownloader, ThreadsDownloader, TwitterDownloader, InstagramDownloader, DouyinDownloader, XiaoHongShuDownloader, Downloader, TedDownloader, FacebookDownloader, PinterestDownloader, SnapchatDownloader, SpotifyDownloader ].forEach((m) => m.start()); }());