// ==UserScript==
// @name e621/danbooru tag extractor
// @namespace http://tampermonkey.net/
// @version 1.2
// @description copy tags from e621
// @author NorthPolarise
// @include https://e621.net/posts/*
// @include https://danbooru.donmai.us/posts/*
// @grant none
// @license GPLv3
// ==/UserScript==
(function() {
'use strict';
// 添加 CSS 样式
const style = document.createElement('style');
style.textContent = `
.copy-button {
background-color: #2e51a2;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.copy-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
.copy-button:active {
transform: translateY(0);
}
.copy-button::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: -100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
transition: 0.5s;
}
.copy-button.clicked::after {
left: 100%;
}
.success-animation {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0);
width: 20px;
height: 20px;
background-color: #4CAF50;
border-radius: 50%;
opacity: 0;
}
.copy-button.success .success-animation {
animation: successPop 0.5s ease-out forwards;
}
@keyframes successPop {
0% {
transform: translate(-50%, -50%) scale(0);
opacity: 1;
}
50% {
transform: translate(-50%, -50%) scale(1.2);
opacity: 0.5;
}
100% {
transform: translate(-50%, -50%) scale(1);
opacity: 0;
}
}
.tag-row {
transition: transform 0.2s ease, box-shadow 0.2s ease;
position: relative;
z-index: 1;
}
.tag-row:hover {
transform: translateX(5px);
box-shadow: -2px 2px 5px rgba(0,0,0,0.2);
}
.tag-row.dragging {
opacity: 0.5;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
z-index: 10;
}
.tag-row.drag-over {
transform: translateY(40px);
}
.tag-row.drag-over-up {
transform: translateY(-40px);
}
.placeholder {
height: 40px;
background-color: rgba(46, 81, 162, 0.2);
border: 2px dashed #2e51a2;
border-radius: 3px;
margin: 5px 0;
transition: all 0.2s ease;
}
`;
document.head.appendChild(style);
createInterface();
// 加载保存的选择状态和顺序
function loadSavedState(tagList) {
const savedOrder = JSON.parse(localStorage.getItem('tagOrder') || '[]');
const savedChecked = JSON.parse(localStorage.getItem('tagChecked') || '{}');
if (savedOrder.length > 0) {
// 按保存的顺序重新排列标签
savedOrder.forEach(tagName => {
const tagRow = Array.from(tagList.children).find(row =>
row.querySelector('span').textContent === tagName
);
if (tagRow) {
tagList.appendChild(tagRow);
}
});
}
// 恢复选中状态
Array.from(tagList.children).forEach(tagRow => {
const label = tagRow.querySelector('span').textContent;
const checkbox = tagRow.querySelector('input');
if (savedChecked[label] !== undefined) {
checkbox.checked = savedChecked[label];
}
});
}
// 保存当前状态
function saveCurrentState(tagList) {
const currentOrder = Array.from(tagList.children).map(row =>
row.querySelector('span').textContent
);
const currentChecked = {};
Array.from(tagList.children).forEach(row => {
const label = row.querySelector('span').textContent;
currentChecked[label] = row.querySelector('input').checked;
});
localStorage.setItem('tagOrder', JSON.stringify(currentOrder));
localStorage.setItem('tagChecked', JSON.stringify(currentChecked));
}
function createInterface() {
// 创建主容器
const container = document.createElement('div');
container.style.position = 'fixed';
container.style.bottom = '20px';
container.style.right = '10px';
container.style.zIndex = '9999';
container.style.backgroundColor = '#284a81';
container.style.borderRadius = '5px';
container.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
// 创建头部(始终可见的部分)
const header = document.createElement('div');
header.style.padding = '10px';
header.style.borderBottom = '1px solid #2e51a2';
header.style.display = 'flex';
header.style.gap = '10px';
header.style.alignItems = 'center';
// 创建展开/收起按钮
const toggleButton = document.createElement('button');
toggleButton.textContent = '展开';
toggleButton.className = 'copy-button';
toggleButton.style.padding = '5px 10px';
toggleButton.style.backgroundColor = '#2e51a2';
toggleButton.style.color = 'white';
toggleButton.style.border = 'none';
toggleButton.style.borderRadius = '3px';
toggleButton.style.cursor = 'pointer';
// 创建复制按钮
const copyButton = document.createElement('button');
copyButton.textContent = '复制标签';
copyButton.className = 'copy-button';
copyButton.style.padding = '5px 10px';
copyButton.style.backgroundColor = '#2e51a2';
copyButton.style.color = 'white';
copyButton.style.border = 'none';
copyButton.style.borderRadius = '3px';
copyButton.style.cursor = 'pointer';
// 创建标签列表容器
const content = document.createElement('div');
content.style.display = 'none';
content.style.padding = '10px';
const tagList = document.createElement('div');
tagList.style.display = 'flex';
tagList.style.flexDirection = 'column';
tagList.style.gap = '5px';
const tagTypes = [
{ id: 'general', name: '一般标签 (General)', class: 'tag-type-0' },
{ id: 'artist', name: '艺术家标签 (Artist)', class: 'tag-type-1' },
{ id: 'character', name: '角色标签 (Character)', class: 'tag-type-4' },
{ id: 'species', name: '物种标签 (Species)', class: 'tag-type-5' },
{ id: 'copyright', name: '版权标签 (Copyright)', class: 'tag-type-3' },
{ id: 'meta', name: '元标签 (Meta)', class: 'tag-type-7' }
];
tagTypes.forEach((type, index) => {
const tagRow = document.createElement('div');
tagRow.className = 'tag-row';
tagRow.draggable = true;
tagRow.style.display = 'flex';
tagRow.style.alignItems = 'center';
tagRow.style.padding = '5px';
tagRow.style.backgroundColor = '#1d3b8f';
tagRow.style.borderRadius = '3px';
tagRow.style.cursor = 'move';
tagRow.dataset.class = type.class;
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.style.marginRight = '10px';
// 添加 change 事件监听器
checkbox.addEventListener('change', () => {
saveCurrentState(tagList);
});
const label = document.createElement('span');
label.textContent = type.name;
label.style.color = 'white';
label.style.flex = '1';
tagRow.appendChild(checkbox);
tagRow.appendChild(label);
tagList.appendChild(tagRow);
// 添加拖拽事件
tagRow.addEventListener('dragstart', handleDragStart);
tagRow.addEventListener('dragover', handleDragOver);
tagRow.addEventListener('dragend', handleDragEnd)
tagRow.addEventListener('drop', handleDrop);
tagRow.addEventListener('dragenter', (e) => e.preventDefault());
});
// 事件处理
toggleButton.addEventListener('click', function() {
const isHidden = content.style.display === 'none';
content.style.display = isHidden ? 'block' : 'none';
toggleButton.textContent = isHidden ? '收起' : '展开';
});
copyButton.addEventListener('click', function() {
extractTags();
copyButton.style.backgroundColor = '#1d3b8f';
setTimeout(() => {
copyButton.style.backgroundColor = '#2e51a2';
}, 200);
});
// 加载保存的状态
loadSavedState(tagList);
// 组装界面
header.appendChild(toggleButton);
header.appendChild(copyButton);
content.appendChild(tagList);
container.appendChild(header);
container.appendChild(content);
document.body.appendChild(container);
}
let dragSrcElement = null;
let placeholder = null;
function createPlaceholder() {
const div = document.createElement('div');
div.className = 'placeholder';
return div;
}
function handleDragStart(e) {
dragSrcElement = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.outerHTML);
// 创建占位符
placeholder = createPlaceholder();
// 添加拖动样式
this.classList.add('dragging');
// 延迟添加占位符,使动画更流畅
requestAnimationFrame(() => {
this.parentNode.insertBefore(placeholder, this);
this.style.display = 'none';
});
}
function handleDragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
const tagRow = this;
if (tagRow === dragSrcElement || tagRow === placeholder) {
return;
}
// 获取鼠标在元素上的相对位置
const rect = tagRow.getBoundingClientRect();
const midpoint = rect.top + (rect.height / 2);
const moveUp = e.clientY < midpoint;
// 移除所有其他元素的过渡类
document.querySelectorAll('.tag-row').forEach(row => {
row.classList.remove('drag-over', 'drag-over-up');
});
// 移动占位符
if (placeholder) {
if (moveUp) {
tagRow.parentNode.insertBefore(placeholder, tagRow);
} else {
tagRow.parentNode.insertBefore(placeholder, tagRow.nextSibling);
}
}
return false;
}
function handleDragEnd(e) {
this.classList.remove('dragging');
this.style.display = '';
// 移除所有过渡类
document.querySelectorAll('.tag-row').forEach(row => {
row.classList.remove('drag-over', 'drag-over-up');
});
// 移除占位符
if (placeholder && placeholder.parentNode) {
placeholder.parentNode.insertBefore(this, placeholder);
placeholder.remove();
}
placeholder = null;
dragSrcElement = null;
}
function handleDrop(e) {
e.preventDefault();
e.stopPropagation();
if (dragSrcElement !== this) {
// 位置已经由 dragover 事件处理,这里只需确保元素可见
dragSrcElement.style.display = '';
}
// 保存当前状态
const tagList = this.parentNode;
saveCurrentState(tagList);
return false;
}
// 修改复制函数
function extractTags() {
const tagContainer = document.querySelector('#tag-list');
if (!tagContainer) {
alert('未找到标签容器!');
return;
}
let allTags = [];
const tagClasses = {
'general': '.general-tag-list .search-tag',
'artist': '.artist-tag-list .search-tag',
'character': '.character-tag-list .search-tag',
'species': '.species-tag-list .search-tag',
'copyright': '.copyright-tag-list .search-tag',
'meta': '.meta-tag-list .search-tag'
};
const tagRows = document.querySelectorAll('.tag-row');
tagRows.forEach(row => {
if (row.querySelector('input').checked) {
const label = row.querySelector('span').textContent;
let type = '';
if (label.includes('一般标签')) type = 'general';
else if (label.includes('艺术家标签')) type = 'artist';
else if (label.includes('角色标签')) type = 'character';
else if (label.includes('物种标签')) type = 'species';
else if (label.includes('版权标签')) type = 'copyright';
else if (label.includes('元标签')) type = 'meta';
if (type && tagClasses[type]) {
const tags = Array.from(document.querySelectorAll(tagClasses[type]))
.map(tag => tag.textContent.trim())
.filter(tag => tag)
.sort();
allTags = allTags.concat(tags);
}
}
});
if (allTags.length === 0) {
alert('请至少选择一个标签类型!');
return;
}
const tagsText = allTags.join(',') + ',';
navigator.clipboard.writeText(tagsText).then(() => {
const copyButton = document.querySelector('#copy-button');
if (copyButton) {
// 添加点击动画效果
copyButton.classList.add('clicked');
copyButton.classList.add('success');
// 创建成功动画元素
const successAnim = document.createElement('div');
successAnim.className = 'success-animation';
copyButton.appendChild(successAnim);
// 重置动画状态
setTimeout(() => {
copyButton.classList.remove('clicked');
copyButton.classList.remove('success');
if (successAnim.parentNode === copyButton) {
copyButton.removeChild(successAnim);
}
}, 1000);
}
}).catch(err => {
console.error('复制失败:', err);
alert('复制失败,请手动复制:\n' + tagsText);
});
}
// 创建按钮
function createCopyButton() {
const button = document.createElement('button');
button.id = 'copy-button';
button.className = 'copy-button';
button.textContent = '复制标签';
button.onclick = extractTags;
return button;
}
})();