YouTube 社区贴文图片下载

右键Youtube社区帖子来自动批量下载图片

// ==UserScript==
// @name           YouTube 社区贴文图片下载
// @name:en        YouTube Community Post Image Downloader
// @description:en Right-click on a YouTube community post to automatically batch download images
// @description    右键Youtube社区帖子来自动批量下载图片
// @version        1.0.1
// @author         kaesinol
// @match          https://*.youtube.com/*/posts
// @match          https://*.youtube.com/post/*
// @match          https://*.youtube.com/@*
// @match          https://*.youtube.com/channel/*/posts
// @grant          GM_download
// @license        MIT
// @namespace https://gf.qytechs.cn/users/1243515
// ==/UserScript==

(function () {
    'use strict';

    function downloadImage(url, filename) {
        GM_download({
            url: url,
            name: filename,
            onerror: err => console.error('Download failed:', err),
            ontimeout: () => console.warn('Download timeout:', url),
        });
    }

    function sanitizeFilename(name) {
        return name.replace(/[\\/:*?"<>|]+/g, '').slice(0, 100);
    }

    function getFileExtensionFromMime(mime) {
        const map = {
            'image/jpeg': 'jpg',
            'image/png': 'png',
            'image/webp': 'webp',
            'image/gif': 'gif'
        };
        return map[mime] || 'png'; // 默认 png
    }

    async function fetchMimeType(url) {
        try {
            const response = await fetch(url, { method: 'HEAD' });
            const mime = response.headers.get('Content-Type');
            return getFileExtensionFromMime(mime);
        } catch (e) {
            return 'png'; //fallback
        }
    }
    function getOriginalUrl(rawUrl) {
        if (!rawUrl) return rawUrl;
        const match = rawUrl.match(/(\S*?)-c-fcrop64=[^=]*/);
        let base = match ? match[1] : rawUrl;
        base = base.replace(/=s\d+/, '=s0');
        if (!base.includes('=s0')) base += '=s0';
        console.info(base);
        return base;

    }

    async function handleRightClick(event) {
        const container = event.currentTarget;

        // 找 #author-text 内的 href
        const authorLink = container.querySelector('#author-text');
        let authorHref = authorLink ? authorLink.getAttribute('href') || '' : '';
        authorHref = sanitizeFilename(authorHref || 'author');

        const imgs = container.querySelectorAll('#content-attachment img');
        const id = container.querySelector('a[href*="/post/"]').href.split('/post/')[1];
        imgs.forEach(async (img, i) => {
            const rawUrl = img.src || img.getAttribute('src');
            if (!rawUrl) return;
            const imgUrl = getOriginalUrl(rawUrl);
            const ext = await fetchMimeType(imgUrl);
            const filename = `${authorHref} - ${id} - ${i + 1}.${ext}`;
            console.info(filename);
            downloadImage(imgUrl, filename);
        });
        event.preventDefault();
    }

    function bindContextMenuEvents() {
        const posts = document.querySelectorAll('#body.style-scope.ytd-backstage-post-renderer');
        posts.forEach(post => {
            post.removeEventListener('contextmenu', handleRightClick);
            post.addEventListener('contextmenu', handleRightClick);
        });
    }

    const observer = new MutationObserver(bindContextMenuEvents);
    observer.observe(document.body, { childList: true, subtree: true });

    bindContextMenuEvents();

})();

QingJ © 2025

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