您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
🎬 Twitter/X平台专业媒体下载器!📱 一键下载高清视频和图片,支持推文时间线、视频播放器、图片模态框多位置下载按钮 🔥 自动获取最高画质,批量下载,完全免费无广告 ✨
// ==UserScript== // @name 🚀 🎬 Enhanced Twitter Media Downloader | 推特媒体下载器 // @name:ar 🚀 🎬 محسن تنزيل وسائط تويتر | تحميل الفيديو والصورة عالي الدقة // @name:bg 🚀 🎬 Подобрен Twitter Media Downloader | HD видео и изображение // @name:ckb 🚀 🎬 پەیشکەوتووی داگرتنی میدیای تویتەر | داگرتنی ڤیدیۆ و وێنەی HD // @name:cs 🚀 🎬 Vylepšený Twitter Media Downloader | HD video a obrázky // @name:da 🚀 🎬 Forbedret Twitter Media Downloader | HD video og billeder // @name:de 🚀 🎬 Verbesserter Twitter Media Downloader | HD Video und Bilder // @name:el 🚀 🎬 Βελτιωμένος Twitter Media Downloader | HD βίντεο και εικόνες // @name:en 🚀 🎬 Enhanced Twitter Media Downloader | HD Video&Image Download // @name:eo 🚀 🎬 Plibonigita Twitter Media Downloader | HD video kaj bildoj // @name:es 🚀 🎬 Descargador Mejorado de Twitter | Descarga HD de Video e Imagen // @name:es-419 🚀 🎬 Descargador Mejorado de Twitter | Descarga HD de Video e Imagen // @name:fi 🚀 🎬 Parannettu Twitter Media Downloader | HD video ja kuvat // @name:fr 🚀 🎬 Téléchargeur Twitter Amélioré | Vidéo et Image HD // @name:fr-CA 🚀 🎬 Téléchargeur Twitter Amélioré | Vidéo et Image HD // @name:he 🚀 🎬 מורד מדיה מטוויטר משופר | וידאו ותמונה באיכות HD // @name:hr 🚀 🎬 Poboljšani Twitter Media Downloader | HD video i slike // @name:hu 🚀 🎬 Fejlett Twitter Media Downloader | HD videó és képek // @name:id 🚀 🎬 Twitter Media Downloader Ditingkatkan | Video dan Gambar HD // @name:it 🚀 🎬 Twitter Media Downloader Migliorato | Video e Immagini HD // @name:ja 🚀 🎬 Enhanced Twitter メディアダウンローダー | HD動画・画像ダウンロード // @name:ka 🚀 🎬 გაუმჯობესებული Twitter Media Downloader | HD ვიდეო და სურათები // @name:ko 🚀 🎬 향상된 Twitter 미디어 다운로더 | HD 비디오 및 이미지 // @name:nb 🚀 🎬 Forbedret Twitter Media Downloader | HD video og bilder // @name:nl 🚀 🎬 Verbeterde Twitter Media Downloader | HD video en afbeeldingen // @name:pl 🚀 🎬 Ulepszony Twitter Media Downloader | HD wideo i obrazy // @name:pt-BR 🚀 🎬 Baixador Aprimorado do Twitter | Download HD de Vídeo e Imagem // @name:ro 🚀 🎬 Twitter Media Downloader Îmbunătățit | Video și imagini HD // @name:ru 🚀 🎬 Улучшенный Twitter Media Downloader | HD видео и изображения // @name:sk 🚀 🎬 Vylepšený Twitter Media Downloader | HD video a obrázky // @name:sr 🚀 🎬 Poboljšani Twitter Media Downloader | HD video i slike // @name:sv 🚀 🎬 Förbättrad Twitter Media Downloader | HD video och bilder // @name:th 🚀 🎬 Twitter Media Downloader ที่ปรับปรุงแล้ว | วิดีโอและรูปภาพ HD // @name:tr 🚀 🎬 Geliştirilmiş Twitter Medya İndirici | HD Video ve Resim // @name:uk 🚀 🎬 Покращений Twitter Media Downloader | HD відео та зображення // @name:ug 🚀 🎬 تەرەققىي قىلغان Twitter Media Downloader | HD سىن ۋە رەسىم // @name:vi 🚀 🎬 Trình Tải Twitter Media Nâng Cao | Video và Hình Ảnh HD // @namespace http://tampermonkey.net/ // @version 1.2 // @description 🎬 Twitter/X平台专业媒体下载器!📱 一键下载高清视频和图片,支持推文时间线、视频播放器、图片模态框多位置下载按钮 🔥 自动获取最高画质,批量下载,完全免费无广告 ✨ // @description:ar 🎬 منزل وسائط Twitter/X المهني! 📱 تحميل بنقرة واحدة لمقاطع الفيديو والصور عالية الدقة مع أزرار التحميل في الجدول الزمني للتغريدات ومشغل الفيديو ونافذة الصورة 🔥 الحصول التلقائي على أعلى جودة، دعم التحميل المجمع، مجاني تماماً بدون إعلانات ✨ // @description:bg 🎬 Професионален изтеглящ за Twitter/X медия! 📱 Едно кликване за изтегляне на HD видеоклипове и изображения с бутони за изтегляне в хронологията на туитовете, видео плейъра и модалното изображение 🔥 Автоматично получаване на най-високо качество, поддръжка за пакетно изтегляне, напълно безплатно и без реклами ✨ // @description:ckb 🎬 داگرتنی پیشەیی میدیای Twitter/X! 📱 داگرتنی یەک کلیک بۆ ڤیدیۆ و وێنەکانی HD لەگەڵ دوگمەکانی داگرتن لە کاتی تویتەکان، لێدەری ڤیدیۆ و مۆدالی وێنە 🔥 بەدەستهێنانی ئۆتۆماتیکی باڵاترین کوالیتی، پشتگیری داگرتنی کۆمەڵایەتی، تەواو خۆڕایی و بەبێ ڕیکلام ✨ // @description:cs 🎬 Profesionální stahování médií Twitter/X! 📱 Jedno kliknutí ke stažení HD videí a obrázků s tlačítky stahování na časové ose tweetů, video přehrávači a modálním okně obrázku 🔥 Automatické získání nejvyšší kvality, podpora dávkového stahování, zcela zdarma bez reklam ✨ // @description:da 🎬 Professionel Twitter/X mediedownloader! 📱 Et-klik download af HD-videoer og billeder med download-knapper i tweet-timeline, videoafspiller og billedmodal 🔥 Automatisk hentning af højeste kvalitet, understøttelse af batch-download, helt gratis uden annoncer ✨ // @description:de 🎬 Professioneller Twitter/X Medien-Downloader! 📱 Ein-Klick-Download von HD-Videos und Bildern mit Download-Buttons in Tweet-Timeline, Video-Player und Bild-Modal 🔥 Automatische Erfassung höchster Qualität, Batch-Download-Unterstützung, völlig kostenlos ohne Werbung ✨ // @description:el 🎬 Επαγγελματικός downloader μέσων Twitter/X! 📱 Λήψη με ένα κλικ HD βίντεο και εικόνων με κουμπιά λήψης στη χρονοσειρά tweet, βίντεο player και modal εικόνας 🔥 Αυτόματη λήψη υψηλότερης ποιότητας, υποστήριξη batch λήψης, εντελώς δωρεάν χωρίς διαφημίσεις ✨ // @description:en 🎬 Professional Twitter/X media downloader! 📱 One-click download HD videos and images with download buttons in tweet timeline, video player, and image modal 🔥 Auto-fetch highest quality, batch download support, completely free & no ads ✨ // @description:eo 🎬 Profesia Twitter/X aŭdvidaĵa elŝutilo! 📱 Unu-klaka elŝuto de HD videoj kaj bildoj kun elŝutaj butonoj en tweet-kronologio, video-ludilo kaj bild-modalo 🔥 Aŭtomata akiro de plej alta kvalito, subteno por amasa elŝuto, tute senpaga sen reklamoj ✨ // @description:es 🎬 ¡Descargador profesional de medios de Twitter/X! 📱 Descarga con un clic videos HD e imágenes con botones de descarga en línea de tiempo de tweets, reproductor de video y modal de imagen 🔥 Obtención automática de máxima calidad, soporte de descarga por lotes, completamente gratis sin anuncios ✨ // @description:es-419 🎬 ¡Descargador profesional de medios de Twitter/X! 📱 Descarga con un clic videos HD e imágenes con botones de descarga en línea de tiempo de tweets, reproductor de video y modal de imagen 🔥 Obtención automática de máxima calidad, soporte de descarga por lotes, completamente gratis sin anuncios ✨ // @description:fi 🎬 Ammattimainen Twitter/X median latausohjelma! 📱 Yhden klikkauksen HD-videoiden ja kuvien lataus latausnappe illa tweet-aikajanalla, videosoittimessa ja kuvamodaalissa 🔥 Automaattinen korkein laadun haku, erälataustuki, täysin ilmainen ilman mainoksia ✨ // @description:fr 🎬 Téléchargeur professionnel de médias Twitter/X ! 📱 Téléchargement en un clic de vidéos HD et d'images avec des boutons de téléchargement dans la timeline des tweets, le lecteur vidéo et la modale d'image 🔥 Récupération automatique de la plus haute qualité, support de téléchargement par lots, entièrement gratuit sans publicités ✨ // @description:fr-CA 🎬 Téléchargeur professionnel de médias Twitter/X ! 📱 Téléchargement en un clic de vidéos HD et d'images avec des boutons de téléchargement dans la chronologie des tweets, le lecteur vidéo et la fenêtre modale d'image 🔥 Récupération automatique de la plus haute qualité, support de téléchargement par lots, entièrement gratuit sans publicités ✨ // @description:he 🎬 מורד מדיה מקצועי של Twitter/X! 📱 הורדה בלחיצה אחת של סרטוני HD ותמונות עם כפתורי הורדה בציר הזמן של הטוויטים, נגן הווידאו ומודל התמונה 🔥 קבלה אוטומטית של איכות גבוהה ביותר, תמיכה בהורדה קבוצתית, חינם לחלוטין ללא פרסומות ✨ // @description:hr 🎬 Profesionalni Twitter/X preuzimatelja medija! 📱 Jednim klikom preuzmite HD videozapise i slike s gumbovima za preuzimanje u vremenskoj liniji tweetova, video playeru i slika modalu 🔥 Automatsko dohvaćanje najviše kvalitete, podrška za grupno preuzimanje, potpuno besplatno bez oglasa ✨ // @description:hu 🎬 Professzionális Twitter/X média letöltő! 📱 Egy kattintással tölts le HD videókat és képeket letöltési gombokkal a tweet idővonalon, videó lejátszóban és kép modálban 🔥 Automatikus legmagasabb minőség lekérés, tömeges letöltés támogatás, teljesen ingyenes reklámok nélkül ✨ // @description:id 🎬 Pengunduh media Twitter/X profesional! 📱 Unduh video HD dan gambar sekali klik dengan tombol unduh di timeline tweet, pemutar video, dan modal gambar 🔥 Pengambilan otomatis kualitas tertinggi, dukungan unduh batch, sepenuhnya gratis tanpa iklan ✨ // @description:it 🎬 Downloader professionale di media Twitter/X! 📱 Download con un clic di video HD e immagini con pulsanti di download nella timeline dei tweet, lettore video e modale immagine 🔥 Recupero automatico della massima qualità, supporto per download in batch, completamente gratuito senza pubblicità ✨ // @description:ja 🎬 プロフェッショナルなTwitter/Xメディアダウンローダー!📱 ツイートタイムライン、動画プレーヤー、画像モーダルにダウンロードボタンでHD動画・画像をワンクリックダウンロード 🔥 最高画質自動取得、一括ダウンロード対応、完全無料・広告なし ✨ // @description:ka 🎬 პროფესიონალური Twitter/X მედია ჩამოტვირთვა! 📱 ერთი წკაპით HD ვიდეოებისა და სურათების ჩამოტვირთვა ჩამოტვირთვის ღილაკებით tweet ზოლზე, ვიდეო დაკვრაში და სურათის მოდალში 🔥 ავტომატური უმაღლესი ხარისხის მიღება, ნაკრები ჩამოტვირთვის მხარდაჭერა, სრულიად უფასო რეკლამების გარეშე ✨ // @description:ko 🎬 전문 Twitter/X 미디어 다운로더! 📱 트윗 타임라인, 비디오 플레이어, 이미지 모달에서 다운로드 버튼으로 HD 비디오와 이미지를 원클릭 다운로드 🔥 최고 품질 자동 가져오기, 일괄 다운로드 지원, 완전 무료 및 광고 없음 ✨ // @description:nb 🎬 Profesjonell Twitter/X medianedlaster! 📱 Ett-klikks nedlasting av HD-videoer og bilder med nedlastingsknapper i tweet-tidslinje, videospiller og bildemodal 🔥 Automatisk henting av høyeste kvalitet, støtte for batch-nedlasting, helt gratis uten annonser ✨ // @description:nl 🎬 Professionele Twitter/X media downloader! 📱 Eén-klik download van HD video's en afbeeldingen met download knoppen in tweet tijdlijn, videospeler en afbeelding modal 🔥 Automatisch ophalen van hoogste kwaliteit, batch download ondersteuning, volledig gratis zonder advertenties ✨ // @description:pl 🎬 Profesjonalny pobieracz mediów Twitter/X! 📱 Jednym kliknięciem pobierz filmy HD i obrazy z przyciskami pobierania na osi czasu tweetów, odtwarzaczu wideo i modalnym obrazie 🔥 Automatyczne pobieranie najwyższej jakości, wsparcie dla pobierania wsadowego, całkowicie za darmo bez reklam ✨ // @description:pt-BR 🎬 Baixador profissional de mídia do Twitter/X! 📱 Download com um clique de vídeos HD e imagens com botões de download na timeline de tweets, player de vídeo e modal de imagem 🔥 Obtenção automática da qualidade máxima, suporte a download em lote, totalmente gratuito sem anúncios ✨ // @description:ro 🎬 Descărcător profesional de media Twitter/X! 📱 Descărcare cu un clic a videoclipurilor HD și imaginilor cu butoane de descărcare în cronologia tweet-urilor, playerul video și modalul imaginii 🔥 Obținerea automată a celei mai înalte calități, suport pentru descărcare în lot, complet gratuit fără reclame ✨ // @description:ru 🎬 Профессиональный загрузчик медиа Twitter/X! 📱 Загрузка HD видео и изображений одним кликом с кнопками загрузки в ленте твитов, видеоплеере и модальном окне изображения 🔥 Автоматическое получение высочайшего качества, поддержка пакетной загрузки, полностью бесплатно без рекламы ✨ // @description:sk 🎬 Profesionálny sťahovač médií Twitter/X! 📱 Jedným kliknutím si stiahnite HD videá a obrázky s tlačidlami sťahovania na časovej osi tweetov, video prehrávači a modálnom obrázku 🔥 Automatické získavanie najvyššej kvality, podpora dávkového sťahovania, úplne zadarmo bez reklám ✨ // @description:sr 🎬 Profesionalni Twitter/X preuzimač medija! 📱 Jednim klikom preuzmite HD video zapise i slike sa dugmićima za preuzimanje u vremenskoj liniji tweetova, video pleeru i modalnoj slici 🔥 Automatsko dobijanje najvišeg kvaliteta, podrška za grupno preuzimanje, potpuno besplatno bez reklama ✨ // @description:sv 🎬 Professionell Twitter/X medianedladdare! 📱 Enklicks nedladdning av HD-videor och bilder med nedladdningsknappar i tweet-tidslinje, videospelare och bildmodal 🔥 Automatisk hämtning av högsta kvalitet, stöd för batch-nedladdning, helt gratis utan annonser ✨ // @description:th 🎬 โปรแกรมดาวน์โหลดสื่อ Twitter/X แบบมืออาชีพ! 📱 ดาวน์โหลดวิดีโอ HD และรูปภาพด้วยการคลิกเดียวพร้อมปุ่มดาวน์โหลดในไทม์ไลน์ทวีต เครื่องเล่นวิดีโอ และโมดัลรูปภาพ 🔥 ดึงข้อมูลคุณภาพสูงสุดอัตโนมัติ รองรับการดาวน์โหลดแบบกลุ่ม ฟรีอย่างสมบูรณ์ไม่มีโฆษณา ✨ // @description:tr 🎬 Profesyonel Twitter/X medya indirici! 📱 Tweet zaman çizelgesi, video oynatıcı ve resim modalında indirme düğmeleri ile tek tıkla HD video ve resim indirme 🔥 Otomatik en yüksek kalite getirme, toplu indirme desteği, tamamen ücretsiz ve reklamasız ✨ // @description:uk 🎬 Професійний завантажувач медіа Twitter/X! 📱 Завантаження HD відео та зображень одним кліком з кнопками завантаження в стрічці твітів, відеоплеєрі та модальному зображенні 🔥 Автоматичне отримання найвищої якості, підтримка пакетного завантаження, повністю безкоштовно без реклами ✨ // @description:ug 🎬 كەسپىي Twitter/X ۋاسىتە چۈشۈرگۈچ! 📱 تىۋىت ۋاقىت ئىزى، سىن قويغۇچ ۋە رەسىم موداللىرىدا چۈشۈرۈش كۇنۇپكىلىرى بىلەن بىر چېكىش ئارقىلىق HD سىن ۋە رەسىملەرنى چۈشۈرۈڭ 🔥 ئاپتوماتىك ئەڭ يۇقىرى سۈپەت ئېلىش، توپ چۈشۈرۈش قوللاش، تولۇق ھەقسىز ۋە ئېلانسىز ✨ // @description:vi 🎬 Trình tải xuống phương tiện Twitter/X chuyên nghiệp! 📱 Tải xuống video HD và hình ảnh bằng một cú nhấp chuột với các nút tải xuống trong dòng thời gian tweet, trình phát video và modal hình ảnh 🔥 Tự động lấy chất lượng cao nhất, hỗ trợ tải xuống hàng loạt, hoàn toàn miễn phí không có quảng cáo ✨ // @author YouhouLab // @license MIT // @match *://*.twitter.com/* // @match *://*.x.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=twitter.com // @grant none // @run-at document-start // @compatible chrome // @compatible firefox // @compatible opera // @compatible edge // @compatible safari // @keywords twitter, x, download, video, image, media, hd, 1080p, 2k, 4k, free, no ads, batch download, enhanced, 推特, 媒体, 下载, 视频, 图片, 高清, 免费, 无广告, 批量下载, ツイッター, メディア, ダウンロード, 動画, 画像, 高画質, 無料, 广告なし, descarga, video, imagen, gratis, sin anuncios, baixar, vídeo, imagem, gratuito, sem anúncios, 🚀, 🎬, 📱, 🔥, ✨, 📺, 🖼️, 💾, ⬇️, 🎯 // ==/UserScript== /** * Enhanced Twitter媒体下载器 - 完整版 🚀 * 基于原有脚本增强,支持多位置下载按钮集成 */ class EnhancedTwitterMediaDownloader { constructor() { this.mediaStore = []; // 存储从API中提取的媒体信息 this.init(); } /** * 初始化下载器 */ init() { this.interceptXMLHttpRequest(); this.setupAllObservers(); this.injectStyles(); } /** * 设置所有观察器 */ setupAllObservers() { this.observeTweetElements(); // 推特动态按钮 this.observeVideoElements(); // 视频控制按钮 this.observeImageModal(); // 图片放大水印按钮 } /** * 拦截XMLHttpRequest,监听Twitter API响应 */ interceptXMLHttpRequest() { const originalOpen = XMLHttpRequest.prototype.open; const self = this; XMLHttpRequest.prototype.open = function(method, url) { // 检查是否是Twitter API请求 if (/(api\.)?(twitter|x)\.com\/(i\/api\/)?(2|graphql|1\.1)\//i.test(url)) { const originalSend = this.send; this.send = function() { const originalOnReadyStateChange = this.onreadystatechange; this.onreadystatechange = function() { // 请求完成时解析响应 if (this.readyState === XMLHttpRequest.DONE && this.responseText) { try { const responseData = JSON.parse(this.responseText); self.extractMediaFromResponse(responseData); } catch (error) { console.error('解析API响应失败:', error); } } // 调用原始回调 if (originalOnReadyStateChange) { return originalOnReadyStateChange.apply(this, arguments); } }; return originalSend.apply(this, arguments); }; } return originalOpen.apply(this, arguments); }; } /** * 从API响应中提取媒体信息 */ extractMediaFromResponse(data) { try { // 查找所有包含扩展实体的对象 const entitiesWithMedia = this.findObjectsWithProperty(data, 'extended_entities'); // 查找包含字符串值的对象(可能包含媒体信息) const stringValues = this.findObjectsWithProperty(data, 'string_value'); // 解析字符串值中的媒体信息 const parsedStringMedia = stringValues .map(obj => this.parseStringValueMedia(obj.string_value)) .filter(Boolean); // 合并所有媒体源 const allMediaSources = [...entitiesWithMedia, ...parsedStringMedia]; // 提取媒体信息 const mediaItems = this.extractMediaItems(allMediaSources); if (mediaItems.length > 0) { this.mediaStore.push(...mediaItems); console.log('提取到媒体信息:', mediaItems); } } catch (error) { console.error('提取媒体信息失败:', error); } } /** * 递归查找包含指定属性的对象 */ findObjectsWithProperty(obj, property, results = []) { if (!obj || typeof obj !== 'object') return results; if (obj.hasOwnProperty(property)) { results.push(obj); } else { Object.values(obj).forEach(value => { results.push(...this.findObjectsWithProperty(value, property)); }); } return results; } /** * 解析字符串值中的媒体信息 */ parseStringValueMedia(stringValue) { try { const parsed = JSON.parse(stringValue); const mediaEntities = Object.values(parsed.media_entities || {}); const mediaEntity = mediaEntities.find(entity => this.isValidMediaType(entity)); if (mediaEntity) { return { extended_entities: { media: [mediaEntity] }, id_str: mediaEntity.id_str }; } } catch (error) { // 解析失败,忽略此项 } return null; } /** * 检查是否是有效的媒体类型 */ isValidMediaType(entity) { return entity && ['video', 'animated_gif', 'photo'].includes(entity.type); } /** * 从媒体源中提取媒体项 */ extractMediaItems(mediaSources) { return mediaSources .filter(source => { const mediaArray = source.extended_entities?.media || []; return mediaArray.some(media => this.isValidMediaType(media)); }) .flatMap(source => { const entityId = source.id_str || source.conversation_id_str; const mediaArray = source.extended_entities.media.filter(media => this.isValidMediaType(media)); return mediaArray.map(media => ({ id: media.id_str, entityId: entityId, thumbnail: this.getThumbnailUrl(media), photo: this.getPhotoUrl(media), video: this.getVideoUrl(media), text: this.generateDisplayText(source) })); }) .filter((item, index, array) => { // 去重 return array.findIndex(other => other.id === item.id) === index; }); } /** * 获取缩略图URL */ getThumbnailUrl(media) { const url = media.media_url_https; return url.substr(0, url.lastIndexOf('.')); } /** * 获取图片URL */ getPhotoUrl(media) { const url = media.media_url_https; const hasLargeSize = media.sizes?.large; return url + (hasLargeSize ? ':large' : ''); } /** * 获取视频URL(最高质量) */ getVideoUrl(media) { const variants = media.video_info?.variants; if (!variants) return null; const mp4Variants = variants .filter(variant => variant.content_type === 'video/mp4') .sort((a, b) => b.bitrate - a.bitrate); return mp4Variants[0]?.url || null; } /** * 生成显示文本 */ generateDisplayText(source) { const id = source.id_str || source.conversation_id_str; if (!source.full_text) return id; let text = source.full_text .split('https://t.co')[0] .trim() .replace(/(\r\n|\n|\r)/gm, '') .substr(0, 50); return text || id; } // ================================= // 推特动态按钮集成 // ================================= observeTweetElements() { const tweetObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node instanceof HTMLElement) { // 检查推特动态 const articles = node.querySelectorAll('article[role="article"]'); articles.forEach(article => this.handleTweetElement(article)); // 如果节点本身是article if (node.matches('article[role="article"]')) { this.handleTweetElement(node); } } }); }); }); tweetObserver.observe(document, { childList: true, subtree: true }); } handleTweetElement(articleElement) { // 延迟处理,等待元素完全渲染 setTimeout(() => { // 检查是否包含媒体 const mediaElements = articleElement.querySelectorAll('img[src*="pbs.twimg.com"], video'); if (mediaElements.length === 0) return; // 查找互动按钮区域 - 使用XPath提供的精确路径逻辑 const buttonGroups = articleElement.querySelectorAll('div[role="group"]'); const actionGroup = buttonGroups[buttonGroups.length - 1]; if (!actionGroup) return; // 检查是否已添加 if (actionGroup.querySelector('.tweet-download-btn')) return; // 查找对应的媒体数据 const mediaItem = this.findMediaForTweet(articleElement); if (!mediaItem) return; // 添加下载按钮 this.addTweetDownloadButton(actionGroup, mediaItem); }, 1000); } addTweetDownloadButton(actionGroup, mediaItem) { const downloadButton = document.createElement('div'); downloadButton.className = 'tweet-download-btn'; downloadButton.setAttribute('role', 'button'); downloadButton.setAttribute('aria-label', '下载媒体'); downloadButton.setAttribute('tabindex', '0'); // 获取其他按钮的样式参考 const otherButtons = actionGroup.querySelectorAll('div[role="button"]'); const referenceButton = otherButtons[0]; // 设置圆形按钮样式 - CSS类会覆盖这些基础样式 downloadButton.innerHTML = ` <div> <svg class="download-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"> <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"/> </svg> <svg class="loading-icon" width="20" height="20" viewBox="0 0 32 32" style="display:none;"> <circle cx="16" cy="16" fill="none" r="14" stroke-width="4" stroke="currentColor" opacity="0.3"/> <circle cx="16" cy="16" fill="none" r="14" stroke-width="4" stroke="currentColor" stroke-dasharray="87.96" stroke-dashoffset="87.96"> <animate attributeName="stroke-dashoffset" dur="2s" values="87.96;0" repeatCount="indefinite"/> </circle> </svg> <svg class="success-icon" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style="display:none;"> <path d="M9 20c-.264 0-.52-.104-.707-.293l-4.785-4.785c-.39-.39-.39-1.023 0-1.414s1.023-.39 1.414 0l3.946 3.945L18.075 4.11c.32-.45.94-.558 1.395-.24.45.318.56.942.24 1.394L9.817 19.577c-.17.24-.438.395-.732.42-.028.002-.057.003-.085.003z"/> </svg> </div> `; // 悬停效果通过CSS处理,这里移除JavaScript处理 downloadButton.addEventListener('click', async (e) => { e.preventDefault(); e.stopPropagation(); // 立即移除焦点,避免圆环残留 downloadButton.blur(); await this.handleDownload(e, downloadButton, mediaItem); }); actionGroup.appendChild(downloadButton); } // ================================= // 视频控制按钮集成 // ================================= observeVideoElements() { const videoObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node instanceof HTMLElement) { // 检查视频元素 const videos = node.querySelectorAll('video'); videos.forEach(video => this.handleVideoElement(video)); // 如果节点本身是视频 if (node.tagName === 'VIDEO') { this.handleVideoElement(node); } } }); }); }); videoObserver.observe(document, { childList: true, subtree: true }); } handleVideoElement(videoElement) { // 等待视频加载完成 const addButton = () => { setTimeout(() => { this.addVideoDownloadButton(videoElement); }, 2000); // 等待控制栏渲染 }; if (videoElement.readyState >= 1) { addButton(); } else { videoElement.addEventListener('loadedmetadata', addButton); } // 也监听用户交互后的控制栏显示 videoElement.addEventListener('play', addButton); videoElement.addEventListener('pause', addButton); } addVideoDownloadButton(videoElement) { const controlBar = this.findVideoControlBar(videoElement); if (!controlBar) return; // 检查是否已添加 if (controlBar.querySelector('.video-download-btn')) return; // 查找对应的媒体数据 const mediaItem = this.findMediaForVideo(videoElement); if (!mediaItem || !mediaItem.video) return; // 创建下载按钮 const downloadButton = this.createVideoDownloadButton(mediaItem); // 查找设置按钮并插入到其旁边 - 基于提供的XPath逻辑 const settingsButton = controlBar.querySelector('button[aria-label*="设置"], button[aria-label*="Settings"], button[aria-label*="更多"]'); if (settingsButton && settingsButton.parentElement) { settingsButton.parentElement.insertBefore(downloadButton, settingsButton.nextSibling); } else { // 如果找不到设置按钮,添加到控制栏末尾 controlBar.appendChild(downloadButton); } } findVideoControlBar(videoElement) { // 向上查找视频容器 let container = videoElement.parentElement; while (container && !container.matches('div[data-testid="videoComponent"], div[data-testid="videoPlayer"], div[role="group"]')) { container = container.parentElement; if (container === document.body) return null; } if (!container) return null; // 在容器中查找控制栏 - 通常是包含按钮的div const controlBars = container.querySelectorAll('div[role="group"], div:has(button)'); // 选择包含最多按钮的div作为控制栏 let bestCandidate = null; let maxButtons = 0; controlBars.forEach(bar => { const buttonCount = bar.querySelectorAll('button').length; if (buttonCount > maxButtons) { maxButtons = buttonCount; bestCandidate = bar; } }); return bestCandidate; } createVideoDownloadButton(mediaItem) { const button = document.createElement('button'); button.className = 'video-download-btn'; button.setAttribute('aria-label', '下载视频'); // 基础样式,圆形样式由CSS类处理 button.style.cssText = ` background: none; border: none; cursor: pointer; color: white; `; button.innerHTML = ` <div> <svg class="download-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"> <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"/> </svg> <svg class="loading-icon" width="20" height="20" viewBox="0 0 32 32" style="display:none;"> <circle cx="16" cy="16" fill="none" r="14" stroke-width="4" stroke="currentColor" opacity="0.3"/> <circle cx="16" cy="16" fill="none" r="14" stroke-width="4" stroke="currentColor" stroke-dasharray="87.96" stroke-dashoffset="87.96"> <animate attributeName="stroke-dashoffset" dur="2s" values="87.96;0" repeatCount="indefinite"/> </circle> </svg> <svg class="success-icon" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style="display:none;"> <path d="M9 20c-.264 0-.52-.104-.707-.293l-4.785-4.785c-.39-.39-.39-1.023 0-1.414s1.023-.39 1.414 0l3.946 3.945L18.075 4.11c.32-.45.94-.558 1.395-.24.45.318.56.942.24 1.394L9.817 19.577c-.17.24-.438.395-.732.42-.028.002-.057.003-.085.003z"/> </svg> </div> `; // 悬停效果通过CSS处理,移除JavaScript处理 button.addEventListener('click', async (e) => { e.preventDefault(); e.stopPropagation(); // 立即移除焦点,避免圆环残留 button.blur(); await this.handleDownload(e, button, mediaItem); }); return button; } // ================================= // 图片放大水印按钮集成 // ================================= observeImageModal() { const modalObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node instanceof HTMLElement) { // 检查模态框 if (node.matches('div[aria-modal="true"]')) { this.handleImageModal(node); } // 检查子元素中的模态框 const modals = node.querySelectorAll('div[aria-modal="true"]'); modals.forEach(modal => this.handleImageModal(modal)); } }); }); }); modalObserver.observe(document, { childList: true, subtree: true }); } handleImageModal(modalElement) { setTimeout(() => { const images = modalElement.querySelectorAll('img[src*="pbs.twimg.com"]'); images.forEach(img => { this.addImageWatermarkButton(img); }); }, 500); // 等待图片渲染完成 } addImageWatermarkButton(imageElement) { // 检查是否已添加 const container = imageElement.closest('div'); if (!container || container.querySelector('.image-watermark-download')) return; // 查找对应的媒体数据 const mediaItem = this.mediaStore.find(item => imageElement.src.indexOf(item.thumbnail) > -1 || imageElement.src.indexOf(item.photo) > -1 ); if (!mediaItem) return; // 创建水印式下载按钮 const watermarkButton = document.createElement('div'); watermarkButton.className = 'image-watermark-download'; watermarkButton.setAttribute('role', 'button'); watermarkButton.setAttribute('aria-label', '下载图片'); watermarkButton.setAttribute('tabindex', '0'); watermarkButton.innerHTML = ` <div> <svg class="download-icon" width="20" height="20" viewBox="0 0 24 24"> <circle cx="12" cy="12" r="12" fill="rgba(0,0,0,0.6)"/> <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="white" stroke-width="2" stroke-linecap="round"/> </svg> <svg class="loading-icon" width="20" height="20" viewBox="0 0 32 32" style="display:none;"> <circle cx="16" cy="16" r="8" fill="rgba(0,0,0,0.6)"/> <circle cx="16" cy="16" fill="none" r="6" stroke-width="2" stroke="white" opacity="0.3"/> <circle cx="16" cy="16" fill="none" r="6" stroke-width="2" stroke="white" stroke-dasharray="37.7" stroke-dashoffset="37.7"> <animate attributeName="stroke-dashoffset" dur="2s" values="37.7;0" repeatCount="indefinite"/> </circle> </svg> <svg class="success-icon" width="20" height="20" viewBox="0 0 24 24" style="display:none;"> <circle cx="12" cy="12" r="10" fill="rgba(0,0,0,0.6)"/> <path d="M9 14.2L6.8 12l-1.4 1.4L9 17 19 7l-1.4-1.4L9 14.2z" fill="white"/> </svg> </div> `; // 样式设置 - 基础样式,详细圆形样式由CSS类处理 Object.assign(watermarkButton.style, { position: 'absolute', top: '16px', right: '16px', cursor: 'pointer', zIndex: '1000', opacity: '0.8', transition: 'opacity 0.2s' }); // 悬停效果通过CSS处理 // 点击下载 watermarkButton.addEventListener('click', async (e) => { e.preventDefault(); e.stopPropagation(); // 立即移除焦点,避免圆环残留 watermarkButton.blur(); await this.handleDownload(e, watermarkButton, mediaItem); }); // 确保图片容器有相对定位 const imageContainer = imageElement.closest('div'); if (imageContainer) { const computedStyle = getComputedStyle(imageContainer); if (computedStyle.position === 'static') { imageContainer.style.position = 'relative'; } imageContainer.appendChild(watermarkButton); } } // ================================= // 通用辅助方法 // ================================= findMediaForVideo(videoElement) { // 通过视频URL或容器查找对应的媒体数据 const videoSrc = videoElement.src || videoElement.currentSrc; if (!videoSrc) return null; return this.mediaStore.find(item => { if (!item.video) return false; // 提取视频ID用于匹配 const videoId = videoSrc.match(/\/(\d+)\//)?.[1]; const itemVideoId = item.video.match(/\/(\d+)\//)?.[1]; return videoId && itemVideoId && videoId === itemVideoId; }); } findMediaForTweet(articleElement) { // 通过推文中的图片找到对应的媒体数据 const images = articleElement.querySelectorAll('img[src*="pbs.twimg.com"]'); for (const img of images) { const mediaItem = this.mediaStore.find(item => img.src.indexOf(item.thumbnail) > -1 ); if (mediaItem) return mediaItem; } return null; } /** * 处理下载事件 */ async handleDownload(event, button, mediaItem) { event.preventDefault(); event.stopImmediatePropagation(); button.disabled = true; this.setButtonState(button, 'loading'); try { // 获取同一条推文的所有媒体 const relatedMedia = this.mediaStore.filter(item => item.entityId === mediaItem.entityId ); const downloadedUrls = new Set(); // 下载所有相关媒体 for (const media of relatedMedia) { const downloadUrl = media.video || media.photo; if (downloadUrl && !downloadedUrls.has(downloadUrl)) { await this.downloadFile(downloadUrl, media.text); downloadedUrls.add(downloadUrl); } } this.setButtonState(button, 'success'); } catch (error) { console.error('下载失败:', error); this.setButtonState(button, 'error'); } } /** * 设置按钮状态 */ setButtonState(button, state) { const downloadIcon = button.querySelector('.download-icon'); const loadingIcon = button.querySelector('.loading-icon'); const successIcon = button.querySelector('.success-icon'); // 隐藏所有图标 [downloadIcon, loadingIcon, successIcon].forEach(icon => { if (icon) icon.style.display = 'none'; }); // 显示对应状态的图标 switch (state) { case 'loading': if (loadingIcon) loadingIcon.style.display = 'block'; break; case 'success': if (successIcon) successIcon.style.display = 'block'; setTimeout(() => { button.disabled = false; if (downloadIcon) downloadIcon.style.display = 'block'; if (successIcon) successIcon.style.display = 'none'; }, 2000); break; case 'error': default: if (downloadIcon) downloadIcon.style.display = 'block'; button.disabled = false; break; } } /** * 下载文件 */ async downloadFile(url, filename) { try { const response = await fetch(url); const blob = await response.blob(); const contentType = response.headers.get('Content-Type'); // 确定文件扩展名 const extension = this.getFileExtension(contentType); // 创建下载链接 const downloadLink = document.createElement('a'); downloadLink.style.display = 'none'; downloadLink.href = window.URL.createObjectURL(blob); downloadLink.setAttribute('target', '_blank'); downloadLink.setAttribute('download', `${filename}.${extension}`); // 触发下载 document.body.appendChild(downloadLink); downloadLink.click(); // 清理 window.URL.revokeObjectURL(downloadLink.href); document.body.removeChild(downloadLink); } catch (error) { console.error('文件下载失败:', error); throw error; } } /** * 根据MIME类型获取文件扩展名 */ getFileExtension(contentType) { switch (contentType) { case 'image/jpeg': return 'jpg'; case 'image/png': return 'png'; case 'video/mp4': return 'mp4'; default: return 'mp4'; // 默认为mp4 } } /** * 注入样式 */ injectStyles() { const style = document.createElement('style'); style.textContent = ` /* 推特动态下载按钮样式 - 圆形设计 */ .tweet-download-btn { color: rgb(83, 100, 113); transition: all 0.2s ease-in-out; border-radius: 50% !important; width: 36px !important; height: 36px !important; min-width: 36px !important; min-height: 36px !important; display: flex !important; align-items: center !important; justify-content: center !important; background-color: transparent !important; border: none !important; cursor: pointer !important; outline: none !important; margin-top: -3px !important; } .tweet-download-btn:hover { color: rgb(29, 161, 242); background-color: rgba(29, 161, 242, 0.1) !important; transform: scale(1.05); } /* 视频下载按钮样式 - 圆形设计 */ .video-download-btn { border-radius: 50% !important; width: 35px !important; height: 35px !important; min-width: 35px !important; min-height: 35px !important; display: flex !important; align-items: center !important; justify-content: center !important; transition: all 0.2s ease-in-out !important; outline: none !important; margin-top: -3px !important; } .video-download-btn:hover { background-color: rgba(255, 255, 255, 0.15) !important; transform: scale(1.1) !important; } /* 图片水印下载按钮样式 - 圆形设计 */ .image-watermark-download { user-select: none; border-radius: 50% !important; width: 35px !important; height: 35px !important; display: flex !important; align-items: center !important; justify-content: center !important; transition: all 0.2s ease-in-out !important; outline: none !important; margin-top: -3px !important; } .image-watermark-download:hover { transform: scale(1.1) !important; opacity: 1 !important; } .image-watermark-download svg { border-radius: 50% !important; } /* 通用样式 */ .tweet-download-btn:disabled, .video-download-btn:disabled, .image-watermark-download:disabled { cursor: not-allowed; opacity: 0.7; transform: none !important; } /* 按钮内图标居中 */ .tweet-download-btn > div, .video-download-btn > div, .image-watermark-download > div { display: flex !important; align-items: center !important; justify-content: center !important; width: 100% !important; height: 100% !important; padding: 0 !important; } /* 加载动画 */ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .loading-icon { animation: spin 1s linear infinite; } /* 移除所有焦点和点击时的圆环效果 */ .tweet-download-btn:focus, .tweet-download-btn:active, .tweet-download-btn:focus-visible, .tweet-download-btn:focus-within, .video-download-btn:focus, .video-download-btn:active, .video-download-btn:focus-visible, .video-download-btn:focus-within, .image-watermark-download:focus, .image-watermark-download:active, .image-watermark-download:focus-visible, .image-watermark-download:focus-within { outline: none !important; border: none !important; box-shadow: none !important; -webkit-appearance: none !important; -moz-appearance: none !important; appearance: none !important; } /* 强制移除所有浏览器默认焦点样式 */ .tweet-download-btn *:focus, .tweet-download-btn *:active, .tweet-download-btn *:focus-visible, .video-download-btn *:focus, .video-download-btn *:active, .video-download-btn *:focus-visible, .image-watermark-download *:focus, .image-watermark-download *:active, .image-watermark-download *:focus-visible { outline: none !important; border: none !important; box-shadow: none !important; } `; document.head.appendChild(style); } } const enhancedDownloader = new EnhancedTwitterMediaDownloader();