// ==UserScript==
// @name Twitter/X Media Downloader
// @name:af Twitter Media Download (2025.04.28 vas)
// @name:am ትዊተር ሚዲያ አውርድ (2025.04.28 ተጠግኗል)
// @name:ar تنزيل Media Twitter (2025.04.28 ثابت)
// @name:az Twitter Media Yükləmə (2025.04.28 Sabit)
// @name:be Загрузка Twitter Media (2025.04.28 Выпраўлена)
// @name:bem Ukukongama kwa pa muulu (2025.04.28 ukwashintililwapo)
// @name:bg Изтегляне на медии в Twitter (2025.04.28 Фиксиран)
// @name:bn টুইটার মিডিয়া ডাউনলোড (2025.04.28 স্থির)
// @name:bo Twitter Media Download (2025.04.28 གཏན་འཁེལ་བྱུང་བ།)
// @name:bs Preuzimanje Twitter Media (2025.04.28 fiksnim)
// @name:ca Descàrrega de Twitter Media (2025.04.28 S’ha corregit)
// @name:ceb Download sa Twitter Media (2025.04.28 naayos)
// @name:ckb داگرتنی میدیای تویتەر (2025.04.28 چاککراوە)
// @name:cs Stahování médií Twitter (2025.04.28 Opraveno)
// @name:cy Lawrlwytho Cyfryngau Twitter (2025.04.28 sefydlog)
// @name:da Twitter Media Download (2025.04.28 Fixed)
// @name:de Download von Twitter Media (2025.04.28 behoben)
// @name:dv ޓްވިޓަރ މީޑިއާ ޑައުންލޯޑް (2025.04.28 ފިކްސްޑް)
// @name:dz Twitter Medownload (2025.04.28 བདེ་སྒྲིག་)
// @name:el Download Media Twitter (2025.04.28 Διορθώθηκε)
// @name:en Twitter Media Downloader (2025.04.28 Fixed)
// @name:en-GB Twitter Media Downloader (2025.04.28 Fixed)
// @name:eo Elŝuti Twitter Media (2025.04.28 Fiksita)
// @name:es Descarga de medios de Twitter (2025.04.28 solucionado)
// @name:et Twitteri meedia allalaadimine (2025.04.28 fikseeritud)
// @name:eu Twitter Media download (2025.04.28 Konpondu)
// @name:fa دانلود رسانه توییتر (2025.04.28 ثابت)
// @name:fi Twitter Media Download (2025.04.28 Kiinteä)
// @name:fo Twitter Media Download (2025.04.28 Fast)
// @name:fr Twitter Media Download (2025.04.28 Correction)
// @name:fr-CA Twitter Media Download (2025.04.28 Correction)
// @name:gd Luchdaich sìos meadhanan Twitter (2025.04.28 stèidhichte)
// @name:gl Twitter Media Download (2025.04.28 Fixado)
// @name:gu ટ્વિટર મીડિયા ડાઉનલોડ (2025.04.28 સ્થિર)
// @name:haw Kāleʻa Twitter (2025.04.28 paʻa)
// @name:he הורדת מדיה בטוויטר (2025.04.28 קבועה)
// @name:hi ट्विटर मीडिया डाउनलोड (2025.04.28 फिक्स्ड)
// @name:hr Twitter Media preuzimanje (2025.04.28 fiksno)
// @name:ht Twitter Media Download (2025.04.28 fiks)
// @name:hu A Twitter Media letöltése (2025.04.28 rögzített)
// @name:hy Twitter Media Download (2025.04.28 ֆիքսված)
// @name:id Unduh Media Twitter (2025.04.28 diperbaiki)
// @name:is Twitter Media Download (2025.04.28 Fast)
// @name:it Download di Twitter Media (2025.04.28 FISSO)
// @name:ja Twitterメディアダウンロード(2025.04.28修正)
// @name:ka Twitter Media Download (2025.04.28 დაფიქსირდა)
// @name:kk Twitter Media Download (2025.04.28 тіркелген)
// @name:km ការទាញយកប្រព័ន្ធផ្សព្វផ្សាយ Twitter (2025.04.28 ថេរ)
// @name:kn ಟ್ವಿಟರ್ ಮಾಧ್ಯಮ ಡೌನ್ಲೋಡ್ (2025.04.28 ಸ್ಥಿರ)
// @name:ko 트위터 미디어 다운로드 (2025.04.28 고정)
// @name:ku Twitter Media Download (2025.04.28 Fixed)
// @name:ky Twitter Media Download (2025.04.28 Fixed)
// @name:la Twitter Media Download (2025.04.28 Fixarum)
// @name:lb Twitter Medien eroflueden (2025.04.28 Fixéiert)
// @name:lo Twitter media ດາວໂຫລດ (2025.04.28 ຄົງທີ່)
// @name:lt „Twitter Media“ atsisiuntimas (fiksuota 2025.04.28)
// @name:lv Twitter multivides lejupielāde (2025.04.28 fiksēts)
// @name:mg Twitter Media Download (2025.04.28 Namboarina)
// @name:mi Twitter Media Tango (2025.04.28 Kua whakaritea)
// @name:mk Преземање на медиуми на Твитер (фиксно 2025.04.28)
// @name:ml ട്വിറ്റർ മീഡിയ ഡൗൺലോഡ് (2025.04.28 സ്ഥിരമായി)
// @name:mn Twitter Media татаж авах (2025.04.28 тогтмол)
// @name:ms Twitter Media Muat turun (2025.04.28 Tetap)
// @name:mt Twitter Media Download (2025.04.28 iffissat)
// @name:my Twitter Media Download (2025.04.28)
// @name:ne ट्विटर मिडिया डाउनलोड (20255.04.04.28 स्थिर)
// @name:nl Twitter Media Download (2025.04.28 opgelost)
// @name:no Twitter Media nedlasting (2025.04.28 Fast)
// @name:ny Twitter Media Download (2025.04.28 Okhazikika)
// @name:pa ਟਵਿੱਟਰ ਮੀਡੀਆ ਡਾ Download ਨਲੋਡ (2025.04.28 ਸਥਿਰ)
// @name:pap Medida di Twitter Descarga (2025.04.28 Fiho)
// @name:pl Pobieranie mediów na Twitterze (ustalone 2025.04.28)
// @name:ps د ټویټر میډیا ډاونلوډ (2025.04.04.28 ټاکل شوی)
// @name:pt Download de mídia do Twitter (2025.04.28 corrigido)
// @name:pt-BR Download de mídia do Twitter (2025.04.28 corrigido)
// @name:ro Descărcare media Twitter (2025.04.28 Fixată)
// @name:ru Скачать Twitter Media (2025.04.28 Исправлена)
// @name:rw Twitter Gukuramo (2025.04.28 Byagenwe)
// @name:sg Twitter media Télécharger (2025.04.28 A leke ni)
// @name:si ට්විටර් මාධ්ය බාගත කිරීම (2025.04.28 ස්ථාවර)
// @name:sk Stiahnutie médií na Twitteri (2025.04.28 Opravené)
// @name:sl Prenos medijev na Twitterju (2025.04.28 Fixed)
// @name:sm Twitter Media Download (2025.04.28 Tumau)
// @name:sn Twitter Media Download (2025.04.28 Yakagadziriswa)
// @name:so Twitter Media Media Download (2025.04.28 go’an)
// @name:sr Преузимање Твиттер Медиа (2025.04.28 фиксно)
// @name:sv Twitter media nedladdning (2025.04.28 fast)
// @name:sw Upakuaji wa media ya Twitter (2025.04.28 fasta)
// @name:ta ட்விட்டர் மீடியா பதிவிறக்கம் (2025.04.28 சரி செய்யப்பட்டது)
// @name:te ట్విట్టర్ మీడియా డౌన్లోడ్ (2025.04.28 పరిష్కరించబడింది)
// @name:tg Twitter Media Download (2025.04.04.28 Стратегия)
// @name:th ดาวน์โหลดสื่อ Twitter (2025.04.28 แก้ไข)
// @name:ti ትዊተር ሚድያ ዳውንሎድ (2025.04.28 ጽኑዕ)
// @name:tk Twitter Media göçürip almak (2025.04.28 kesgitlenen)
// @name:tn Bobegakgang jwa Twitter Latalo (2025.04.28 E Tlhomamisitswe)
// @name:to Twitter Mītia Download (2025.04.28 Tuʻunga)
// @name:tpi Twitter Midia Daonlodem (2025.04.28 Oli fiksimap)
// @name:tr Twitter Medya İndir (2025.04.28 Sabit)
// @name:uk Завантажити медіа Twitter (2025.04.28 Виправлено)
// @name:ur ٹویٹر میڈیا ڈاؤن لوڈ (2025.04.28 فکسڈ)
// @name:uz Twitter Media Download (2025.04.28 belgilangan)
// @name:vi Tải xuống phương tiện truyền thông Twitter (2025.04,28 đã sửa)
// @name:xh I-Twitter Demiedia Eendaba (2025.04.28 ilungisiwe)
// @name:yi טוויטטער מעדיע אראפקאפיע (2025.04.28 פאַרפעסטיקט)
// @name:zh Twitter 媒体下载 (2025.04.28 修复)
// @name:zh-CN Twitter 媒体下载 (2025.04.28 修复)
// @name:zh-HK Twitter 媒體下載 (2025.04.28 修復)
// @name:zh-MO Twitter 媒體下載 (2025.04.28 修復)
// @name:zh-MY Twitter 媒体下载 (2025.04.28 修复)
// @name:zh-SG Twitter 媒体下载 (2025.04.28 修复)
// @name:zh-TW Twitter 媒體下載 (2025.04.28 修復)
// @name:zu I-Twitter Media Download (2025.04.28 Ilungisiwe)
// @description Download videos/pictures with one click | Automatically package them into a ZIP file for batch download
// @description:af Laai video’s/prente met een klik af, en ondersteun outomatiese verpakking as ’n zip -lêer om af te laai wanneer bondel aflaai. Ondersteun nuwe API -koppelvlak
// @description:am ቪዲዮዎችን / ስዕሎችን በአንድ ጠቅታ ያውርዱ እና የቡድን ማውረድ በሚረዱበት ጊዜ ለማውረድ ራስ-ሰር ማሸጊያዎችን እንደ ዚፕ ፋይል ይደግፉ. አዲስ የኤፒአይ በይነገጽን ይደግፉ
// @description:ar قم بتنزيل مقاطع الفيديو/الصور بنقرة واحدة ، ودعم التغليف التلقائي كملف مضغوط للتنزيل عند تنزيل الدُفعات. دعم واجهة API جديدة
// @description:az Videoları / şəkilləri bir kliklə yükləyin və toplu yükləmələri zamanı yükləmək üçün bir zip faylı olaraq avtomatik qablaşdırmanı dəstəkləyin. Yeni API interfeysini dəstəkləyin
// @description:be Eénklik downloaden van video's/afbeeldingen, ondersteunt batch-download en pakt ze automatisch in een ZIP-bestand. Ondersteunt de nieuwste API-interface.
// @description:bem Ukupoka amavidyo/amavidyo no kutinika fye kamo, no kwafwilisha ukupakasa ukwaibela nga failo ya ZIP iya kukopolola nga ca kuti ukukopolola kwa ciputulwa. Ukwafwilishako ifipya ifya API
// @description:bg Изтеглете видеоклипове/снимки с едно щракване и поддържайте автоматична опаковка като Zip файл, за да изтеглите при изтегляне на партиди. Подкрепете нов API интерфейс
// @description:bn এক ক্লিকের সাথে ভিডিও/ছবি ডাউনলোড করুন এবং ব্যাচ ডাউনলোড করার সময় ডাউনলোড করতে জিপ ফাইল হিসাবে স্বয়ংক্রিয় প্যাকেজিং সমর্থন করুন। নতুন এপিআই ইন্টারফেস সমর্থন করুন
// @description:bo བརྙན་འཕྲིན་དང་པར་རིས་དེ་ཚོ་གཅིག་སྣུན་གཅིག་གིས་ཕབ་ལེན་བྱེད་པ་དང་། པར་རིས་ཕབ་ལེན་བྱེད་སྐབས་ཕབ་ལེན་བྱེད་པར་རང་འགུལ་གྱི་ཐུམ་སྒྲིལ་ལ་རྒྱབ་སྐྱོར་བྱེད་དགོས། API མཐུད་ཁ་གསར་པར་རྒྱབ་སྐྱོར་བྱེད།
// @description:bs Preuzmite videozapise / slike jednim klikom i podržavajte automatsko pakovanje kao zip datoteku za preuzimanje kada se batch preuzima. Podržite novi API sučelje
// @description:ca Descarregueu vídeos/imatges amb un clic i admeteu els envasos automàtics com a fitxer zip per descarregar -lo quan es descarreguen. Suporteu la nova interfície de l’API
// @description:ceb I-download ang mga video / litrato nga adunay usa ka pag-klik, ug suportahan ang awtomatikong packaging ingon usa ka zip file aron ma-download kung ang mga pag-download sa batch. Pagsuporta sa bag-ong interface sa API
// @description:ckb ڤیدیۆ/وێنەکان بە یەک کلیک دابەزێنە، و پشتگیری لە پاکەت و بەستەری ئۆتۆماتیکی بکە وەک فایلێکی زیپ بۆ داگرتن کاتێک وەجبە دابەزێنراوە. پشتگیری ڕووکاری نوێی API بکە
// @description:cs Stáhněte si videa/obrázky s jedním kliknutím a podporujte automatické obaly jako soubor ZIP ke stažení při stahování dávek. Podporujte nové rozhraní API
// @description:cy Dadlwythwch fideos/lluniau gydag un clic, a chefnogwch becynnu awtomatig fel ffeil zip i’w lawrlwytho wrth lawrlwythiadau swp. Cefnogwch ryngwyneb API newydd
// @description:da Download videoer/billeder med et enkelt klik, og support automatisk emballage som en zip -fil, der skal downloades, når batch -downloads. Støtt nyt API -interface
// @description:de Laden Sie Videos/Bilder mit einem Klick herunter und unterstützen Sie die automatische Verpackung als ZIP -Datei zum Herunterladen beim Stapel -Download. Unterstützen Sie die neue API -Schnittstelle
// @description:dv އެއް ކްލިކުން ވީޑިއޯ/ތަސްވީރުތައް ޑައުންލޯޑްކޮށް، ބެޗް ޑައުންލޯޑް ކުރާއިރު ޑައުންލޯޑް ކުރުމަށް ޒިޕް ފައިލްއެއްގެ ގޮތުގައި އޮޓޮމެޓިކް ޕެކޭޖިންގ އަށް ސަޕޯޓް ކުރައްވާށެވެ. އާ އެޕީއައި އިންޓަފޭސް އަށް ސަޕޯޓް ދިނުން
// @description:dz ཨེབ་གཏང་གཅིག་གིས་ བརྙན་འཕྲིན་/པར་རིས་ཚུ་ཕབ་ལེན་འབད་ཞིནམ་ལས་ བེཆ་ཕབ་ལེན་འབད་བའི་སྐབས་ ཕབ་ལེན་འབད་ནིའི་དོན་ལུ་ ཛིཔ་ཡིག་སྣོད་སྦེ་ རང་བཞིན་ཐུམ་སྒྲིལ་ལུ་རྒྱབ་སྐྱོར་འབད། ཨེ་པི་ཨའི་ ངོས་འདྲ་བ་གསརཔ་རྒྱབ་སྐྱོར་འབད།
// @description:el Κατεβάστε βίντεο/εικόνες με ένα κλικ και υποστηρίξτε την αυτόματη συσκευασία ως αρχείο ZIP για λήψη όταν κατεβάσετε κατά τις λήψεις. Υποστήριξη νέας διασύνδεσης API
// @description:en Download videos/pictures with one click, and support automatic packaging as a ZIP file to download when batch downloads. Support new API interface
// @description:en-GB Download videos/pictures with one click, and support automatic packaging as a ZIP file to download when batch downloads. Support new API interface
// @description:eo Elŝutu filmetojn/bildojn per unu alklako, kaj subtenu aŭtomatan pakaĵon kiel zip -dosieron por elŝuti kiam elŝutoj de batch. Subtenu novan API -interfacon
// @description:es Descargue videos/imágenes con un solo clic y admite el embalaje automático como un archivo zip para descargar cuando se descarga por lotes. Admite una nueva interfaz API
// @description:et Laadige alla videod/pildid ühe klõpsuga ja toetage automaatset pakendit ZIP -failina, mida partii allalaadimisel alla laadida. Toetage uut API -liidest
// @description:eu Deskargatu bideoak / irudiak klik bakarrarekin eta laguntza automatikoko pakete gisa deskargatzeko, lote deskargatzen direnean deskargatzeko. Laguntza API interfaze berria
// @description:fa فیلم ها/تصاویر را با یک کلیک بارگیری کنید و از بسته بندی های خودکار به عنوان یک فایل zip پشتیبانی کنید تا هنگام بارگیری دسته ای بارگیری شود. از رابط جدید API پشتیبانی کنید
// @description:fi Lataa videoita/kuvia yhdellä napsautuksella ja tue automaattista pakkausta zip -tiedostona ladattaessa erän latauksia. Tukea uutta API -käyttöliittymää
// @description:fo Heinta video/myndir við einum klikki, og stuðla sjálvvirkandi pakking sum ZIP fílu at heinta tá batch downloads. Stuðla nýggjum API-grunnflati
// @description:fr Téléchargez des vidéos / photos en un clic et prends en charge l’emballage automatique comme un fichier zip à télécharger lors des téléchargements par lots. Soutenez la nouvelle interface API
// @description:fr-CA Téléchargez des vidéos / photos en un clic et prends en charge l’emballage automatique comme un fichier zip à télécharger lors des téléchargements par lots. Soutenez la nouvelle interface API
// @description:gd Luchdaich sìos bhideothan / dealbhan le aon bhriogadh, agus a ’toirt taic do phostadh fèin-ghluasadach mar fhaidhle zip ri luchdachadh sìos nuair a chleachd thu gu do luchdachadh sìos bailte. Cuir taic ri eadar-aghaidh API ùr
// @description:gl Descarga vídeos/imaxes cun só clic e admite os envases automáticos como ficheiro zip para descargar cando as descargas por lotes. Apoia a nova interface API
// @description:gu એક ક્લિક સાથે વિડિઓઝ/ચિત્રો ડાઉનલોડ કરો અને જ્યારે બેચ ડાઉનલોડ્સ ડાઉનલોડ કરવા માટે ઝિપ ફાઇલ તરીકે સ્વચાલિત પેકેજિંગને સપોર્ટ કરો. નવા API ઇન્ટરફેસને સપોર્ટ કરો
// @description:haw Hoʻoiho i nā wikiō / nā kiʻi me kahi kaomi hoʻokahi, a kākoʻo i ka paleʻana ma keʻano he file zip e hoʻoiho i ka wā e hoʻoiho ai nā waihona. Kākoʻo Ui Openta Interneface
// @description:he הורד סרטונים/תמונות בלחיצה אחת, ותמך באריזה אוטומטית כקובץ מיקוד כדי להוריד כאשר הורדות אצווה. תמיכה בממשק API חדש
// @description:hi एक क्लिक के साथ वीडियो/चित्र डाउनलोड करें, और बैच डाउनलोड होने पर डाउनलोड करने के लिए एक ज़िप फ़ाइल के रूप में स्वचालित पैकेजिंग का समर्थन करें। नए एपीआई इंटरफ़ेस का समर्थन करें
// @description:hr Preuzmite videozapise/slike jednim klikom i podržavajte automatsko pakiranje kao zip datoteku za preuzimanje kada se preuzmu batch. Podržite novo API sučelje
// @description:ht Download videyo/foto ak yon sèl klike sou, ak sipòte anbalaj otomatik kòm yon dosye postal yo download lè pakèt downloads. Sipòte nouvo koòdone API
// @description:hu Töltse le a videókat/képeket egy kattintással, és támogassa az automatikus csomagolást ZIP fájlként, hogy letöltse a tétel letöltésekor. Támogassa az új API felületet
// @description:hy Ներբեռնեք տեսանյութեր / նկարներ մեկ կտտոցով եւ աջակցեք ավտոմատ փաթեթավորմանը `որպես փոստային ֆայլ ներբեռնելու համար, երբ ներլցվում է խմբաքանակի ներլցումներ: Աջակցեք նոր API ինտերֆեյսին
// @description:id Unduh video/gambar dengan satu klik, dan mendukung kemasan otomatis sebagai file zip untuk diunduh saat unduhan batch. Dukung Antarmuka API Baru
// @description:is Sæktu myndbönd/myndir með einum smelli og styððu sjálfvirkar umbúðir sem zip skrá til að hlaða niður þegar lotu niðurhal. Styðjið nýtt API viðmót
// @description:it Scarica video/immagini con un clic e supporta l’imballaggio automatico come file zip da scaricare quando i download batch. Supportare la nuova interfaccia API
// @description:ja ワンクリックでビデオ/写真をダウンロードし、バッチダウンロード時にダウンロードするzipファイルとして自動パッケージをサポートします。新しいAPIインターフェイスをサポートします
// @description:ka ჩამოტვირთეთ ვიდეო/სურათები ერთი დაწკაპუნებით და მხარი დაუჭირეთ ავტომატურ შეფუთვას, როგორც ZIP ფაილი, რომ ჩამოტვირთოთ ჯგუფების ჩამოტვირთვისას. ახალი API ინტერფეისის მხარდაჭერა
// @description:kk Бейнелерді / суреттерді бір рет нұқыңыз және ZIP файлы ретінде автоматты түрде орауышпен жүктеп алыңыз. Жаңа API интерфейсін қолдаңыз
// @description:km ទាញយកវីដេអូ / រូបភាពដោយចុចតែម្តងចុចការវេចខ្ចប់ដោយស្វ័យប្រវត្តិជាឯកសារហ្ស៊ីបដើម្បីទាញយកនៅពេលទាញយកបាច់។ គាំទ្រចំណុចប្រទាក់ API ថ្មី
// @description:kn ಒಂದು ಕ್ಲಿಕ್ನೊಂದಿಗೆ ವೀಡಿಯೊಗಳು/ಚಿತ್ರಗಳನ್ನು ಡೌನ್ಲೋಡ್ ಮಾಡಿ, ಮತ್ತು ಬ್ಯಾಚ್ ಡೌನ್ಲೋಡ್ಗಳು ಯಾವಾಗ ಡೌನ್ಲೋಡ್ ಮಾಡಲು ಜಿಪ್ ಫೈಲ್ ಆಗಿ ಸ್ವಯಂಚಾಲಿತ ಪ್ಯಾಕೇಜಿಂಗ್ ಅನ್ನು ಬೆಂಬಲಿಸಿ. ಹೊಸ API ಇಂಟರ್ಫೇಸ್ ಅನ್ನು ಬೆಂಬಲಿಸಿ
// @description:ko 한 번의 클릭으로 비디오/사진을 다운로드하고 배치 다운로드 할 때 다운로드 할 수있는 zip 파일로 자동 포장을 지원하십시오. 새로운 API 인터페이스를 지원합니다
// @description:ku Vîdyoyên / wêneyên bi yek klîk dakêşin, û piştgiriyê bidin pakkirina otomatîk wekî pelek zip ku dakêşin dema daxistinên batch. Piştgiriya API ya nû piştgirî bikin
// @description:ky Видеолорду / сүрөттөрдү бир чыкылдатуу менен жүктөп алыңыз жана партия жүктөлүп жүктөөлөрдү жүктөп алуу үчүн автоматтык түрдө таңгактоону жүктөп алыңыз. Жаңы API интерфейсинти колдоо
// @description:la Download Videos / Pictures una cum una click, et firmamentum automatic packaging ut ZIP lima ut download cum batch downloads. Support New API interface
// @description:lb تحميل الفيديوهات/الصور بنقرة واحدة، يدعم التحميل الجماعي وتعبئتها تلقائيًا في ملف ZIP. يدعم واجهة API الجديدة.
// @description:lo ດາວໂຫລດວິດີໂອ / ຮູບພາບດ້ວຍການກົດປຸ່ມດຽວ, ແລະສະຫນັບສະຫນູນການຫຸ້ມຫໍ່ແບບອັດຕະໂນມັດເປັນແຟ້ມ ZIP ເພື່ອດາວໂຫລດໃນເວລາທີ່ດາວໂຫລດມາ. ສະຫນັບສະຫນູນການໂຕ້ຕອບ API ໃຫມ່
// @description:lt Atsisiųskite vaizdo įrašus/paveikslėlius vienu paspaudimu ir palaikykite automatines pakuotes kaip ZIP failą, kurį galite atsisiųsti, kai atsisiunčiami partijos. Palaikykite naują API sąsają
// @description:lv Lejupielādējiet videoklipus/attēlus ar vienu klikšķi un atbalstiet automātisko iesaiņojumu kā zip failu, lai lejupielādētu, kad parasti lejupielādē. Atbalstiet jauno API saskarni
// @description:mg Misintona horonantsary / sary miaraka amin’ny tsindry iray, ary manohana fonosana mandeha ho azy amin’ny rakitra zip mba hisintona rehefa download batch. Tohanana ny interface voalohany API
// @description:mi Tangohia nga ataata / pikitia me te paato, me te tautoko i te kapi aunoa hei konae kao hei tango i te wa o nga hapai. Tautokohia te atanga API hou
// @description:mk Преземете видеа/слики со еден клик, и поддржувајте автоматско пакување како поштенска датотека за преземање кога преземате серии. Поддржете нов интерфејс API
// @description:ml ഒരു ക്ലിക്കിലൂടെ വീഡിയോകൾ / ചിത്രങ്ങൾ ഡൗൺലോഡുചെയ്യുക, ബാച്ച് ഡൗൺലോഡുകൾ ഡ download ൺലോഡ് ചെയ്യാനുള്ള ഒരു സിപ്പ് ഫയലായി യാന്ത്രിക പാക്കേജിംഗിനെ പിന്തുണയ്ക്കുക. പുതിയ API ഇന്റർഫേസിനെ പിന്തുണയ്ക്കുക
// @description:mn Багцыг нэг товшилтоор татаж авах, автомат багц татаж авах, багц татаж авахад автомат багцыг татаж авах. Шинэ API интерфэйсийг дэмжинэ
// @description:ms Muat turun video/gambar dengan satu klik, dan menyokong pembungkusan automatik sebagai fail zip untuk dimuat turun apabila muat turun batch. Sokong antara muka API baru
// @description:mt Niżżel vidjows / stampi bi klikk waħda, u appoġġ l-imballaġġ awtomatiku bħala fajl zip biex tniżżel meta tniżżel lott. Appoġġ Interface API Ġdid
// @description:my ဗွီဒီယိုများ / ရုပ်ပုံများကိုတစ်ချက်နှိပ်ပြီး batch downloads ကို download လုပ်သည့်အခါအလိုအလျောက်ထုပ်ပိုးမှုကို Automatic Packaging ကိုကူညီပါ။ API interface အသစ်ကိုထောက်ပံ့ပါ
// @description:ne भिडियोहरू / चित्रहरू डाउनलोड गर्नुहोस् एक क्लिकमा क्लिक गर्नुहोस्, र डाउनलोड डाउनलोड गर्न एक क्लिक फाइलमा स्वचालित प्याकेजिंगलाई समर्थन गर्नुहोस्। नयाँ api ईन्टरफेस समर्थन गर्नुहोस्
// @description:nl Download video’s/foto’s met één klik en ondersteun automatische verpakkingen als een zip -bestand om te downloaden wanneer batch downloads. Steun nieuwe API -interface
// @description:no Last ned videoer/bilder med ett klikk, og støtt automatisk emballasje som en zip -fil for å laste ned når batch nedlastinger. Støtt nytt API -grensesnitt
// @description:ny Tsitsani mavidiyo / zithunzi ndi dinani imodzi, ndikuchirikiza makina okhawo ngati fayilo ya zip kuti mutsitse pomwe ma batch otsitsa. Thandizani API yatsopano ya API
// @description:pa ਇਕ ਕਲਿਕ ਦੇ ਨਾਲ ਵੀਡੀਓ / ਤਸਵੀਰਾਂ ਡਾ Download ਨਲੋਡ ਕਰੋ, ਅਤੇ ਬੈਚ ਡਾਉਨਲੋਡ ਕਰਨ ਲਈ ਇੱਕ ਕਲਿਕ ਕਰੋ. ਨਵੇਂ ਏਪੀਆਈ ਇੰਟਰਫੇਸ ਦਾ ਸਮਰਥਨ ਕਰੋ
// @description:pap Download videonan/potretnan ku un klik, i sostené empaketamentu outomatiko komo un archivo ZIP pa download ora di downloads di lote. Sostené interfase nobo di API
// @description:pl Pobierz filmy/zdjęcia za pomocą jednego kliknięcia i obsługuj automatyczne opakowanie jako plik zip, aby pobrać po pobraniu partii. Obsługuj nowy interfejs API
// @description:ps د ویډیوګانو سره ویډیوګانې / عکسونه ډاونلوډ کړئ، او د ډاونلوډ فایل په توګه د زنايي بسته بندۍ په توګه د ډاونلوډ لپاره د زوم فایل په توګه ملاتړ وکړئ کله چې د بچ کښته کوونې. د نوي اپی انٹرفیس ملاتړ وکړئ
// @description:pt Faça o download de vídeos/imagens com um clique e suporta a embalagem automática como um arquivo zip para download quando downloads em lote. Apoie a nova interface da API
// @description:pt-BR Faça o download de vídeos/imagens com um clique e suporta a embalagem automática como um arquivo zip para download quando downloads em lote. Apoie a nova interface da API
// @description:ro Descărcați videoclipuri/imagini cu un singur clic și acceptați ambalajele automate ca fișier zip pentru a descărca atunci când descărcări de lot. Susțineți interfața API nouă
// @description:ru Загрузите видео/картинки с одним щелчком, и поддержать автоматическую упаковку в качестве zip -файла для загрузки при загрузке пакетов. Поддержать новый интерфейс API
// @description:rw Kuramo amashusho / amashusho hamwe na kanda imwe, kandi ushyigikire gupakira byikora nka zip dosiye kugirango ukuremo mugihe bakuramo amashusho. Shigikira Imigaragarire Nshya ya API
// @description:sg Téléchargé avidéo/afoto ni na lege ti mbeni clic oko, nga mû maboko na automatique tongana mbeni fichier ZIP ti téléchargé ni tongana a téléchargé ni. Mû maboko na fini interface ti API .
// @description:si එක් ක්ලික් කිරීමකින් වීඩියෝ / පින්තූර බාගත කිරීම සමඟ බාගන්න, සහ ස්වයංක්රීය ඇසුරුම්කරණයට බාගත කිරීමේදී බාගත කිරීම සඳහා zip ගොනුවක් ලෙස ස්වයංක්රීය ඇසුරුම්කරණය කරන්න. නව API අතුරුමුහුණතට සහාය වන්න
// @description:sk Stiahnite si videá/obrázky jedným kliknutím a podporujte automatické obaly ako súbor zip a stiahnite si pri stiahnutí dávky. Podporte nové rozhranie API
// @description:sl Prenesite videoposnetke/slike z enim klikom in podprite samodejno embalažo kot zip datoteko, ki jo lahko prenesete, ko se prenašajo s paketom. Podprite nov vmesnik API
// @description:sm Sii mai Vitio / ata ma le tasi kiliki, ma lagolago otometi afifiina o se faila faila e download pe a download downloads. Lagolagoina fou API interface
// @description:sn Dhawunirodha mifananidzo / mifananidzo ine imwe chete tinya, uye rutsigiro otomatiki caping seye zip faira yekurodha kana batch downloads. Tsigira New API Interface
// @description:so Kalasoco fiidiyowyada / sawirrada hal guji, oo taageer baakadaha otomatiga ah sida faylka zip si aad u soo dejiso markii dufcaddu soo dejiso. Taageerso Interface-ka cusub ee API
// @description:sr Преузмите видео записе / слике једним кликом и подржавате аутоматско паковање као ЗИП датотеку за преузимање када се серишите. Подржите нови АПИ интерфејс
// @description:sv Ladda ner videor/bilder med ett klick och stödja automatisk förpackning som zip -fil för att ladda ner när Batch Downloads. Stöd Nytt API -gränssnitt
// @description:sw Pakua video/picha na bonyeza moja, na usaidie ufungaji wa moja kwa moja kama faili ya zip ili kupakua wakati wa kupakua. Kusaidia interface mpya ya API
// @description:ta ஒரே கிளிக்கில் வீடியோக்கள்/படங்களை பதிவிறக்கம் செய்து, தொகுதி பதிவிறக்கும்போது பதிவிறக்குவதற்கு தானியங்கி பேக்கேஜிங்கை ஜிப் கோப்பாக ஆதரிக்கவும். புதிய ஏபிஐ இடைமுகத்தை ஆதரிக்கவும்
// @description:te వీడియోలు/చిత్రాలను ఒకే క్లిక్తో డౌన్లోడ్ చేయండి మరియు బ్యాచ్ డౌన్లోడ్ చేసినప్పుడు డౌన్లోడ్ చేయడానికి ఆటోమేటిక్ ప్యాకేజింగ్కు జిప్ ఫైల్గా మద్దతు ఇవ్వండి. కొత్త API ఇంటర్ఫేస్కు మద్దతు ఇవ్వండి
// @description:tg Видео / расмҳоро бо як клик зеркашӣ кунед ва бастабандӣ автоматикуниро ҳамчун файли ZIP барои зеркашӣ зеркашӣ кунед, вақте ки Боргирии гурӯҳҳо. Дастгирии интерфейси нави API
// @description:th ดาวน์โหลดวิดีโอ/รูปภาพด้วยคลิกเดียวและสนับสนุนบรรจุภัณฑ์อัตโนมัติเป็นไฟล์ซิปเพื่อดาวน์โหลดเมื่อดาวน์โหลดแบทช์ รองรับอินเตอร์เฟส API ใหม่
// @description:ti ቪድዮታት/ስእልታት ብሓደ ጠውቂ ኣውርድ፣ ከምኡ’ውን Batch Downloads ምስ ዝወርድ ከተውርድዎ እትኽእል ኣውቶማቲክ መዐሸጊ ከም ዚፕ ፋይል ደግፉ። ሓድሽ ኤፒኣይ ኢንተርፌስ ምድጋፍ
// @description:tk Bir gezek basany / suratlary göçürip alyň we pol ýüklenende göçürip almak üçin poçta faýly göçürip alyň. Täze API interfeýseri goldamak
// @description:tn Laisolola dibidio/ditshwantsho ka go tobetsa gangwe fela, mme o tshegetse go phuthela ka go itirisa jaaka faele ya ZIP go e laisolola fa di-download tsa ditlhopha. Tshegetsa segokaganyi se sešwa sa API
// @description:to Download ’a e ngaahi vitio/ngaahi fakatata ’aki ha kiliki ’e taha, pea poupou’i ’a e ’otometiki ’a e packaging ko ha faile ZIP ke download ’i he taimi ’oku download ai ’a e batch. Poupou’i ’a e interface fo’ou API .
// @description:tpi Daonlodem ol vidio/pikja wetem wan klik, mo sapotem otomatik pakejing olsem wan ZIP fael blong daonlodem taem yu stap daon long ol daon daonlod. Sapotim nupela API intafes
// @description:tr Videoları/resimleri tek bir tıklamayla indirin ve toplu indirmeler yaparken indirilecek bir zip dosyası olarak otomatik ambalajı destekleyin. Yeni API arayüzünü destekleyin
// @description:uk Завантажте відео/зображення одним клацанням та підтримайте автоматичну упаковку як Zip -файл для завантаження при завантаженні пакетів. Підтримуйте новий інтерфейс API
// @description:ur ایک کلک کے ساتھ ویڈیوز/تصاویر ڈاؤن لوڈ کریں ، اور بیچ ڈاؤن لوڈ ہونے پر ڈاؤن لوڈ کرنے کے لئے خودکار پیکیجنگ کو زپ فائل کے طور پر سپورٹ کریں۔ نئے API انٹرفیس کی حمایت کریں
// @description:uz Videolarni / rasmlarni bitta bosish bilan yuklab oling va partiya yuklab olinganda yuklab olish uchun ZIP fayl sifatida avtomatik qadoqlash. Yangi API interfeysini qo’llab-quvvatlang
// @description:vi Tải xuống video/hình ảnh chỉ bằng một cú nhấp chuột và hỗ trợ bao bì tự động dưới dạng tệp zip để tải xuống khi tải xuống hàng loạt. Hỗ trợ giao diện API mới
// @description:xh Khuphela iividiyo / imifanekiso kunye Cofa, kwaye uxhase ukupakishwa ngokuzenzekelayo njengefayile ye-zip ukuze ukhuphele xa kutsala umdla. INKXASO YOKUGQIBELA I-API
// @description:yi אראפקאפיע ווידיאס / בילדער מיט איין גיט, און שטיצן אָטאַמאַטיק פּאַקקאַגינג ווי אַ פאַרשלעסלען טעקע צו אָפּלאָדירן ווען פּעקל דאַונלאָודז. סופּפּאָרט ניו אַפּי צובינד
// @description:zh 一键下载视频/图片,支持批量下载时自动打包为一个ZIP文件下载.支持新版API接口
// @description:zh-CN 一键下载视频/图片,支持批量下载时自动打包为一个ZIP文件下载.支持新版API接口
// @description:zh-HK 一鍵下載視頻/圖片,支持批量下載時自動打包為一個ZIP文件下載.支持新版API接口
// @description:zh-MO 一鍵下載視頻/圖片,支持批量下載時自動打包為一個ZIP文件下載.支持新版API接口
// @description:zh-MY 一键下载视频/图片,支持批量下载时自动打包为一个ZIP文件下载.支持新版API接口
// @description:zh-SG 一键下载视频/图片,支持批量下载时自动打包为一个ZIP文件下载.支持新版API接口
// @description:zh-TW 一鍵下載視頻/圖片,支持批量下載時自動打包為一個ZIP文件下載.支持新版API接口
// @description:zu Landa amavidiyo / izithombe ngokuchofoza okukodwa, bese usekela ukupakishwa okuzenzakalelayo njengefayela le-zip ukulanda lapho i-batch ilanda. Sekela interface entsha ye-API
// @author goemon2017,天音,Tiande,人民的勤务员 <[email protected]>
// @namespace https://github.com/ChinaGodMan/UserScripts
// @supportURL https://github.com/ChinaGodMan/UserScripts/issues
// @homepageURL https://github.com/ChinaGodMan/UserScripts
// @license MIT
// @icon https://raw.githubusercontent.com/ChinaGodMan/UserScriptsHistory/main/scriptsIcon/x.svg
// @compatible chrome
// @compatible firefox
// @compatible edge
// @compatible opera
// @compatible safari
// @compatible kiwi
// @compatible qq
// @compatible via
// @compatible brave
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_download
// @match https://x.com/*
// @match https://twitter.com/*
// @version 2025.04.28.1503
// @created 2025-03-11 08:11:29
// @modified 2025-03-11 08:11:29
// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
// ==/UserScript==
/**
* File: twitter-media-downloader.user.js
* Project: UserScripts
* File Created: 2025/03/11,Tuesday 08:11:41
* Author: goemon2017,天音,Tiande,人民的勤务员@ChinaGodMan ([email protected])
* -----
* Last Modified: 2025/04/28,Monday 15:17:18
* Last Modified: 2025/04/28,Monday 15:17:18
* Modified By: 人民的勤务员@ChinaGodMan ([email protected])
* License: MIT License
* Copyright © 2024 - 2025 ChinaGodMan,Inc
*/
//! 修复代码来自:goemon2017:https://greatest.deepsurf.us/scripts/423001/discussions/296626#comment-589742
/* jshint esversion: 8 */
const filename = 'twitter_{user-name}(@{user-id})_{date-time}_{status-id}_{file-type}'
const TMD = (function () {
let lang, host, history, show_sensitive, is_tweetdeck
return {
init: async function () {
GM_registerMenuCommand((this.language[navigator.language] || this.language.en).settings, this.settings)
lang = this.language[document.querySelector('html').lang] || this.language.en
host = location.hostname
is_tweetdeck = host.indexOf('tweetdeck') >= 0
history = this.storage_obsolete()
if (history.length) {
this.storage(history)
this.storage_obsolete(true)
} else history = await this.storage()
show_sensitive = GM_getValue('show_sensitive', false)
document.head.insertAdjacentHTML('beforeend', '<style>' + this.css + (show_sensitive ? this.css_ss : '') + '</style>')
let observer = new MutationObserver(ms => ms.forEach(m => m.addedNodes.forEach(node => this.detect(node))))
observer.observe(document.body, { childList: true, subtree: true })
},
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)
},
addButtonTo: function (article) {
if (article.dataset.detected) return
article.dataset.detected = 'true'
let media_selector = [
'a[href*="/photo/1"]',
'div[role="progressbar"]',
'button[data-testid="playButton"]',
'a[href="/settings/content_you_see"]', //hidden content
'div.media-image-container', // for tweetdeck
'div.media-preview-container', // for tweetdeck
'div[aria-labelledby]>div:first-child>div[role="button"][tabindex="0"]' //for audio (experimental)
]
let media = article.querySelector(media_selector.join(','))
if (media) {
let status_id = article.querySelector('a[href*="/status/"]').href.split('/status/').pop().split('/').shift()
let btn_group = article.querySelector('div[role="group"]:last-of-type, ul.tweet-actions, ul.tweet-detail-actions')
let btn_share = Array.from(btn_group.querySelectorAll(':scope>div>div, li.tweet-action-item>a, li.tweet-detail-action-item>a')).pop().parentNode
let btn_down = btn_share.cloneNode(true)
btn_down.querySelector('button').removeAttribute('disabled')
if (is_tweetdeck) {
btn_down.firstElementChild.innerHTML = '<svg viewBox="0 0 24 24" style="width: 18px; height: 18px;">' + this.svg + '</svg>'
btn_down.firstElementChild.removeAttribute('rel')
btn_down.classList.replace('pull-left', 'pull-right')
} else {
btn_down.querySelector('svg').innerHTML = this.svg
}
let is_exist = history.indexOf(status_id) >= 0
this.status(btn_down, 'tmd-down')
this.status(btn_down, is_exist ? 'completed' : 'download', is_exist ? lang.completed : lang.download)
btn_group.insertBefore(btn_down, btn_share.nextSibling)
btn_down.onclick = () => this.click(btn_down, status_id, is_exist)
if (show_sensitive) {
let btn_show = article.querySelector('div[aria-labelledby] div[role="button"][tabindex="0"]:not([data-testid]) > div[dir] > span > span')
if (btn_show) btn_show.click()
}
}
let imgs = article.querySelectorAll('a[href*="/photo/"]')
if (imgs.length > 1) {
let status_id = article.querySelector('a[href*="/status/"]').href.split('/status/').pop().split('/').shift()
let btn_group = article.querySelector('div[role="group"]:last-of-type')
let btn_share = Array.from(btn_group.querySelectorAll(':scope>div>div')).pop().parentNode
imgs.forEach(img => {
let index = img.href.split('/status/').pop().split('/').pop()
let is_exist = history.indexOf(status_id) >= 0
let btn_down = document.createElement('div')
btn_down.innerHTML = '<div><div><svg viewBox="0 0 24 24" style="width: 18px; height: 18px;">' + this.svg + '</svg></div></div>'
btn_down.classList.add('tmd-down', 'tmd-img')
this.status(btn_down, 'download')
img.parentNode.appendChild(btn_down)
btn_down.onclick = e => {
e.preventDefault()
this.click(btn_down, status_id, is_exist, index)
}
})
}
},
addButtonToMedia: function (listitems) {
listitems.forEach(li => {
if (li.dataset.detected) return
li.dataset.detected = 'true'
let status_id = li.querySelector('a[href*="/status/"]').href.split('/status/').pop().split('/').shift()
let is_exist = history.indexOf(status_id) >= 0
let btn_down = document.createElement('div')
btn_down.innerHTML = '<div><div><svg viewBox="0 0 24 24" style="width: 18px; height: 18px;">' + this.svg + '</svg></div></div>'
btn_down.classList.add('tmd-down', 'tmd-media')
this.status(btn_down, is_exist ? 'completed' : 'download', is_exist ? lang.completed : lang.download)
li.appendChild(btn_down)
btn_down.onclick = () => this.click(btn_down, status_id, is_exist)
})
},
click: async function (btn, status_id, is_exist, index) {
if (btn.classList.contains('loading')) return
this.status(btn, 'loading')
let out = (await GM_getValue('filename', filename)).split('\n').join('')
let save_history = await GM_getValue('save_history', true)
let json = await this.fetchJson(status_id)
let tweet = json.quoted_status_result?.result?.legacy?.media//此媒体存在,属于引用推文
|| json.quoted_status_result?.result?.legacy
|| json.legacy
let user = json.core.user_results.result.legacy
let invalid_chars = { '\\': '\', '\/': '/', '\|': '|', '<': '<', '>': '>', ':': ':', '*': '*', '?': '?', '"': '"', '\u200b': '', '\u200c': '', '\u200d': '', '\u2060': '', '\ufeff': '', '🔞': '' }
let datetime = out.match(/\{date-time(-local)?:[^{}]+\}/) ? out.match(/\{date-time(?:-local)?:([^{}]+)\}/)[1].replace(/[\\/|<>*?:"]/g, v => invalid_chars[v]) : 'YYYYMMDD-hhmmss'
let info = {}
info['status-id'] = status_id
info['user-name'] = user.name.replace(/([\\/|*?:"\u200b-\u200d\u2060\ufeff]|🔞)/g, v => invalid_chars[v])
info['user-id'] = user.screen_name
info['date-time'] = this.formatDate(tweet.created_at, datetime)
info['date-time-local'] = this.formatDate(tweet.created_at, datetime, true)
info['full-text'] = tweet.full_text.split('\n').join(' ').replace(/\s*https:\/\/t\.co\/\w+/g, '').replace(/[\\/|<>*?:"\u200b-\u200d\u2060\ufeff]/g, v => invalid_chars[v])
let medias = tweet.extended_entities && tweet.extended_entities.media
if (json?.card) {
this.status(btn, 'failed', 'This tweet contains a link, which is not supported by this script.')
return
}
if (!Array.isArray(medias)) {
this.status(btn, 'failed', 'MEDIA_NOT_FOUND')
return
}
if (index) medias = [medias[index - 1]]
if (medias.length > 0) {
let tasks = medias.map((media, i) => {
info.url = media.type == 'photo' ? media.media_url_https + ':orig' : media.video_info.variants.filter(n => n.content_type == 'video/mp4').sort((a, b) => b.bitrate - a.bitrate)[0].url
info.file = info.url.split('/').pop().split(/[:?]/).shift()
info['file-name'] = info.file.split('.').shift()
info['file-ext'] = info.file.split('.').pop()
info['file-type'] = media.type.replace('animated_', '')
info.out = (out.replace(/\.?\{file-ext\}/, '') + ((medias.length > 1 || index) && !out.match('{file-name}') ? '-' + (index ? index - 1 : i) : '') + '.{file-ext}').replace(/\{([^{}:]+)(:[^{}]+)?\}/g, (match, name) => info[name])
return { url: info.url, name: info.out }
})
this.downloader.add(tasks, btn, save_history, is_exist, status_id, GM_getValue('enable_packaging', true))
} else {
this.status(btn, 'failed', 'MEDIA_NOT_FOUND')
}
}, downloader: (function () {
let tasks = [], thread = 0, failed = 0, notifier, has_failed = false
return {
add: function (taskList, btn, save_history, is_exist, status_id, enable_packaging) {
if (taskList.length > 1) {
tasks.push(...taskList)
this.update()
if (enable_packaging) {
let zip = new JSZip()
let completedCount = 0
taskList.forEach((task, i) => {
thread++
this.update()
fetch(task.url)
.then(response => response.blob())
.then(blob => {
zip.file(task.name, blob)
tasks = tasks.filter(t => t.url !== task.url)
thread--
this.update()
completedCount++
if (completedCount === taskList.length) {
zip.generateAsync({ type: 'blob' }).then(content => {
let a = document.createElement('a')
a.href = URL.createObjectURL(content)
a.download = `${taskList[0].name}.zip`
a.click()
this.status(btn, 'completed', lang.completed)
if (save_history && !is_exist) {
history.push(status_id)
this.storage(status_id)
}
})
}
})
.catch(error => {
failed++
tasks = tasks.filter(t => t.url !== task.url)
this.status(btn, 'failed', error.message)
this.update()
})
})
} else {
taskList.forEach((task) => {
thread++
this.update()
GM_download({
url: task.url,
name: task.name,
onload: () => {
thread--
tasks = tasks.filter(t => t.url !== task.url)
this.status(btn, 'completed', lang.completed)
if (save_history && !is_exist) {
history.push(status_id)
this.storage(status_id)
}
this.update()
},
onerror: result => {
thread--
failed++
tasks = tasks.filter(t => t.url !== task.url)
this.status(btn, 'failed', result.details.current)
this.update()
}
})
})
}
} else {
tasks.push(taskList[0])
thread++
this.update()
GM_download({
url: taskList[0].url,
name: taskList[0].name,
onload: () => {
thread--
tasks = tasks.filter(t => t.url !== taskList[0].url)
this.status(btn, 'completed', lang.completed)
if (save_history && !is_exist) {
history.push(status_id)
this.storage(status_id)
}
this.update()
},
onerror: result => {
thread--
failed++
tasks = tasks.filter(t => t.url !== taskList[0].url)
this.status(btn, 'failed', result.details.current)
this.update()
}
})
}
},
status: function (btn, css, title, style) {
if (css) {
btn.classList.remove('download', 'completed', 'loading', 'failed')
btn.classList.add(css)
}
if (title) btn.title = title
if (style) btn.style.cssText = style
},
storage: async function (value) {
let data = await GM_getValue('download_history', [])
let data_length = data.length
if (value) {
if (Array.isArray(value)) data = data.concat(value)
else if (data.indexOf(value) < 0) data.push(value)
} else return data
if (data.length > data_length) GM_setValue('download_history', data)
},
update: function () {
if (!notifier) {
notifier = document.createElement('div')
notifier.title = 'Twitter Media Downloader'
notifier.classList.add('tmd-notifier')
notifier.innerHTML = '<label>0</label>|<label>0</label>'
document.body.appendChild(notifier)
}
if (failed > 0 && !has_failed) {
has_failed = true
notifier.innerHTML += '|'
let clear = document.createElement('label')
notifier.appendChild(clear)
clear.onclick = () => {
notifier.innerHTML = '<label>0</label>|<label>0</label>'
failed = 0
has_failed = false
this.update()
}
}
notifier.firstChild.innerText = thread
notifier.firstChild.nextElementSibling.innerText = tasks.length - thread - failed
if (failed > 0) notifier.lastChild.innerText = failed
if (thread > 0 || tasks.length > 0 || failed > 0) notifier.classList.add('running')
else notifier.classList.remove('running')
}
}
})(),
status: function (btn, css, title, style) {
if (css) {
btn.classList.remove('download', 'completed', 'loading', 'failed')
btn.classList.add(css)
}
if (title) btn.title = title
if (style) btn.style.cssText = style
},
settings: async function () {
const $element = (parent, tag, style, content, css) => {
let el = document.createElement(tag)
if (style) el.style.cssText = style
if (typeof content !== 'undefined') {
if (tag == 'input') {
if (content == 'checkbox') el.type = content
else el.value = content
} else el.innerHTML = content
}
if (css) css.split(' ').forEach(c => el.classList.add(c))
parent.appendChild(el)
return el
}
let wapper = $element(document.body, 'div', 'position: fixed; left: 0px; top: 0px; width: 100%; height: 100%; background-color: #0009; z-index: 10;')
let wapper_close
wapper.onmousedown = e => {
wapper_close = e.target == wapper
}
wapper.onmouseup = e => {
if (wapper_close && e.target == wapper) wapper.remove()
}
let dialog = $element(wapper, 'div', 'position: absolute; left: 50%; top: 50%; transform: translateX(-50%) translateY(-50%); width: fit-content; width: -moz-fit-content; background-color: #f3f3f3; border: 1px solid #ccc; border-radius: 10px; color: black;')
let title = $element(dialog, 'h3', 'margin: 10px 20px;', lang.dialog.title)
let options = $element(dialog, 'div', 'margin: 10px; border: 1px solid #ccc; border-radius: 5px;')
let save_history_label = $element(options, 'label', 'display: block; margin: 10px;', lang.dialog.save_history)
let save_history_input = $element(save_history_label, 'input', 'float: left;', 'checkbox')
save_history_input.checked = await GM_getValue('save_history', true)
save_history_input.onchange = () => {
GM_setValue('save_history', save_history_input.checked)
}
let clear_history = $element(save_history_label, 'label', 'display: inline-block; margin: 0 10px; color: blue;', lang.dialog.clear_history)
clear_history.onclick = () => {
if (confirm(lang.dialog.clear_confirm)) {
history = []
GM_setValue('download_history', [])
}
}
let show_sensitive_label = $element(options, 'label', 'display: block; margin: 10px;', lang.dialog.show_sensitive)
let show_sensitive_input = $element(show_sensitive_label, 'input', 'float: left;', 'checkbox')
show_sensitive_input.checked = await GM_getValue('show_sensitive', false)
show_sensitive_input.onchange = () => {
show_sensitive = show_sensitive_input.checked
GM_setValue('show_sensitive', show_sensitive)
}
let show_enable_packaging = $element(options, 'label', 'display: block; margin: 10px;', lang.enable_packaging)
let show_enable_packaging_input = $element(show_enable_packaging, 'input', 'float: left;', 'checkbox')
show_enable_packaging_input.checked = await GM_getValue('enable_packaging', true)
show_enable_packaging_input.onchange = () => {
GM_setValue('enable_packaging', show_enable_packaging_input.checked)
}
let filename_div = $element(dialog, 'div', 'margin: 10px; border: 1px solid #ccc; border-radius: 5px;')
let filename_label = $element(filename_div, 'label', 'display: block; margin: 10px 15px;', lang.dialog.pattern)
let filename_input = $element(filename_label, 'textarea', 'display: block; min-width: 500px; max-width: 500px; min-height: 100px; font-size: inherit; background: white; color: black;', await GM_getValue('filename', filename))
let filename_tags = $element(filename_div, 'label', 'display: table; margin: 10px;', `
<span class="tmd-tag" title="user name">{user-name}</span>
<span class="tmd-tag" title="The user name after @ sign.">{user-id}</span>
<span class="tmd-tag" title="example: 1234567890987654321">{status-id}</span>
<span class="tmd-tag" title="{date-time} : Posted time in UTC.\n{date-time-local} : Your local time zone.\n\nDefault:\nYYYYMMDD-hhmmss => 20201231-235959\n\nExample of custom:\n{date-time:DD-MMM-YY hh.mm} => 31-DEC-21 23.59">{date-time}</span><br>
<span class="tmd-tag" title="Text content in tweet.">{full-text}</span>
<span class="tmd-tag" title="Type of "video" or "photo" or "gif".">{file-type}</span>
<span class="tmd-tag" title="Original filename from URL.">{file-name}</span>
`)
filename_input.selectionStart = filename_input.value.length
filename_tags.querySelectorAll('.tmd-tag').forEach(tag => {
tag.onclick = () => {
let ss = filename_input.selectionStart
let se = filename_input.selectionEnd
filename_input.value = filename_input.value.substring(0, ss) + tag.innerText + filename_input.value.substring(se)
filename_input.selectionStart = ss + tag.innerText.length
filename_input.selectionEnd = ss + tag.innerText.length
filename_input.focus()
}
})
let btn_save = $element(title, 'label', 'float: right;', lang.dialog.save, 'tmd-btn')
btn_save.onclick = async () => {
await GM_setValue('filename', filename_input.value)
wapper.remove()
}
},
fetchJson: async function (status_id) {
let base_url = `https://${host}/i/api/graphql/2ICDjqPd81tulZcYrtpTuQ/TweetResultByRestId`
let variables = {
'tweetId': status_id,
'with_rux_injections': false,
'includePromotedContent': true,
'withCommunity': true,
'withQuickPromoteEligibilityTweetFields': true,
'withBirdwatchNotes': true,
'withVoice': true,
'withV2Timeline': true
}
let features = {
'articles_preview_enabled': true,
'c9s_tweet_anatomy_moderator_badge_enabled': true,
'communities_web_enable_tweet_community_results_fetch': false,
'creator_subscriptions_quote_tweet_preview_enabled': false,
'creator_subscriptions_tweet_preview_api_enabled': false,
'freedom_of_speech_not_reach_fetch_enabled': true,
'graphql_is_translatable_rweb_tweet_is_translatable_enabled': true,
'longform_notetweets_consumption_enabled': false,
'longform_notetweets_inline_media_enabled': true,
'longform_notetweets_rich_text_read_enabled': false,
'premium_content_api_read_enabled': false,
'profile_label_improvements_pcf_label_in_post_enabled': true,
'responsive_web_edit_tweet_api_enabled': false,
'responsive_web_enhance_cards_enabled': false,
'responsive_web_graphql_exclude_directive_enabled': false,
'responsive_web_graphql_skip_user_profile_image_extensions_enabled': false,
'responsive_web_graphql_timeline_navigation_enabled': false,
'responsive_web_grok_analysis_button_from_backend': false,
'responsive_web_grok_analyze_button_fetch_trends_enabled': false,
'responsive_web_grok_analyze_post_followups_enabled': false,
'responsive_web_grok_image_annotation_enabled': false,
'responsive_web_grok_share_attachment_enabled': false,
'responsive_web_grok_show_grok_translated_post': false,
'responsive_web_jetfuel_frame': false,
'responsive_web_media_download_video_enabled': false,
'responsive_web_twitter_article_tweet_consumption_enabled': true,
'rweb_tipjar_consumption_enabled': true,
'rweb_video_screen_enabled': false,
'standardized_nudges_misinfo': true,
'tweet_awards_web_tipping_enabled': false,
'tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled': true,
'tweetypie_unmention_optimization_enabled': false,
'verified_phone_label_enabled': false,
'view_counts_everywhere_api_enabled': true
}
let url = encodeURI(`${base_url}?variables=${JSON.stringify(variables)}&features=${JSON.stringify(features)}`)
let cookies = this.getCookie()
let headers = {
'authorization': 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA',
'x-twitter-active-user': 'yes',
'x-twitter-client-language': cookies.lang,
'x-csrf-token': cookies.ct0
}
if (cookies.ct0.length == 32) headers['x-guest-token'] = cookies.gt
let tweet_detail = await fetch(url, { headers: headers }).then(result => result.json())
let tweet_result = tweet_detail.data.tweetResult.result
return tweet_result.tweet || tweet_result
},
getCookie: function (name) {
let cookies = {}
document.cookie.split(';').filter(n => n.indexOf('=') > 0).forEach(n => {
n.replace(/^([^=]+)=(.+)$/, (match, name, value) => {
cookies[name.trim()] = value.trim()
})
})
return name ? cookies[name] : cookies
},
storage: async function (value) {
let data = await GM_getValue('download_history', [])
let data_length = data.length
if (value) {
if (Array.isArray(value)) data = data.concat(value)
else if (data.indexOf(value) < 0) data.push(value)
} else return data
if (data.length > data_length) GM_setValue('download_history', data)
},
storage_obsolete: function (is_remove) {
let data = JSON.parse(localStorage.getItem('history') || '[]')
if (is_remove) localStorage.removeItem('history')
else return data
},
formatDate: function (i, o, tz) {
let d = new Date(i)
if (tz) d.setMinutes(d.getMinutes() - d.getTimezoneOffset())
let m = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']
let v = {
YYYY: d.getUTCFullYear().toString(),
YY: d.getUTCFullYear().toString(),
MM: d.getUTCMonth() + 1,
MMM: m[d.getUTCMonth()],
DD: d.getUTCDate(),
hh: d.getUTCHours(),
mm: d.getUTCMinutes(),
ss: d.getUTCSeconds(),
h2: d.getUTCHours() % 12,
ap: d.getUTCHours() < 12 ? 'AM' : 'PM'
}
return o.replace(/(YY(YY)?|MMM?|DD|hh|mm|ss|h2|ap)/g, n => ('0' + v[n]).substr(-n.length))
},
language: {
en: { download: 'Download', completed: 'Download Completed', settings: 'Settings', dialog: { title: 'Download Settings', save: 'Save', save_history: 'Remember download history', clear_history: '(Clear)', clear_confirm: 'Clear download history?', show_sensitive: 'Always show sensitive content', pattern: 'File Name Pattern' }, enable_packaging: 'Package multiple files into a ZIP' },
ja: { download: 'ダウンロード', completed: 'ダウンロード完了', settings: '設定', dialog: { title: 'ダウンロード設定', save: '保存', save_history: 'ダウンロード履歴を保存する', clear_history: '(クリア)', clear_confirm: 'ダウンロード履歴を削除する?', show_sensitive: 'センシティブな内容を常に表示する', pattern: 'ファイル名パターン' }, enable_packaging: '複数ファイルを ZIP にパッケージ化する' },
zh: { download: '下载', completed: '下载完成', settings: '设置', dialog: { title: '下载设置', save: '保存', save_history: '保存下载记录', clear_history: '(清除)', clear_confirm: '确认要清除下载记录?', show_sensitive: '自动显示敏感的内容', pattern: '文件名格式' }, enable_packaging: '多文件打包成 ZIP' },
'zh-Hant': { download: '下載', completed: '下載完成', settings: '設置', dialog: { title: '下載設置', save: '保存', save_history: '保存下載記錄', clear_history: '(清除)', clear_confirm: '確認要清除下載記錄?', show_sensitive: '自動顯示敏感的内容', pattern: '文件名規則' }, enable_packaging: '多文件打包成 ZIP' }
},
css: `
.tmd-down {margin-left: 12px; order: 99;}
.tmd-down:hover > div > div > div > div {color: rgba(29, 161, 242, 1.0);}
.tmd-down:hover > div > div > div > div > div {background-color: rgba(29, 161, 242, 0.1);}
.tmd-down:active > div > div > div > div > div {background-color: rgba(29, 161, 242, 0.2);}
.tmd-down:hover svg {color: rgba(29, 161, 242, 1.0);}
.tmd-down:hover div:first-child:not(:last-child) {background-color: rgba(29, 161, 242, 0.1);}
.tmd-down:active div:first-child:not(:last-child) {background-color: rgba(29, 161, 242, 0.2);}
.tmd-down.tmd-media {position: absolute; right: 0;}
.tmd-down.tmd-media > div {display: flex; border-radius: 99px; margin: 2px;}
.tmd-down.tmd-media > div > div {display: flex; margin: 6px; color: #fff;}
.tmd-down.tmd-media:hover > div {background-color: rgba(255,255,255, 0.6);}
.tmd-down.tmd-media:hover > div > div {color: rgba(29, 161, 242, 1.0);}
.tmd-down.tmd-media:not(:hover) > div > div {filter: drop-shadow(0 0 1px #000);}
.tmd-down g {display: none;}
.tmd-down.download g.download, .tmd-down.completed g.completed, .tmd-down.loading g.loading,.tmd-down.failed g.failed {display: unset;}
.tmd-down.loading svg {animation: spin 1s linear infinite;}
@keyframes spin {0% {transform: rotate(0deg);} 100% {transform: rotate(360deg);}}
.tmd-btn {display: inline-block; background-color: #1DA1F2; color: #FFFFFF; padding: 0 20px; border-radius: 99px;}
.tmd-tag {display: inline-block; background-color: #FFFFFF; color: #1DA1F2; padding: 0 10px; border-radius: 10px; border: 1px solid #1DA1F2; font-weight: bold; margin: 5px;}
.tmd-btn:hover {background-color: rgba(29, 161, 242, 0.9);}
.tmd-tag:hover {background-color: rgba(29, 161, 242, 0.1);}
.tmd-notifier {display: none; position: fixed; left: 16px; bottom: 16px; color: #000; background: #fff; border: 1px solid #ccc; border-radius: 8px; padding: 4px;}
.tmd-notifier.running {display: flex; align-items: center;}
.tmd-notifier label {display: inline-flex; align-items: center; margin: 0 8px;}
.tmd-notifier label:before {content: " "; width: 32px; height: 16px; background-position: center; background-repeat: no-repeat;}
.tmd-notifier label:nth-child(1):before {background-image:url("data:image/svg+xml;charset=utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2216%22 height=%2216%22 viewBox=%220 0 24 24%22><path d=%22M3,14 v5 q0,2 2,2 h14 q2,0 2,-2 v-5 M7,10 l4,4 q1,1 2,0 l4,-4 M12,3 v11%22 fill=%22none%22 stroke=%22%23666%22 stroke-width=%222%22 stroke-linecap=%22round%22 /></svg>");}
.tmd-notifier label:nth-child(2):before {background-image:url("data:image/svg+xml;charset=utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2216%22 height=%2216%22 viewBox=%220 0 24 24%22><path d=%22M12,2 a1,1 0 0 1 0,20 a1,1 0 0 1 0,-20 M12,5 v7 h6%22 fill=%22none%22 stroke=%22%23999%22 stroke-width=%222%22 stroke-linejoin=%22round%22 stroke-linecap=%22round%22 /></svg>");}
.tmd-notifier label:nth-child(3):before {background-image:url("data:image/svg+xml;charset=utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2216%22 height=%2216%22 viewBox=%220 0 24 24%22><path d=%22M12,0 a2,2 0 0 0 0,24 a2,2 0 0 0 0,-24%22 fill=%22%23f66%22 stroke=%22none%22 /><path d=%22M14.5,5 a1,1 0 0 0 -5,0 l0.5,9 a1,1 0 0 0 4,0 z M12,17 a2,2 0 0 0 0,5 a2,2 0 0 0 0,-5%22 fill=%22%23fff%22 stroke=%22none%22 /></svg>");}
.tmd-down.tmd-img {position: absolute; right: 0; bottom: 0; display: none !important;}
.tmd-down.tmd-img > div {display: flex; border-radius: 99px; margin: 2px; background-color: rgba(255,255,255, 0.6);}
.tmd-down.tmd-img > div > div {display: flex; margin: 6px; color: #fff !important;}
.tmd-down.tmd-img:not(:hover) > div > div {filter: drop-shadow(0 0 1px #000);}
.tmd-down.tmd-img:hover > div > div {color: rgba(29, 161, 242, 1.0);}
:hover > .tmd-down.tmd-img, .tmd-img.loading, .tmd-img.completed, .tmd-img.failed {display: block !important;}
.tweet-detail-action-item {width: 20% !important;}
`,
css_ss: `
/* show sensitive in media tab */
li[role="listitem"]>div>div>div>div:not(:last-child) {filter: none;}
li[role="listitem"]>div>div>div>div+div:last-child {display: none;}
`,
svg: `
<g class="download"><path d="M3,14 v5 q0,2 2,2 h14 q2,0 2,-2 v-5 M7,10 l4,4 q1,1 2,0 l4,-4 M12,3 v11" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" /></g>
<g class="completed"><path d="M3,14 v5 q0,2 2,2 h14 q2,0 2,-2 v-5 M7,10 l3,4 q1,1 2,0 l8,-11" fill="none" stroke="#1DA1F2" stroke-width="2" stroke-linecap="round" /></g>
<g class="loading"><circle cx="12" cy="12" r="10" fill="none" stroke="#1DA1F2" stroke-width="4" opacity="0.4" /><path d="M12,2 a10,10 0 0 1 10,10" fill="none" stroke="#1DA1F2" stroke-width="4" stroke-linecap="round" /></g>
<g class="failed"><circle cx="12" cy="12" r="11" fill="#f33" stroke="currentColor" stroke-width="2" opacity="0.8" /><path d="M14,5 a1,1 0 0 0 -4,0 l0.5,9.5 a1.5,1.5 0 0 0 3,0 z M12,17 a2,2 0 0 0 0,4 a2,2 0 0 0 0,-4" fill="#fff" stroke="none" /></g>
`
}
})()
TMD.init()