I wrote a userscript that adds a download button directly onto video pages. It is not a website you paste a link into — it is a small file of JavaScript that runs inside your own browser and puts a download icon on the page you are already looking at. This Tampermonkey video downloader works on YouTube, YouTube Shorts, TikTok, Instagram Reels, X (formerly Twitter), Threads, Douyin and Xiaohongshu.
The script itself is in the code block further down, ready to copy. Everything above it explains what you are installing and what to do when it misbehaves.

Instagram

What a userscript is, and why it needs a manager
A userscript is one file of JavaScript that runs on pages matching a rule you set. It cannot run on its own — it needs a browser extension called a userscript manager, which watches what page you open and injects the matching script into it.
Two managers are worth using, both free. Tampermonkey, the most widely installed userscript manager, works on Chrome, Edge, Firefox, Safari and Opera. Violentmonkey, the open-source alternative, does the same job with a smaller codebase. Pick one. Installing both causes problems I cover further down.
The distinction that matters: the manager is the engine, the userscript is the instruction sheet. You install the manager once and can then run any number of scripts through it.
Installing the userscript video downloader
Three steps, and the whole thing takes about thirty seconds once the extension is in place.
Step 1 — Install a userscript manager
Go to the Tampermonkey or Violentmonkey site linked above and install the extension for your browser. Chrome and Edge will ask you to confirm the permissions — a userscript manager legitimately needs read and write access to page content, because injecting scripts into pages is the entire job.
Step 2 — Add the script
Open the manager's dashboard from the toolbar icon, create a new script, delete the template it gives you, and paste in the code block below. Save with Ctrl+S or Cmd+S.
The block at the top of the script — the lines starting with // @match — controls which sites it runs on. If a platform you want is missing, that is the section to edit.
Step 3 — Open a video
Load any video on a supported site. A download icon appears on or near the player. Click it and the file saves to your normal downloads folder.
If nothing happens on the first page
Hard-refresh once with Ctrl+Shift+R. The manager injects on page load, and a tab that was already open before you saved the script will not have it.
Downloading a TikTok video without watermark
The script pulls the video file the platform serves to the player, which on TikTok is often the version before the watermark overlay is composited in. That is why a download from a TikTok video without watermark is possible at all — nothing is being stripped or edited, the file is simply requested at a different point.
This is a technical description, not a legal one. Every platform here has terms of service governing what you may do with content, and copyright sits with whoever made the video. Whether a given download is permitted is your call, not mine, and I would check the terms of the specific platform before saving anything you plan to reuse or republish.
When the download button does not appear
Most reports I get come down to one of four things.
The site changed its layout
Userscripts attach to specific elements in a page's structure. When a platform ships a redesign, the element the script was looking for moves or gets renamed, and the button silently stops appearing. This is normal and it happens to every script of this kind. The fix is updating the selector — the line describing where on the page to attach.
Two managers are installed at once
Running a Tampermonkey install and a Violentmonkey install side by side means both try to inject the same script. You get duplicate buttons, or none. Disable one.
The browser blocked the install
Chrome tightened extension permissions under Manifest V3, the current extension platform documented in the Chrome extensions migration guide. If your manager shows as installed but scripts never run, check that developer mode is enabled in chrome://extensions.
The video is protected or private
Some content will not download regardless of the script — see below.
What this script cannot do
Being straight about the ceiling saves everyone time.
It will not touch DRM-protected content. Netflix, Spotify and paid streaming use encrypted media, and the browser never holds a plain file to save. It will not reach posts on private accounts you are not logged into, or age-gated content behind a sign-in wall. It downloads one video at a time, by hand, because that is what a page-level button does.
It also captures no metadata. If you need the caption, view count, upload date, author handle or comment thread alongside the file, this is the wrong tool — that is a scraping job, not a download button.
When a button is not enough
Plenty of people arrive at this script wanting something it structurally cannot do: four hundred Reels from a competitor's account with captions and posting times in a spreadsheet, or a daily check on a hashtag that saves anything new.
That is the work I do. I build Python and Playwright bots for scheduled bulk collection, no-code browser macros with UI.Vision RPA when a client wants to run and edit it themselves, and pipelines that land the results in Google Sheets, Airtable, SQLite or PostgreSQL rather than a downloads folder. There is a fuller breakdown on my browser automation and scraping services page, and other build write-ups on the DatafetchPro blog archive.
If you already know what you need collected and where it should end up, describe it on my project intake and Fiverr ordering page and I will tell you whether it is a two-hour script or a real build before you pay anything. Some background on how I work is on the about page.
The script below stays free either way.
// ==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, X (formerly Twitter), and more. Enjoy seamless, high-quality downloads with no watermarks.
// @description:ar أداة شاملة وسريعة ومجانية لتحميل مقاطع الفيديو بدقة عالية. يدعم تحميل مقاطع الفيديو من يوتيوب، تيك توك، Douyin، إنستغرام، ثريدز، XiaoHongShu، إكس (تويتر سابقًا)، والمزيد. استمتع بتحميلات عالية الجودة بدون علامات مائية.
// @description:bg Всичко в едно, бързо и безплатно изтегляне на видео с висока резолюция. Поддържа изтегляне на видеа от YouTube, TikTok, Douyin, Instagram, Threads, XiaoHongShu, 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, Threads, XiaoHongShu, 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, Threads, XiaoHongShu, 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, Threads, XiaoHongShu, X (ehemals Twitter) und mehr. Genießen Sie nahtlose, hochqualitative Downloads ohne Wasserzeichen.
// @description:el Ένας ολοκληρωμένος, γρήγορος και δωρεάν downloader HD βίντεο. Υποστηρίζει λήψη βίντεο από YouTube, TikTok, Douyin, Instagram, Threads, XiaoHongShu, X (πρώην Twitter) και άλλα. Απολαύστε απρόσκοπτες, υψηλής ποιότητας λήψεις χωρίς υδατογραφήματα.
// @description:en An all-in-one, fast, and free HD video downloader. Supports video downloads from YouTube, TikTok, Douyin, Instagram, Threads, XiaoHongShu, 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, Threads, XiaoHongShu, 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, Threads, XiaoHongShu, 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, 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, Threads, XiaoHongShu, 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, Threads, XiaoHongShu, X (anciennement Twitter) et plus encore. Profitez de téléchargements fluides et de haute qualité sans filigranes.
// @description:he מנוע הורדות וידאו HD מהיר, חינמי ומקיף. תומך בהורדת וידאו מיוטיוב, טיקטוק, אינסטגרם, Threads, XiaoHongShu, X (בעבר טוויטר) ועוד. תהנה מהורדות באיכות גבוהה ללא סימני מים.
// @description:hr Svi-u-jednom, brzi i besplatni HD downloader za video. Podržava preuzimanje videa s YouTubea, TikToka, Instagrama, Threads, XiaoHongShu, 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, Threads, XiaoHongShu, 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, Threads, XiaoHongShu, 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, Threads, XiaoHongShu, X (precedentemente Twitter) e altri. Goditi download fluidi e di alta qualità senza filigrane.
// @description:ja YouTube の視聴体験を向上させるための高速で無料な HD 動画ダウンローダー。YouTube、TikTok、Douyin、Instagram、Threads、XiaoHongShu、X(旧 Twitter)などからの動画ダウンロードをサポート。シームレスで高品質なダウンロードをウォーターマークなしでお楽しみください。
// @description:ka ყველაფერი ერთში, სწრაფი და უფასო HD ვიდეო ჩამოტვირთვა. მხარს უჭერს ვიდეოს ჩამოტვირთვას YouTube, TikTok, Douyin, Instagram, Threads, XiaoHongShu, X (ყოფილი Twitter) და სხვა პლატფორმებიდან. დატკბით უარყოფითი ნიშნების გარეშე, უმაღლესი ხარისხის ჩამოტვირთვებით.
// @description:ko YouTube 시청 경험을 향상시키는 모든 기능을 갖춘, 빠르고 무료인 HD 비디오 다운로드기. YouTube, TikTok, Douyin, Instagram, Threads, XiaoHongShu, X(이전 Twitter) 등에서 비디오 다운로드를 지원합니다. 워터마크 없이 원활한 고품질 다운로드를 즐기세요.
// @description:nb En alt-i-en, rask og gratis HD video nedlaster. Støtter video nedlastinger fra YouTube, TikTok, Douyin, Instagram, Threads, XiaoHongShu, 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, Threads, XiaoHongShu, 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, Threads, XiaoHongShu, 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, Threads, XiaoHongShu, 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, Threads, XiaoHongShu, 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, Threads, XiaoHongShu, X (бывший Twitter) и других. Наслаждайтесь безупречными загрузками высокого качества без водяных знаков.
// @description:sv En allt-i-ett, snabb och gratis HD-video nedladdare. Stöder video nedladdningar från YouTube, TikTok, Douyin, Instagram, Threads, XiaoHongShu, X (tidigare Twitter) och mer. Njut av sömlösa, högkvalitativa nedladdningar utan vattenmärken.
// @description:th เครื่องมือดาวน์โหลดวิดีโอ HD ที่รวดเร็วและฟรีแบบครบวงจร รองรับการดาวน์โหลดวิดีโอจาก YouTube, TikTok, Douyin, Instagram, Threads, XiaoHongShu, X (เดิม Twitter) และอื่นๆ เพลิดเพลินกับการดาวน์โหลดที่มีคุณภาพสูงโดยไม่มีลายน้ำ
// @description:tr Birçok platformdan video indirmenizi destekleyen, hızlı ve ücretsiz HD video indirme aracıdır. YouTube, TikTok, Douyin, Instagram, Threads, XiaoHongShu, 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, Threads, XiaoHongShu, X (колишній Twitter) та інших. Насолоджуйтесь безперешкодними високоякісними завантаженнями без водяних знаків.
// @description:ug YouTube كۆرۈش تەجرىبەڭىزنى ياخشىلايدىغان، تىز ۋە ھەقسىز HD ۋىدىئو چۈشۈرگۈچى. يۇتۇب، تيك توك، دۇيىن، ئىنستاگرام، Threads، XiaoHongShu، 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, Threads, XiaoHongShu, 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、Threads、小红书、X(Twitter)等平台下载视频。享受无水印、高质量、流畅便捷的下载体验
// @description:zh-TW 一款多合一、快速且免費的線上視頻下載器。支援從 YouTube、TikTok、抖音、Instagram、Threads、小紅書、X(Twitter)等平台下載視頻。享受無水印、高質量、流暢便捷的下載體驗
// @namespace AllInOneDownloader_Daniel
// @version 1.0.8
// @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://*.youtube.com/*
// @include https://x.com/*
// @include https://twitter.com/*
// @include https://www.threads.com/*
// @include https://www.instagram.com/reels/*
// @include https://www.tikTok.com/*
// @include https://www.douyin.com/*
// @include https://www.xiaohongshu.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/*
// @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
// @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
// @downloadURL https://update.greasyfork.org/scripts/563321/All-in-One%20Video%20Downloader%20%E2%80%93%20HD%20%28YouTube%2C%20TikTok%2C%20Instagram%2C%20X%20%20More%29.user.js
// @updateURL https://update.greasyfork.org/scripts/563321/All-in-One%20Video%20Downloader%20%E2%80%93%20HD%20%28YouTube%2C%20TikTok%2C%20Instagram%2C%20X%20%20More%29.meta.js
// ==/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) {
let 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);
let 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`;
}
};
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);
}
}, 500);
});
},
genrateShorts: function() {
const genrateHtml = () => {
if (window.location.href.indexOf("/shorts/") != -1) {
const navigationButtonDown = document.querySelector("#navigation-button-down");
if (navigationButtonDown) {
const download = document.createElement("div");
download.style.cursor = "pointer";
download.setAttribute("style", "cursor:pointer;display: flex;justify-content: center;align-items: center;");
download.id = "script_download_shorts";
download.className = "navigation-button style-scope ytd-shorts";
navigationButtonDown.after(download);
download.appendChild(CommonUtils.genrateDownloadSvg("#3e3e3e", 45, 45));
download.addEventListener("click", () => {
this.downloadVideo();
});
}
}
};
setInterval(() => {
if (!document.querySelector("#script_download_shorts")) {
genrateHtml();
}
}, 800);
},
run: function() {
this.genrate();
this.genrateShorts();
},
start: function() {
const isRun = /youtube\.com/.test(window.location.host);
if (isRun) {
this.run();
}
}
};
const TiktokDownloader = {
downloadVideo: function(playId) {
if (playId) {
const url = "https://www.tool77.com/" + CommonUtils.getSupportedLang() + "/v/downloader?url=https%3A%2F%2Fwww.tiktok.com%2F%40_%2Fvideo/" + playId;
CommonUtils.openInTab(url);
}
},
findAncestorByClass: function(element, className, maxDepth = Infinity) {
let current = element;
let depth = 0;
while (current && depth < maxDepth) {
current = current.parentElement;
depth++;
if (current && current.classList.contains(className)) {
return current;
}
}
return null;
},
extractLastDashNumber: function(str) {
if (!str)
return null;
const match = str.match(/-(\d+)$/);
return match ? match[1] : null;
},
genrate: function() {
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%; }
}
`);
setInterval(() => {
document.querySelectorAll("video").forEach((element, index) => {
if (element.getAttribute("src")) {
const parentNode = element.parentNode;
if (!parentNode.querySelector("*[x-add='true']")) {
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";
parentNode.appendChild(downloadElement);
downloadElement.addEventListener("click", () => {
const container = this.findAncestorByClass(parentNode, "xgplayer-container", 3);
if (container && container.getAttribute("id")) {
const playId = this.extractLastDashNumber(container.getAttribute("id"));
if (playId)
this.downloadVideo(playId);
}
});
}
}
});
}, 1e3);
},
start: function() {
const isRun = /tiktok\.com/.test(window.location.host);
if (isRun) {
this.genrate();
}
}
};
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) => {
element.setAttribute("detectvs", "true");
if (element.querySelector("video") === null) {
return;
}
const buttons = element.querySelectorAll("*[role='button']");
const lastButton = buttons[buttons.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);
}
});
});
}, 500);
},
start: function() {
const isYoutube = /threads\.com/.test(window.location.host);
if (isYoutube) {
this.run();
}
}
};
const TwitterDownloader = {
svg: ` <g class="download"><path d="M11.99 16l-5.7-5.7L7.7 8.88l3.29 3.3V2.59h2v9.59l3.3-3.3 1.41 1.42-5.71 5.7zM21 15l-.02 3.51c0 1.38-1.12 2.49-2.5 2.49H5.5C4.11 21 3 19.88 3 18.5V15h2v3.5c0 .28.22.5.5.5h12.98c.28 0 .5-.22.5-.5L19 15h2z" /></g>`,
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.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 = `<svg viewBox="0 0 20 20" width="15" height="15">${this.svg}</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.style.cursor = "pointer";
btnDownload.innerHTML = `<div><div><svg viewBox="0 0 20 20" width="15" height="15">${this.svg}</svg></div></div>`;
li.appendChild(btnDownload);
btnDownload.onclick = () => {
this.downloadVideo(statusId);
};
});
},
detect: function(node) {
const article = node.tagName === "ARTICLE" && node || node.tagName === "DIV" && (node.querySelector("article") || node.closest("article"));
if (article) {
this.addButtonTo(article);
}
const listitems = node.tagName === "LI" && node.getAttribute("role") === "listitem" ? [node] : node.tagName === "DIV" && node.querySelectorAll('li[role="listitem"]');
if (listitems) {
this.addButtonToMedia(listitems);
}
},
run: function() {
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 = {
downloadVideo: function() {
const url = "https://www.tool77.com/" + CommonUtils.getSupportedLang() + "/v/downloader?url=" + encodeURIComponent(window.location.href);
CommonUtils.openInTab(url);
},
run: function() {
const interval = setInterval(() => {
const toolbar = document.querySelector("div[role='toolbar']");
if (toolbar) {
clearInterval(interval);
const downloadDiv = toolbar.firstElementChild.cloneNode();
downloadDiv.style.cursor = "pointer";
downloadDiv.appendChild(CommonUtils.genrateDownloadSvg("#000", 30, 30));
downloadDiv.addEventListener("click", () => {
this.downloadVideo();
});
toolbar.appendChild(downloadDiv);
}
}, 500);
},
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();
}, 500);
}
}
};
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 = /www\.xiaohongshu\.com/.test(window.location.host);
if (isRun) {
setInterval(() => {
this.genrate();
}, 500);
}
}
};
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/
];
const currentHost = window.location.host;
return hosts.some((rule) => rule.test(currentHost));
},
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.<br>* 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.<br>* 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.<br>* 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.<br>* Se o arquivo for muito grande (acima de 200MB), recomenda-se salvar manualmente."
},
"ru": {
startDownload: "Начать загрузку",
cancelDownload: "Отменить загрузку",
downloadProgress: "Прогресс загрузки:",
downloadCompleted: "Загрузка завершена",
downloadCompletedToast: "Загрузка завершена!",
reDownload: "Скачать снова",
downloadFailed: "Ошибка загрузки",
downloadFailedToast: "Сбой загрузки, проверьте консоль",
downloadCanceledToast: "Загрузка отменена",
downloaderTip: "* Если загрузка не удалась, сохраните вручную: правый клик -> Сохранить видео как.<br>* Если файл слишком большой (более 200MB), рекомендуется сохранять вручную."
},
"ja": {
startDownload: "ダウンロード開始",
cancelDownload: "ダウンロードをキャンセル",
downloadProgress: "ダウンロード進行状況:",
downloadCompleted: "ダウンロード完了",
downloadCompletedToast: "ダウンロードが完了しました!",
reDownload: "再ダウンロード",
downloadFailed: "ダウンロード失敗",
downloadFailedToast: "ダウンロードに失敗しました。コンソールを確認してください",
downloadCanceledToast: "ダウンロードをキャンセルしました",
downloaderTip: "* ダウンロードに失敗した場合は手動保存: 右クリック -> 動画を名前を付けて保存。<br>* ファイルが大きすぎる場合(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.<br>* Wenn die Datei zu gross ist (uber 200MB), wird manuelles Speichern empfohlen."
},
"ko": {
startDownload: "다운로드 시작",
cancelDownload: "다운로드 취소",
downloadProgress: "다운로드 진행률:",
downloadCompleted: "다운로드 완료",
downloadCompletedToast: "다운로드가 완료되었습니다!",
reDownload: "다시 다운로드",
downloadFailed: "다운로드 실패",
downloadFailedToast: "다운로드에 실패했습니다. 콘솔을 확인하세요",
downloadCanceledToast: "다운로드가 취소되었습니다",
downloaderTip: "* 다운로드 실패 시 수동 저장: 마우스 오른쪽 클릭 -> 동영상을 다른 이름으로 저장.<br>* 파일이 너무 큰 경우(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.<br>* 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.<br>* 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.<br>* 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.<br>* Jesli plik jest zbyt duzy (powyej 200MB), zaleca sie zapis reczny."
},
"uk": {
startDownload: "Почати завантаження",
cancelDownload: "Скасувати завантаження",
downloadProgress: "Прогрес завантаження:",
downloadCompleted: "Завантаження завершено",
downloadCompletedToast: "Завантаження завершено!",
reDownload: "Завантажити знову",
downloadFailed: "Помилка завантаження",
downloadFailedToast: "Не вдалося завантажити, перевірте консоль",
downloadCanceledToast: "Завантаження скасовано",
downloaderTip: "* Якщо завантаження не вдалося, збережіть вручну: правий клік -> Зберегти відео як.<br>* Якщо файл занадто великий (понад 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.<br>* 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.<br>* Neu tep qua lon (tren 200MB), khuyen nghi luu thu cong."
},
"th": {
startDownload: "เริ่มดาวน์โหลด",
cancelDownload: "ยกเลิกการดาวน์โหลด",
downloadProgress: "ความคืบหน้าการดาวน์โหลด:",
downloadCompleted: "ดาวน์โหลดเสร็จสิ้น",
downloadCompletedToast: "ดาวน์โหลดเสร็จสิ้น!",
reDownload: "ดาวน์โหลดอีกครั้ง",
downloadFailed: "ดาวน์โหลดล้มเหลว",
downloadFailedToast: "ดาวน์โหลดล้มเหลว โปรดตรวจสอบคอนโซล",
downloadCanceledToast: "ยกเลิกการดาวน์โหลดแล้ว",
downloaderTip: "* หากดาวน์โหลดล้มเหลว ให้บันทึกด้วยตนเอง: คลิกขวา -> บันทึกวิดีโอเป็น.<br>* หากไฟล์มีขนาดใหญ่เกินไป (มากกว่า 200MB) แนะนำให้บันทึกด้วยตนเอง."
},
"ar": {
startDownload: "بدء التنزيل",
cancelDownload: "إلغاء التنزيل",
downloadProgress: "تقدم التنزيل:",
downloadCompleted: "اكتمل التنزيل",
downloadCompletedToast: "اكتمل التنزيل!",
reDownload: "تنزيل مرة اخرى",
downloadFailed: "فشل التنزيل",
downloadFailedToast: "فشل التنزيل، يرجى التحقق من وحدة التحكم",
downloadCanceledToast: "تم إلغاء التنزيل",
downloaderTip: "* إذا فشل التنزيل، احفظ يدويًا: انقر بزر الماوس الأيمن -> حفظ الفيديو باسم.<br>* إذا كان الملف كبيرًا جدًا (أكثر من 200MB)، يوصى بالحفظ اليدوي."
},
"fa": {
startDownload: "شروع دانلود",
cancelDownload: "لغو دانلود",
downloadProgress: "پیشرفت دانلود:",
downloadCompleted: "دانلود کامل شد",
downloadCompletedToast: "دانلود کامل شد!",
reDownload: "دانلود دوباره",
downloadFailed: "دانلود ناموفق بود",
downloadFailedToast: "دانلود ناموفق بود، لطفا کنسول را بررسی کنید",
downloadCanceledToast: "دانلود لغو شد",
downloaderTip: "* اگر دانلود ناموفق بود، به صورت دستی ذخیره کنید: راست کلیک -> ذخیره ویدیو با نام.<br>* اگر فایل خیلی بزرگ است (بیش از 200MB)، ذخیره دستی توصیه می شود."
},
"hi": {
startDownload: "डाउनलोड शुरू करें",
cancelDownload: "डाउनलोड रद्द करें",
downloadProgress: "डाउनलोड प्रगति:",
downloadCompleted: "डाउनलोड पूरा हुआ",
downloadCompletedToast: "डाउनलोड पूरा हुआ!",
reDownload: "फिर से डाउनलोड करें",
downloadFailed: "डाउनलोड असफल रहा",
downloadFailedToast: "डाउनलोड असफल रहा, कृपया कंसोल जांचें",
downloadCanceledToast: "डाउनलोड रद्द कर दिया गया",
downloaderTip: "* यदि डाउनलोड असफल हो, मैन्युअली सेव करें: राइट क्लिक -> वीडियो को इस नाम से सेव करें.<br>* यदि फाइल बहुत बड़ी है (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.<br>* Jika fail terlalu besar (lebih 200MB), simpan secara manual adalah disyorkan."
},
"zh-CN": {
startDownload: "开始下载",
cancelDownload: "取消下载",
downloadProgress: "下载进度:",
downloadCompleted: "下载完成",
downloadCompletedToast: "下载完成!",
reDownload: "重新下载",
downloadFailed: "下载失败",
downloadFailedToast: "下载失败,请查看控制台",
downloadCanceledToast: "下载已取消",
downloaderTip: "* 如果下载失败,请手动保存:鼠标右键 -> 视频另存为。<br>* 如果文件太大(200MB)以上,推荐使用手动保存。"
},
"zh-TW": {
startDownload: "開始下載",
cancelDownload: "取消下載",
downloadProgress: "下載進度:",
downloadCompleted: "下載完成",
downloadCompletedToast: "下載完成!",
reDownload: "重新下載",
downloadFailed: "下載失敗",
downloadFailedToast: "下載失敗,請查看控制台",
downloadCanceledToast: "下載已取消",
downloaderTip: "* 如果下載失敗,請手動儲存:滑鼠右鍵 -> 影片另存為。<br>* 如果檔案太大(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 = `
<button class="downloader-btn downloader-download-btn" data-role="download-btn">${language.startDownload}</button>
<button class="downloader-btn downloader-cancel-btn" data-role="cancel-btn">${language.cancelDownload}</button>
<div class="downloader-progress-container" data-role="progress-container">
<div class="downloader-progress-label">${language.downloadProgress}</div>
<div class="downloader-progress-wrap">
<div class="downloader-progress-bar" data-role="progress-bar"></div>
</div>
<div class="downloader-progress-text" data-role="progress-text">0%</div>
</div>
<div class="downloader-tip">${language.downloaderTip}</div>
`;
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;
console.error("download error", err);
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) {
console.error("getFileExtension error", 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);
}
};
YouTubeDownloader.start();
TiktokDownloader.start();
ThreadsDownloader.start();
TwitterDownloader.start();
InstagramDownloader.start();
DouyinDownloader.start();
XiaoHongShuDownloader.start();
Downloader.start();
}());