简篇助手

简篇网站账号切换与媒体提取工具

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

// ==UserScript==
// @name         简篇助手
// @namespace    http://tampermonkey.net/
// @version      0.3
// @description  简篇网站账号切换与媒体提取工具
// @author       Your name
// @match        https://www.jianpian.cn/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @license      MIT
// ==/UserScript==


(function() {
    'use strict';

    const STYLES = {
        floatingWindow: `
            position: fixed;
            top: 20px;
            right: 20px;
            width: 320px;
            height: 500px;
            background: rgba(255, 255, 255, 0.98);
            border-radius: 12px;
            padding: 15px;
            z-index: 9999;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
            transition: all 0.3s ease;
            display: flex;
            flex-direction: column;
        `,
        tabs: `display: flex; margin-bottom: 15px; border-bottom: 1px solid #eee;`,
        tab: `padding: 8px 16px; cursor: pointer; border-bottom: 2px solid transparent; color: #666; transition: all 0.3s ease;`,
        activeTab: `color: #2c5282; border-bottom: 2px solid #2c5282;`,
        button: `
            background: #4299e1 !important;
            color: white !important;
            border: none !important;
            padding: 8px 12px !important;
            border-radius: 4px !important;
            cursor: pointer !important;
            font-size: 13px !important;
            font-weight: 500 !important;
            transition: all 0.2s ease !important;
            width: 100% !important;
            height: auto !important;
            line-height: 1.5 !important;
            box-sizing: border-box !important;
            margin: 0 !important;
            text-align: center !important;
            display: block !important;
        `,
        content: `height: 100%; overflow-y: auto; padding: 10px;`,
        minimizeButton: `
            position: absolute;
            top: 15px;
            right: 15px;
            width: 20px;
            height: 20px;
            line-height: 20px;
            text-align: center;
            cursor: pointer;
            font-size: 16px;
            color: #666;
            transition: all 0.3s ease;
        `,
        minimized: `
            position: fixed;
            top: 20%;
            right: 20px;
            width: auto;
            height: auto;
            padding: 10px 15px;
            background: rgba(255, 255, 255, 0.98);
            border-radius: 12px;
            z-index: 9999;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
            cursor: pointer;
            transition: all 0.3s ease;
        `,
        modal: `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.5);
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 10000;
        `,
        modalContent: `
            background: white;
            padding: 20px;
            border-radius: 12px;
            max-width: 400px;
            width: 90%;
            max-height: 80vh;
            overflow-y: auto;
        `,
        accountItem: `
            padding: 12px;
            margin: 8px 0;
            border: 1px solid #e2e8f0;
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.2s ease;
        `
    };

    const ESSENTIAL_COOKIES = ['epian-token', 'epian-user-id'];
    const addedUrls = new Set();

    function isValidUrl(url) {
        return url && (
            url.startsWith('https://media-volc.jianpian.info/') ||
            url.startsWith('https://img-volc.jianpian.info/')
        );
    }

    function reformatImageUrl(url) {
        if (!url.startsWith('https://img-volc.jianpian.info/')) return url;
        const match = url.match(/https:\/\/img-volc\.jianpian\.info\/([^?~]+)/);
        if (!match) return url;

        const filePath = match[1];
        if (filePath.includes('__transed__')) {
            const [basePath, extension] = filePath.split('__transed__');
            return `https://img-volc.jianpian.info/${basePath}.${extension.split('.')[0]}`;
        }
        return url;
    }

    function addMediaLink(url, type, posterUrl = null) {
        const processedUrl = type === '图片' ? reformatImageUrl(url) : url;
        if (addedUrls.has(processedUrl)) return;
        addedUrls.add(processedUrl);

        const container = document.getElementById('mediaContent');
        const linkDiv = document.createElement('div');
        
        const buttons = type === '视频' && posterUrl ? `
            <div style="display: flex; gap: 10px;">
                <button class="copy-btn" style="${STYLES.button}">复制链接</button>
                <button class="copy-bbcode-btn" style="${STYLES.button} background: #38a169 !important;">复制BBCode</button>
                <button class="copy-bbcode2-btn" style="${STYLES.button} background: #805ad5 !important;">复制BBCode2</button>
            </div>
        ` : `
            <div style="display: flex; gap: 10px;">
                <button class="copy-btn" style="${STYLES.button}">复制链接</button>
                <button class="copy-bbcode-btn" style="${STYLES.button} background: #38a169 !important;">复制BBCode</button>
            </div>
        `;

        linkDiv.innerHTML = `
            <div style="display: inline-block; padding: 2px 8px; background: ${
                type === '视频' ? '#3182ce' : 
                type === '音频' ? '#38a169' : 
                '#805ad5'
            }; color: white; border-radius: 4px; font-size: 12px; margin-bottom: 8px;">${type}</div>
            <div style="font-size: 14px; color: #4a5568; word-break: break-all; margin-bottom: 12px; 
                line-height: 1.5; font-family: monospace;">${processedUrl}</div>
            ${buttons}
        `;
        
        linkDiv.style.cssText = `
            margin: 0 0 15px 0;
            padding: 12px;
            background: #f8f9fa;
            border-radius: 8px;
            border: 1px solid #e2e8f0;
        `;

        const copyBtn = linkDiv.querySelector('.copy-btn');
        const copyBBCodeBtn = linkDiv.querySelector('.copy-bbcode-btn');
        
        copyBtn.onclick = () => {
            navigator.clipboard.writeText(processedUrl);
            copyBtn.textContent = '已复制';
            setTimeout(() => copyBtn.textContent = '复制链接', 1000);
        };

        copyBBCodeBtn.onclick = () => {
            const bbcode = {
                '图片': `[img]${processedUrl}[/img]`,
                '音频': `[audio]${processedUrl}[/audio]`,
                '视频': `[movie]${processedUrl}[/movie]`
            }[type];
            navigator.clipboard.writeText(bbcode);
            copyBBCodeBtn.textContent = '已复制';
            setTimeout(() => copyBBCodeBtn.textContent = '复制BBCode', 1000);
        };

        if (type === '视频' && posterUrl) {
            const copyBBCode2Btn = linkDiv.querySelector('.copy-bbcode2-btn');
            copyBBCode2Btn.onclick = () => {
                const bbcode2 = `[movie]${processedUrl}|${posterUrl}[/movie]`;
                navigator.clipboard.writeText(bbcode2);
                copyBBCode2Btn.textContent = '已复制';
                setTimeout(() => copyBBCode2Btn.textContent = '复制BBCode2', 1000);
            };
        }

        container.appendChild(linkDiv);
    }

    function scanMedia() {
        const mediaContent = document.getElementById('mediaContent');
        if (!mediaContent || mediaContent.style.display === 'none') return;
        
        mediaContent.innerHTML = '<div style="text-align: center; padding: 30px;">正在扫描媒体...</div>';
        addedUrls.clear();
        
        const videoPosterMap = new Map();
        
        requestAnimationFrame(() => {
            try {
                document.querySelectorAll('.poster').forEach(poster => {
                    const style = poster.getAttribute('style');
                    if (style) {
                        const match = style.match(/url\("([^"]+)"\)/);
                        if (match && isValidUrl(match[1])) {
                            const videoElem = poster.closest('div[class*="video"]')?.querySelector('video');
                            if (videoElem) {
                                const posterUrl = reformatImageUrl(match[1]);
                                videoPosterMap.set(videoElem, posterUrl);
                            }
                        }
                    }
                });

                Array.from(document.getElementsByTagName('video')).forEach(video => {
                    if (video.src && isValidUrl(video.src)) {
                        const posterUrl = videoPosterMap.get(video);
                        addMediaLink(video.src, '视频', posterUrl);
                    }
                    
                    Array.from(video.getElementsByTagName('source')).forEach(source => {
                        if (source.src && isValidUrl(source.src)) {
                            const posterUrl = videoPosterMap.get(video);
                            addMediaLink(source.src, '视频', posterUrl);
                        }
                    });
                });

                ['audio', 'img'].forEach(tag => {
                    Array.from(document.getElementsByTagName(tag)).forEach(elem => {
                        if (elem.src && isValidUrl(elem.src)) {
                            addMediaLink(elem.src, tag === 'img' ? '图片' : '音频');
                        }
                        if (tag !== 'img') {
                            Array.from(elem.getElementsByTagName('source')).forEach(source => {
                                if (source.src && isValidUrl(source.src)) {
                                    addMediaLink(source.src, '音频');
                                }
                            });
                        }
                    });
                });

                if (addedUrls.size === 0) {
                    mediaContent.innerHTML = '<div style="text-align: center; padding: 30px;">未找到媒体文件</div>';
                }
            } catch (error) {
                console.error('扫描媒体时出错:', error);
                mediaContent.innerHTML = '扫描媒体时出错,请刷新页面重试';
            }
        });
    }

    function getAccountList(accounts) {
        const accountNames = Object.keys(accounts);
        if (accountNames.length === 0) return null;
        
        return '已保存的账号:\n\n' + accountNames.map(name => {
            const account = accounts[name];
            return `${name}${account.note ? ` (${account.note})` : ''}\n保存时间: ${account.saveDate}`;
        }).join('\n\n');
    }

    function showAccountSelector(accounts, onSelect, title) {
        const modal = document.createElement('div');
        modal.style.cssText = STYLES.modal;
        
        const content = document.createElement('div');
        content.style.cssText = STYLES.modalContent;
        content.innerHTML = `<h3 style="margin: 0 0 16px 0; font-size: 18px;">${title}</h3>`;

        Object.entries(accounts).forEach(([name, account]) => {
            const item = document.createElement('div');
            item.style.cssText = STYLES.accountItem;
            item.innerHTML = `
                <div style="font-weight: 500; color: #2d3748;">${name}</div>
                ${account.note ? `<div style="color: #718096; font-size: 12px;">备注: ${account.note}</div>` : ''}
                <div style="color: #a0aec0; font-size: 12px;">保存时间: ${account.saveDate}</div>
            `;
            item.onmouseover = () => item.style.background = '#f7fafc';
            item.onmouseout = () => item.style.background = 'white';
            item.onclick = () => {
                onSelect(name);
                document.body.removeChild(modal);
            };
            content.appendChild(item);
        });

        const closeBtn = document.createElement('button');
        closeBtn.textContent = '取消';
        closeBtn.style.cssText = STYLES.button;
        closeBtn.onclick = () => document.body.removeChild(modal);
        content.appendChild(closeBtn);

        modal.appendChild(content);
        document.body.appendChild(modal);

        // 点击背景关闭
        modal.onclick = (e) => {
            if (e.target === modal) {
                document.body.removeChild(modal);
            }
        };
    }

    function checkAccounts() {
        const accounts = GM_getValue('accounts', {});
        if (Object.keys(accounts).length === 0) {
            alert('还没有保存任何账号!');
            return null;
        }
        return accounts;
    }

    function switchAccount() {
        const accounts = checkAccounts();
        if (!accounts) return;

        showAccountSelector(accounts, (selectedAccount) => {
            ESSENTIAL_COOKIES.forEach(name => {
                document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=.jianpian.cn`;
            });

            accounts[selectedAccount].cookies.split(';').forEach(cookie => {
                document.cookie = `${cookie.trim()}; path=/; domain=.jianpian.cn`;
            });

            location.reload();
        }, '选择要切换的账号');
    }

    function deleteAccount() {
        const accounts = checkAccounts();
        if (!accounts) return;

        showAccountSelector(accounts, (selectedAccount) => {
            if (confirm(`确定要删除账号 "${selectedAccount}" 吗?`)) {
                delete accounts[selectedAccount];
                GM_setValue('accounts', accounts);
                alert('删除成功!');
            }
        }, '选择要删除的账号');
    }

    const BUTTON_CONFIGS = {
        saveCookie: ['保存当前账号', async () => {
            const currentCookies = document.cookie.split(';');
            const essentialCookies = currentCookies.filter(cookie => 
                ESSENTIAL_COOKIES.some(name => cookie.split('=')[0].trim() === name)
            );

            if (essentialCookies.length === 0) {
                alert('未检测到登录(不可用)状态,请先登录(不可用)!');
                return;
            }

            const accountName = prompt('请为当前账号设置一个名称:');
            if (!accountName) return;

            const accountNote = prompt('请输入账号备注(可选):');
            const accounts = GM_getValue('accounts', {});
            accounts[accountName] = {
                cookies: essentialCookies.join(';'),
                note: accountNote || '',
                saveDate: new Date().toLocaleString()
            };
            GM_setValue('accounts', accounts);
            alert('保存成功!');
        }],
        switchAccount: ['切换账号', switchAccount],
        deleteAccount: ['删除账号', deleteAccount],
        exportAccounts: ['导出账号', () => {
            const accounts = GM_getValue('accounts', {});
            const blob = new Blob([JSON.stringify(accounts, null, 2)], { type: 'application/json' });
            const a = document.createElement('a');
            a.href = URL.createObjectURL(blob);
            a.download = '简篇账号数据.json';
            a.click();
            URL.revokeObjectURL(a.href);
        }],
        importAccounts: ['导入账号', () => {
            const input = document.createElement('input');
            input.type = 'file';
            input.accept = '.json';
            input.onchange = e => {
                const reader = new FileReader();
                reader.onload = event => {
                    try {
                        const accounts = JSON.parse(event.target.result);
                        GM_setValue('accounts', accounts);
                        alert('导入成功!');
                    } catch (error) {
                        alert('导入失败,文件格式错误!');
                    }
                };
                reader.readAsText(e.target.files[0]);
            };
            input.click();
        }]
    };

    function createUI() {
        const container = document.createElement('div');
        container.id = 'jianpianHelper';
        container.style.cssText = STYLES.floatingWindow;

        const minimizeBtn = document.createElement('div');
        minimizeBtn.textContent = '−';
        minimizeBtn.style.cssText = STYLES.minimizeButton;
        minimizeBtn.title = '最小化';

        let isMinimized = false;
        minimizeBtn.onclick = (e) => {
            e.stopPropagation();
            isMinimized = !isMinimized;
            
            if (isMinimized) {
                container.innerHTML = '简篇助手';
                container.style.cssText = STYLES.minimized;
                container.title = '点击展开';
                container.onclick = () => {
                    isMinimized = false;
                    container.style.cssText = STYLES.floatingWindow;
                    container.innerHTML = '';
                    setupUI();
                    setupAccountButtons();
                    container.onclick = null;
                };
            }
        };

        function setupUI() {
            const tabs = document.createElement('div');
            tabs.style.cssText = STYLES.tabs;
            
            const [accountTab, mediaTab] = ['账号管理', '媒体提取'].map(text => {
                const tab = document.createElement('div');
                tab.textContent = text;
                tab.style.cssText = STYLES.tab + (text === '账号管理' ? STYLES.activeTab : '');
                return tab;
            });

            tabs.append(accountTab, mediaTab);

            const [accountContent, mediaContent] = ['account', 'media'].map(id => {
                const content = document.createElement('div');
                content.id = `${id}Content`;
                content.style.cssText = STYLES.content + `; display: ${id === 'account' ? 'block' : 'none'};`;
                return content;
            });

            [accountTab, mediaTab].forEach((tab, i) => {
                tab.onclick = () => {
                    [accountTab, mediaTab].forEach((t, j) => 
                        t.style.cssText = STYLES.tab + (i === j ? STYLES.activeTab : '')
                    );
                    accountContent.style.display = i === 0 ? 'block' : 'none';
                    mediaContent.style.display = i === 0 ? 'none' : 'block';
                    if (i === 1) scanMedia();
                };
            });

            container.appendChild(minimizeBtn);
            container.appendChild(tabs);
            container.appendChild(accountContent);
            container.appendChild(mediaContent);
        }

        setupUI();
        return container;
    }

    function setupAccountButtons() {
        const accountContent = document.getElementById('accountContent');
        const accountButtons = document.createElement('div');
        accountButtons.style.cssText = `
            padding: 10px;
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 10px;
        `;
        
        Object.entries(BUTTON_CONFIGS).forEach(([_, [text, handler]]) => {
            const button = document.createElement('button');
            button.textContent = text;
            button.style.cssText = STYLES.button;
            button.onclick = handler;
            accountButtons.appendChild(button);
        });

        accountContent.appendChild(accountButtons);
    }

    function init() {
        const container = createUI();
        document.body.appendChild(container);
        setupAccountButtons();
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();

QingJ © 2025

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