您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
简篇网站账号切换与媒体提取工具
// ==UserScript== // @name 简篇助手 // @namespace http://tampermonkey.net/ // @version 0.5 // @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 MediaStore = { items: new Map(), clear() { this.items.clear(); }, add(type, url, posterUrl = null) { const processedUrl = type === '图片' ? reformatImageUrl(url) : url; if (this.items.has(processedUrl)) return false; this.items.set(processedUrl, { type, url: processedUrl, posterUrl, bbcode: this.generateBBCode(type, processedUrl, posterUrl) }); return true; }, generateBBCode(type, url, posterUrl = null) { switch(type) { case '图片': return `[img]${url}[/img]`; case '音频': return `[audio]${url}[/audio]`; case '视频': return `[movie]${url}[/movie]`; // 基础BBCode不包含封面 } }, getAllUrls() { return Array.from(this.items.keys()); }, getAllBBCode() { return Array.from(this.items.values()).map(item => { // 普通BBCode永远不包含封面 return this.generateBBCode(item.type, item.url); }); }, getAllBBCode2() { return Array.from(this.items.values()).map(item => { // BBCode2 仅在视频类型且有封面时才包含封面 if (item.type === '视频' && item.posterUrl) { return `[movie]${item.url}|${item.posterUrl}[/movie]`; } return this.generateBBCode(item.type, item.url); }); }, getItem(url) { return this.items.get(url); }, size() { return this.items.size; } }; 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 updateButtonState(button, originalText, duration = 1000) { button.textContent = '已复制'; setTimeout(() => button.textContent = originalText, duration); } const MEDIA_STYLES = { container: ` margin: 0 0 15px 0; padding: 12px; background: #f8f9fa; border-radius: 8px; border: 1px solid #e2e8f0; `, typeLabel: (type) => ` display: inline-block; padding: 2px 8px; background: ${ type === '视频' ? '#3182ce' : type === '音频' ? '#38a169' : '#805ad5' }; color: white; border-radius: 4px; font-size: 12px; margin-bottom: 8px; `, url: ` font-size: 14px; color: #4a5568; word-break: break-all; margin-bottom: 12px; line-height: 1.5; font-family: monospace; `, buttonGroup: ` display: flex; gap: 10px; ` }; function addMediaLink(url, type, posterUrl = null) { if (!MediaStore.add(type, url, posterUrl)) return; const container = document.getElementById('mediaContent'); const linkDiv = document.createElement('div'); const mediaItem = MediaStore.getItem(type === '图片' ? reformatImageUrl(url) : url); linkDiv.style.cssText = MEDIA_STYLES.container; linkDiv.innerHTML = ` <div style="${MEDIA_STYLES.typeLabel(type)}">${type}</div> <div style="${MEDIA_STYLES.url}">${mediaItem.url}</div> <div style="${MEDIA_STYLES.buttonGroup}"> <button class="copy-btn" style="${STYLES.button}">复制链接</button> <button class="copy-bbcode-btn" style="${STYLES.button} background: #38a169 !important;">复制BBCode</button> ${type === '视频' && posterUrl ? ` <button class="copy-bbcode2-btn" style="${STYLES.button} background: #805ad5 !important;">复制BBCode2</button> ` : ''} </div> `; const copyBtn = linkDiv.querySelector('.copy-btn'); const copyBBCodeBtn = linkDiv.querySelector('.copy-bbcode-btn'); copyBtn.onclick = () => { navigator.clipboard.writeText(mediaItem.url); updateButtonState(copyBtn, '复制链接'); }; copyBBCodeBtn.onclick = () => { // 普通BBCode永远不包含封面 navigator.clipboard.writeText(MediaStore.generateBBCode(type, mediaItem.url)); updateButtonState(copyBBCodeBtn, '复制BBCode'); }; if (type === '视频' && posterUrl) { const copyBBCode2Btn = linkDiv.querySelector('.copy-bbcode2-btn'); copyBBCode2Btn.onclick = () => { // BBCode2 包含封面 navigator.clipboard.writeText(`[movie]${mediaItem.url}|${posterUrl}[/movie]`); updateButtonState(copyBBCode2Btn, '复制BBCode2'); }; } container.appendChild(linkDiv); } function createBatchCopyButtons() { const container = document.createElement('div'); container.style.cssText = MEDIA_STYLES.buttonGroup; container.style.padding = '10px'; container.style.marginBottom = '15px'; const buttons = [ ['复制全部链接', () => MediaStore.getAllUrls()], ['复制全部BBCode', () => MediaStore.getAllBBCode()], ['复制全部BBCode2', () => MediaStore.getAllBBCode2()] ]; buttons.forEach(([text, getter]) => { const button = document.createElement('button'); button.textContent = text; button.style.cssText = STYLES.button; button.onclick = () => { navigator.clipboard.writeText(getter().join('\n')); updateButtonState(button, text); }; container.appendChild(button); }); return container; } const POSTER_URL_REGEX = /url\("([^"]+)"\)/; const MediaScanner = { scanVideoPosters() { const posterMap = new Map(); document.querySelectorAll('.poster').forEach(poster => { const style = poster.getAttribute('style'); if (style) { const match = style.match(POSTER_URL_REGEX); if (match && isValidUrl(match[1])) { const videoElem = poster.closest('div[class*="video"]')?.querySelector('video'); if (videoElem) { const posterUrl = reformatImageUrl(match[1]); posterMap.set(videoElem, posterUrl); } } } }); return posterMap; }, scan() { try { const posterMap = this.scanVideoPosters(); // 扫描视频 document.querySelectorAll('video').forEach(video => { if (video.src && isValidUrl(video.src)) { addMediaLink(video.src, '视频', posterMap.get(video)); } video.querySelectorAll('source').forEach(source => { if (source.src && isValidUrl(source.src)) { addMediaLink(source.src, '视频', posterMap.get(video)); } }); }); // 扫描音频 document.querySelectorAll('audio').forEach(audio => { if (audio.src && isValidUrl(audio.src)) { addMediaLink(audio.src, '音频'); } audio.querySelectorAll('source').forEach(source => { if (source.src && isValidUrl(source.src)) { addMediaLink(source.src, '音频'); } }); }); // 扫描图片 document.querySelectorAll('img').forEach(img => { if (img.src && isValidUrl(img.src)) { addMediaLink(img.src, '图片'); } }); return MediaStore.size(); } catch (error) { console.error('扫描媒体出错:', error); throw error; } } }; function scanMedia() { const mediaContent = document.getElementById('mediaContent'); if (!mediaContent || mediaContent.style.display === 'none') return; mediaContent.innerHTML = ''; mediaContent.appendChild(createBatchCopyButtons()); MediaStore.clear(); requestAnimationFrame(() => { try { const count = MediaScanner.scan(); if (count === 0) { mediaContent.innerHTML = '<div style="text-align: center; padding: 30px;">未找到媒体文件</div>'; } } catch (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(); } })();