Twitter Image Downloader

Download images from Twitter posts and pack them into a ZIP archive with metadata.

目前為 2024-08-12 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Twitter Image Downloader
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Download images from Twitter posts and pack them into a ZIP archive with metadata.
// @author       Dramorian
// @license      MIT
// @match        https://twitter.com/*
// @match        https://x.com/*
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Add a download button to the tweet actions bar
    function addDownloadButton(tweetElement) {
        // Check if button already exists
        if (tweetElement.querySelector('.download-images-btn')) return;

        const button = document.createElement('button');
        button.innerText = 'Download Images';
        button.className = 'download-images-btn';
        button.style.marginLeft = '10px';
        button.addEventListener('click', () => downloadImages(tweetElement));

        const actionBar = tweetElement.querySelector('[role="group"]');
        if (actionBar) {
            actionBar.appendChild(button);
        }
    }

    // Download images and metadata
    async function downloadImages(tweetElement) {
        const zip = new JSZip();
        const tweetLinkElement = tweetElement.querySelector('a[href*="/status/"]');
        const tweetLink = tweetLinkElement.href;
        const tweetParts = tweetLink.match(/https:\/\/(?:x\.com|twitter\.com)\/([^\/]+)\/status\/(\d+)/);
        const authorHandle = tweetParts[1];
        const tweetId = tweetParts[2];
        const authorComment = tweetElement.querySelector('div[lang]').innerText;
        const dateElement = tweetElement.querySelector('time');
        const postDateTime = dateElement ? new Date(dateElement.getAttribute('datetime')) : new Date();
        const imageElements = tweetElement.querySelectorAll('div.css-175oi2r a[href*="/photo/"]');

        // Metadata starts with the link to the tweet, followed by the author comment, handle, date and time, and links to each image
        let metadata = `${tweetLink}\n${authorComment}\n@${authorHandle}\n${postDateTime.toLocaleString()}\n`;

        // Add images to the zip
        let imageIndex = 1;
        for (const imgElement of imageElements) {
            let imgUrl = imgElement.querySelector('img').src;
            imgUrl = imgUrl.replace(/(?<=name=)[^&]+/, 'orig');
            const imgData = await fetch(imgUrl).then(res => res.blob());
            const imageName = `${authorHandle}_${tweetId}_${imageIndex}.jpg`;
            zip.file(imageName, imgData);

            // Append each image link to the metadata
            metadata += `https://x.com${imgElement.getAttribute('href')}\n`;
            imageIndex++;
        }

        // Add metadata files
        zip.file('metadata.txt', metadata);
        zip.file(`${authorHandle}_${tweetId}.url`, `[InternetShortcut]\nURL=${tweetLink}`);

        // Generate the zip file and trigger the download
        const content = await zip.generateAsync({ type: 'blob' });
        saveAs(content, `${authorHandle}_${tweetId}.zip`);
    }

    // Observe the DOM for new tweets and add the download button
    const observer = new MutationObserver(mutations => {
        for (const mutation of mutations) {
            for (const addedNode of mutation.addedNodes) {
                if (addedNode.nodeType === Node.ELEMENT_NODE) {
                    const tweetElements = addedNode.querySelectorAll('article');
                    tweetElements.forEach(addDownloadButton);
                }
            }
        }
    });

    observer.observe(document.body, { childList: true, subtree: true });
})();



QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址