淘宝评论

发表于 视频

// ==UserScript==
// @name         淘宝/天猫评论图集高清原图下载器【商品名随机码版】
// @namespace    tb-comment-hd-gallery-downloader
// @version      4.8
// @description  淘宝/天猫评论图下载:强力去重,右侧单图可删,按 商品名a7/01.jpg 保存
// @match        *://*.taobao.com/*
// @match        *://*.tmall.com/*
// @match        *://*.tmall.hk/*
// @match        *://*.world.taobao.com/*
// @grant        GM_download
// @connect      gw.alicdn.com
// @connect      img.alicdn.com
// @connect      alicdn.com
// ==/UserScript==

(function () {
    'use strict';

    const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

    const BLOCKED_URLS = [
        'https://img.alicdn.com/imgextra/i0/O1CN01GbZNxl26Vzotrjqli_!!6000000007668-2-tps-160-160.png'
    ];

    function fullUrl(url) {
        if (!url) return '';
        if (url.startsWith('//')) return 'https:' + url;
        return url;
    }

    function cleanName(name, maxLen = 255) {
        return (name || '未命名')
            .replace(/[\\/:*?"<>|]/g, '')
            .replace(/[\r\n\t]/g, '')
            .replace(/\s+/g, ' ')
            .trim()
            .slice(0, maxLen);
    }

    function randomTwoCode() {
        const chars = '0123456789abcdefghijklmnopqrstuvwxyz';
        return Array.from({ length: 2 }, () =>
            chars[Math.floor(Math.random() * chars.length)]
        ).join('');
    }

    function getRootFolderName() {
        const input = document.querySelector('#tb-folder-name-input');
        const value = input?.value?.trim() || '';
        return cleanName(value || '淘宝评论原图', 253);
    }

    function getSkuBaseName() {
        return cleanName(getRootFolderName(), 80).replace(/\s+/g, '');
    }

    function makeSkuFolderName() {
        const skuName = getSkuBaseName() || '商品';
        return cleanName(`${skuName}${randomTwoCode()}`, 255);
    }

    function normalizeUrl(url) {
        return fullUrl(url || '')
            .split('?')[0]
            .replace(/^http:/i, 'https:')
            .replace(/\/i\d+\//i, '/i0/');
    }

    function getHDImage(url) {
        if (!url) return '';

        url = normalizeUrl(url);

        const match = url.match(/.*?-(0|2)-(rate|tbbala)(?:_livephoto)?\.(jpg|jpeg|png|webp)/i);
        if (match) return match[0];

        const match2 = url.match(/.*?!!\d+-(rate|tbbala)(?:_livephoto)?\.(jpg|jpeg|png|webp)/i);
        if (match2) return match2[0];

        const imgMatch = url.match(/.*?\.(jpg|jpeg|png|webp)/i);
        if (imgMatch) return imgMatch[0];

        return url;
    }

    function getImageIdentity(url) {
        return normalizeUrl(getHDImage(url))
            .replace(/_(\d+)x(\d+)(q\d+)?\.(jpg|jpeg|png|webp)$/i, '.$4')
            .replace(/_\d+x\d+\.(jpg|jpeg|png|webp)$/i, '.$1')
            .replace(/\.sum\.(jpg|jpeg|png|webp)$/i, '.$1')
            .replace(/\.webp$/i, '.jpg')
            .replace(/\.jpeg$/i, '.jpg');
    }

    function getGroupIdentities(urls) {
        return [...new Set((urls || []).map(getImageIdentity).filter(Boolean))];
    }

    function getGroupKey(urls) {
        return getGroupIdentities(urls).sort().join('|');
    }

    function isSimilarGroup(urlsA, urlsB, threshold = 0.8) {
        const a = getGroupIdentities(urlsA);
        const b = getGroupIdentities(urlsB);

        if (!a.length || !b.length) return false;

        const setA = new Set(a);
        const setB = new Set(b);

        let same = 0;
        setA.forEach(item => {
            if (setB.has(item)) same++;
        });

        return same / Math.min(setA.size, setB.size) >= threshold;
    }

    function getImageSrc(img) {
        return img.getAttribute('data-src') ||
            img.getAttribute('src') ||
            img.currentSrc ||
            img.src ||
            '';
    }

    function isBlockedImageUrl(url) {
        const clean = normalizeUrl(url);
        return BLOCKED_URLS.some(blocked => clean === normalizeUrl(blocked));
    }

    function isValidImageUrl(url) {
        if (!url) return false;
        if (isBlockedImageUrl(url)) return false;
        if (url.includes('tps-145-145')) return false;
        if (url.includes('tps-56-56')) return false;
        if (url.includes('tps-160-160')) return false;
        if (url.includes('playerIcon')) return false;
        if (url.includes('6000000002189-2-tps')) return false;
        if (url.includes('6000000006294-2-tps')) return false;
        if (url.includes('6000000007668-2-tps-160-160')) return false;
        return /\.(jpg|jpeg|png|webp)$/i.test(url);
    }

    function isImageSizeOk(img) {
        const w = img.naturalWidth || img.width || 0;
        const h = img.naturalHeight || img.height || 0;
        return w >= 300 && h >= 300;
    }

    function getFileExt(url) {
        const match = url.match(/\.(jpg|jpeg|png|webp)$/i);
        return match ? match[1].toLowerCase() : 'jpg';
    }

    function getImages(container) {
        const imgs = [...container.querySelectorAll('img')];
        const seen = new Set();
        const result = [];

        imgs.forEach(img => {
            if (!isImageSizeOk(img)) return;

            const hdUrl = getHDImage(getImageSrc(img));
            if (!isValidImageUrl(hdUrl)) return;

            const identity = getImageIdentity(hdUrl);
            if (seen.has(identity)) return;

            seen.add(identity);
            result.push(hdUrl);
        });

        return result;
    }

    function getComments() {
        return [...document.querySelectorAll('[class*="Comment--"]')]
            .filter(el => {
                const imgs = getImages(el);
                return imgs.length > 0 && imgs.length <= 80;
            });
    }

    function getImageItems() {
        return [...document.querySelectorAll(
            '[class*="commentsImgItem"], [class*="photo--"], [class*="cover--"]'
        )];
    }

    function injectStyle() {
        if (document.querySelector('#tb-hd-style')) return;

        const style = document.createElement('style');
        style.id = 'tb-hd-style';

        style.innerHTML = `
            #tb-hd-download-panel {
                position: fixed;
                right: 18px;
                top: 18px;
                z-index: 999999999;
                width: 600px;
                max-height: calc(100vh - 36px);
                overflow: hidden;
                padding: 0;
                border-radius: 20px;
                background: rgba(255,255,255,.96);
                backdrop-filter: blur(18px);
                box-shadow: 0 18px 46px rgba(0,0,0,.14), 0 8px 24px rgba(255,80,0,.22);
                border: 1px solid rgba(255,80,0,.22);
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, "Microsoft YaHei", sans-serif;
                color: #222;
            }

            .tb-panel-top {
                display: flex;
                align-items: center;
                justify-content: space-between;
                gap: 12px;
                padding: 14px 16px;
                cursor: move;
                user-select: none;
                background: linear-gradient(135deg, #ff5000, #ff8a1c);
                color: #fff;
            }

            .tb-panel-title {
                display: flex;
                align-items: center;
                gap: 10px;
            }

            .tb-logo {
                width: 38px;
                height: 38px;
                border-radius: 13px;
                background: rgba(255,255,255,.22);
                color: #fff;
                display: flex;
                align-items: center;
                justify-content: center;
                font-weight: 900;
                font-size: 19px;
            }

            .tb-title-main {
                font-size: 17px;
                font-weight: 900;
                line-height: 1.2;
            }

            .tb-title-sub {
                font-size: 12px;
                opacity: .9;
                margin-top: 3px;
            }

            .tb-panel-body {
                padding: 14px 16px 16px;
                max-height: calc(100vh - 105px);
                overflow-y: auto;
                background: linear-gradient(180deg, #fff7f1 0%, #ffffff 120px);
            }

            .tb-folder-row {
                display: flex;
                gap: 8px;
                align-items: center;
                margin-bottom: 12px;
            }

            #tb-folder-name-input {
                flex: 1;
                height: 38px;
                border-radius: 13px;
                border: 1px solid rgba(255,80,0,.24);
                background: #fff;
                padding: 0 13px;
                font-size: 13px;
                outline: none;
                color: #333;
            }

            .tb-folder-preview {
                width: 150px;
                height: 38px;
                line-height: 38px;
                border-radius: 13px;
                background: #fff3eb;
                color: #ff5000;
                font-size: 12px;
                font-weight: 800;
                text-align: center;
                overflow: hidden;
                white-space: nowrap;
                text-overflow: ellipsis;
                border: 1px solid rgba(255,80,0,.18);
            }

            .tb-btn-row {
                display: grid;
                grid-template-columns: repeat(5, 1fr);
                gap: 8px;
            }

            .tb-btn {
                height: 40px;
                border: none;
                outline: none;
                border-radius: 13px;
                font-size: 13px;
                font-weight: 800;
                cursor: pointer;
            }

            .tb-btn.primary {
                color: #fff;
                background: linear-gradient(135deg, #ff8a1c, #ff5000);
            }

            .tb-btn.success {
                color: #fff;
                background: linear-gradient(135deg, #20c997, #12b886);
            }

            .tb-btn.blue {
                color: #fff;
                background: linear-gradient(135deg, #4dabf7, #228be6);
            }

            .tb-btn.light {
                color: #ff5000;
                background: #fff;
                border: 1px solid rgba(255,80,0,.26);
            }

            .tb-download-label {
                display: inline-flex;
                align-items: center;
                gap: 8px;
                padding: 7px 12px;
                margin-bottom: 10px;
                border-radius: 999px;
                background: linear-gradient(135deg, #fff7f2, #fff);
                color: #ff5000;
                font-size: 13px;
                font-weight: 800;
                border: 1px solid rgba(255,80,0,.25);
                cursor: pointer;
                user-select: none;
            }

            .tb-download-label input,
            .tb-filter-check {
                accent-color: #ff5000;
            }

            .tb-img-select-wrap {
                position: absolute !important;
                right: 8px;
                top: 8px;
                z-index: 99999999;
                width: 28px;
                height: 28px;
                border-radius: 50%;
                background: rgba(0,0,0,.42);
                backdrop-filter: blur(8px);
                display: flex;
                align-items: center;
                justify-content: center;
                cursor: pointer;
                border: 1px solid rgba(255,255,255,.45);
            }

            .tb-img-select-wrap input {
                width: 17px;
                height: 17px;
                margin: 0;
                accent-color: #ff5000;
                cursor: pointer;
            }

            .tb-img-selected {
                outline: 3px solid #ff5000 !important;
                outline-offset: -3px !important;
                border-radius: 8px !important;
            }

            #tb-filter-result-panel {
                margin-top: 12px;
                max-height: 560px;
                overflow-y: auto;
                background: #fffaf7;
                border: 1px solid #ffd4bd;
                border-radius: 18px;
                padding: 12px;
            }

            .tb-filter-header {
                position: sticky;
                top: 0;
                z-index: 2;
                background: linear-gradient(135deg,#ff5000,#ff8a1c);
                color: #fff;
                padding: 11px 13px;
                border-radius: 13px;
                font-weight: 900;
                margin-bottom: 12px;
            }

            .tb-filter-close {
                float: right;
                border: none;
                background: #fff;
                color: #ff5000;
                border-radius: 999px;
                padding: 4px 11px;
                cursor: pointer;
                font-weight: 800;
            }

            .tb-filter-card {
                margin-bottom: 13px;
                padding: 11px;
                border: 1px solid #ffd4bd;
                border-radius: 15px;
                background: #fff;
            }

            .tb-filter-title {
                display: flex;
                justify-content: space-between;
                align-items: center;
                font-size: 13px;
                font-weight: 900;
                color: #ff5000;
                margin-bottom: 10px;
            }

            .tb-filter-title label {
                display: flex;
                align-items: center;
                gap: 6px;
                cursor: pointer;
            }

            .tb-filter-pics {
                display: grid;
                grid-template-columns: repeat(4, 1fr);
                gap: 9px;
            }

            .tb-filter-img-wrap {
                position: relative;
                overflow: hidden;
                border-radius: 12px;
            }

            .tb-filter-img-del {
                position: absolute;
                right: 6px;
                top: 6px;
                z-index: 3;
                width: 22px;
                height: 22px;
                border: none;
                border-radius: 50%;
                color: #fff;
                background: rgba(0,0,0,.62);
                cursor: pointer;
                font-size: 16px;
                line-height: 20px;
                font-weight: 900;
                padding: 0;
            }

            .tb-filter-img-del:hover {
                background: #ff5000;
            }

            .tb-filter-pics img {
                width: 100%;
                aspect-ratio: 3 / 4;
                height: auto;
                object-fit: cover;
                border-radius: 12px;
                border: 1px solid #ffe0cf;
                background: #fff;
            }

            .tb-filter-tip {
                margin: 10px 0 0;
                padding: 8px 10px;
                border-radius: 12px;
                color: #9a4b16;
                background: #fff2e8;
                font-size: 12px;
                line-height: 1.5;
            }
        `;

        document.head.appendChild(style);
    }

    function makePanelDraggable(panel, handle) {
        let dragging = false;
        let startX = 0;
        let startY = 0;
        let startLeft = 0;
        let startTop = 0;

        handle.addEventListener('mousedown', e => {
            if (e.target.closest('button, input, label')) return;

            dragging = true;
            startX = e.clientX;
            startY = e.clientY;

            const rect = panel.getBoundingClientRect();
            startLeft = rect.left;
            startTop = rect.top;

            panel.style.left = `${startLeft}px`;
            panel.style.top = `${startTop}px`;
            panel.style.right = 'auto';

            document.body.style.userSelect = 'none';
        });

        document.addEventListener('mousemove', e => {
            if (!dragging) return;
            panel.style.left = `${Math.max(0, startLeft + e.clientX - startX)}px`;
            panel.style.top = `${Math.max(0, startTop + e.clientY - startY)}px`;
        });

        document.addEventListener('mouseup', () => {
            dragging = false;
            document.body.style.userSelect = '';
        });
    }

    function addPanel() {
        if (document.querySelector('#tb-hd-download-panel')) return;

        injectStyle();

        const panel = document.createElement('div');
        panel.id = 'tb-hd-download-panel';

        panel.innerHTML = `
            <div class="tb-panel-top">
                <div class="tb-panel-title">
                    <span class="tb-logo">淘</span>
                    <div>
                        <div class="tb-title-main">评论原图下载</div>
                        <div class="tb-title-sub">强力去重 · 单图可删 · 商品名随机码保存</div>
                    </div>
                </div>
            </div>

            <div class="tb-panel-body">
                <div class="tb-folder-row">
                    <input id="tb-folder-name-input" placeholder="输入商品名,目录按 商品名a7 生成">
                    <div class="tb-folder-preview" id="tb-folder-preview">淘宝评论原图a7</div>
                </div>

                <div class="tb-btn-row">
                    <button class="tb-btn primary" id="tb-scan-group-btn">评论分组</button>
                    <button class="tb-btn blue" id="tb-filter-group-btn">筛选</button>
                    <button class="tb-btn blue" id="tb-scan-img-btn">图集选择</button>
                    <button class="tb-btn success" id="tb-download-btn">下载选中</button>
                    <button class="tb-btn light" id="tb-select-all-btn">全选/反选</button>
                </div>

                <div class="tb-filter-tip">
                    下载目录:商品名a7 / 01.jpg。不会再创建“商品名/分组01”这种多层文件夹。
                </div>

                <div id="tb-filter-body"></div>
            </div>
        `;

        document.body.appendChild(panel);
        makePanelDraggable(panel, panel.querySelector('.tb-panel-top'));

        const input = document.querySelector('#tb-folder-name-input');
        const preview = document.querySelector('#tb-folder-preview');

        input.addEventListener('input', () => {
            preview.textContent = `${getSkuBaseName()}${randomTwoCode()}`;
        });

        document.querySelector('#tb-scan-group-btn').onclick = markComments;
        document.querySelector('#tb-filter-group-btn').onclick = filterCommentGroups;
        document.querySelector('#tb-scan-img-btn').onclick = markImageItems;
        document.querySelector('#tb-download-btn').onclick = downloadSelected;
        document.querySelector('#tb-select-all-btn').onclick = toggleAll;
    }

    function markComments() {
        const comments = getComments();
        let validCount = 0;

        comments.forEach(comment => {
            if (comment.querySelector('.tb-download-check')) return;

            const imgs = getImages(comment);
            if (!imgs.length) return;

            validCount++;

            const box = document.createElement('label');
            box.className = 'tb-download-label';

            box.innerHTML = `
                <input type="checkbox" class="tb-download-check tb-group-check">
                <span>本组 ${imgs.length} 张原图</span>
            `;

            comment.prepend(box);
        });

        alert(`评论分组扫描完成:找到 ${validCount} 个图片评论分组`);
    }

    function filterCommentGroups() {
        const body = document.querySelector('#tb-filter-body');
        if (!body) return;

        body.innerHTML = '';

        const comments = getComments();
        const groups = [];

        comments.forEach(comment => {
            const urls = getImages(comment);
            if (urls.length < 3) return;

            const key = getGroupKey(urls);
            if (!key) return;

            const duplicated = groups.some(oldGroup => {
                return oldGroup.key === key || isSimilarGroup(oldGroup.urls, urls, 0.8);
            });

            if (duplicated) return;

            groups.push({
                key,
                urls,
                count: urls.length
            });
        });

        groups.sort((a, b) => b.count - a.count);

        if (!groups.length) {
            alert('当前已加载评论里没有找到 3 张以上且不重复的分组。请先滚动评论加载更多,再点筛选。');
            return;
        }

        const resultPanel = document.createElement('div');
        resultPanel.id = 'tb-filter-result-panel';

        resultPanel.innerHTML = `
            <div class="tb-filter-header">
                筛选结果:${groups.length} 组,已强力去重
                <button id="tb-close-filter-panel" class="tb-filter-close">清空</button>
            </div>
        `;

        groups.forEach((group, index) => {
            const card = document.createElement('div');
            card.className = 'tb-filter-card';
            card.dataset.urls = JSON.stringify(group.urls);

            const pics = group.urls.map(url => `
                <div class="tb-filter-img-wrap" data-url="${url}">
                    <button type="button" class="tb-filter-img-del" title="删除此图,删除后不会下载">×</button>
                    <img src="${url}" loading="lazy">
                </div>
            `).join('');

            card.innerHTML = `
                <div class="tb-filter-title">
                    <label>
                        <input type="checkbox" class="tb-filter-check">
                        第 ${index + 1} 组 · <b class="tb-filter-count">${group.count}</b> 张
                    </label>
                    <span><b class="tb-filter-count-right">${group.count}</b> 张原图</span>
                </div>
                <div class="tb-filter-pics">${pics}</div>
            `;

            resultPanel.appendChild(card);
        });

        body.appendChild(resultPanel);

        document.querySelector('#tb-close-filter-panel').onclick = () => {
            body.innerHTML = '';
        };

        resultPanel.addEventListener('click', e => {
            const del = e.target.closest('.tb-filter-img-del');
            if (!del) return;

            e.preventDefault();
            e.stopPropagation();

            const wrap = del.closest('.tb-filter-img-wrap');
            const card = del.closest('.tb-filter-card');
            if (!wrap || !card) return;

            const removedIdentity = getImageIdentity(wrap.dataset.url);
            let urls = [];

            try {
                urls = JSON.parse(card.dataset.urls || '[]');
            } catch (err) {
                urls = [];
            }

            urls = urls.filter(url => getImageIdentity(url) !== removedIdentity);
            card.dataset.urls = JSON.stringify(urls);
            wrap.remove();

            card.querySelectorAll('.tb-filter-count, .tb-filter-count-right').forEach(el => {
                el.textContent = urls.length;
            });

            if (urls.length === 0) card.remove();
        });

        alert(`筛选完成:显示 ${groups.length} 个强力去重分组。右上角 × 可删除单张图。`);
    }

    function markImageItems() {
        const items = getImageItems();
        let validCount = 0;
        const groupMap = new Map();

        items.forEach(item => {
            if (item.querySelector('.tb-single-check')) return;

            const img = item.querySelector('img');
            if (!img) return;
            if (!isImageSizeOk(img)) return;

            const url = getHDImage(getImageSrc(img));
            if (!isValidImageUrl(url)) return;

            const identity = getImageIdentity(url);

            if (!groupMap.has(identity)) {
                groupMap.set(identity, String(groupMap.size + 1).padStart(2, '0'));
            }

            const groupId = groupMap.get(identity);

            item.style.position = 'relative';
            item.dataset.tbImageUrl = url;
            item.dataset.tbGalleryGroupId = groupId;

            const wrap = document.createElement('label');
            wrap.className = 'tb-img-select-wrap';
            wrap.title = `选中下载高清原图,分组:${groupId}`;
            wrap.innerHTML = `<input type="checkbox" class="tb-single-check">`;

            const checkbox = wrap.querySelector('input');

            checkbox.addEventListener('change', () => {
                item.classList.toggle('tb-img-selected', checkbox.checked);
            });

            wrap.addEventListener('click', e => {
                e.stopPropagation();
            });

            item.appendChild(wrap);
            validCount++;
        });

        alert(`图集扫描完成:找到 ${validCount} 张可选择图片`);
    }

    function toggleAll() {
        const checks = [
            ...document.querySelectorAll('.tb-download-check'),
            ...document.querySelectorAll('.tb-single-check'),
            ...document.querySelectorAll('.tb-filter-check')
        ];

        if (!checks.length) {
            alert('请先点击“评论分组”“筛选”或“图集选择”');
            return;
        }

        const hasUnchecked = checks.some(c => !c.checked);

        checks.forEach(c => {
            c.checked = hasUnchecked;

            const item = c.closest('[class*="commentsImgItem"], [class*="photo--"], [class*="cover--"]');
            if (item) item.classList.toggle('tb-img-selected', c.checked);
        });
    }

    function addTask(tasks, seen, groupCounters, rawUrl, folder) {
        if (!rawUrl || !folder) return;

        const hdUrl = getHDImage(rawUrl);
        if (!isValidImageUrl(hdUrl)) return;

        const identity = getImageIdentity(hdUrl);
        if (seen.has(identity)) return;

        seen.add(identity);

        if (!groupCounters[folder]) groupCounters[folder] = 0;

        groupCounters[folder]++;

        const ext = getFileExt(hdUrl);
        const filename = `${folder}/${String(groupCounters[folder]).padStart(2, '0')}.${ext}`;

        tasks.push({
            url: hdUrl,
            filename
        });
    }

    async function downloadSelected() {
        const tasks = [];
        const seen = new Set();
        const groupCounters = {};

        const selectedGroups = [...document.querySelectorAll('.tb-group-check:checked')]
            .map(input => input.closest('[class*="Comment--"]'))
            .filter(Boolean);

        selectedGroups.forEach(comment => {
            const folder = makeSkuFolderName();
            const imgs = getImages(comment);

            imgs.forEach(url => {
                addTask(tasks, seen, groupCounters, url, folder);
            });
        });

        const selectedFilterGroups = [...document.querySelectorAll('.tb-filter-check:checked')]
            .map(input => input.closest('.tb-filter-card'))
            .filter(Boolean);

        selectedFilterGroups.forEach(card => {
            let urls = [];

            try {
                urls = JSON.parse(card.dataset.urls || '[]');
            } catch (e) {
                urls = [];
            }

            const folder = makeSkuFolderName();

            urls.forEach(url => {
                addTask(tasks, seen, groupCounters, url, folder);
            });
        });

        const selectedSingles = [...document.querySelectorAll('.tb-single-check:checked')]
            .map(input => input.closest('[class*="commentsImgItem"], [class*="photo--"], [class*="cover--"]'))
            .filter(Boolean);

        const singleSkuFolders = {};

        selectedSingles.forEach(item => {
            const rawUrl = item.dataset.tbImageUrl;
            if (!rawUrl) return;

            const groupId = item.dataset.tbGalleryGroupId || `single${randomTwoCode()}`;

            if (!singleSkuFolders[groupId]) {
                singleSkuFolders[groupId] = makeSkuFolderName();
            }

            addTask(tasks, seen, groupCounters, rawUrl, singleSkuFolders[groupId]);
        });

        if (!tasks.length) {
            alert('请先勾选要下载的图片或分组');
            return;
        }

        for (let i = 0; i < tasks.length; i++) {
            const task = tasks[i];

            console.log('下载高清原图:', task.url, task.filename);

            GM_download({
                url: task.url,
                name: task.filename,
                saveAs: false,
                onload: () => console.log('下载完成:', task.filename),
                onerror: err => console.warn('下载失败:', task.url, err)
            });

            await sleep(500);
        }

        alert(
            `已开始下载 ${tasks.length} 张高清原图。\n\n` +
            `目录结构:${getSkuBaseName()}${randomTwoCode()}/01.jpg\n` +
            `不会再创建“商品名/分组01”这种多层文件夹。\n` +
            `随机干扰符只使用2位字母数字,不加横杠。\n` +
            `重复图片已按图片身份去重。\n` +
            `右侧删除过的图片不会下载。`
        );
    }

    addPanel();

    const observer = new MutationObserver(() => {
        addPanel();
    });

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

})();


发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。