Sleazy Fork is available in English.

e621/danbooru tag extractor

copy tags from e621

질문, 리뷰하거나, 이 스크립트를 신고하세요.
  1. // ==UserScript==
  2. // @name e621/danbooru tag extractor
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2
  5. // @description copy tags from e621
  6. // @author NorthPolarise
  7. // @include https://e621.net/posts/*
  8. // @include https://danbooru.donmai.us/posts/*
  9. // @grant none
  10. // @license GPLv3
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. // 添加 CSS 样式
  17. const style = document.createElement('style');
  18. style.textContent = `
  19. .copy-button {
  20. background-color: #2e51a2;
  21. color: white;
  22. border: none;
  23. padding: 10px 20px;
  24. border-radius: 5px;
  25. cursor: pointer;
  26. font-size: 14px;
  27. font-weight: 500;
  28. position: relative;
  29. overflow: hidden;
  30. transition: all 0.3s ease;
  31. box-shadow: 0 2px 5px rgba(0,0,0,0.2);
  32. }
  33.  
  34. .copy-button:hover {
  35. transform: translateY(-2px);
  36. box-shadow: 0 4px 8px rgba(0,0,0,0.2);
  37. }
  38.  
  39. .copy-button:active {
  40. transform: translateY(0);
  41. }
  42.  
  43. .copy-button::after {
  44. content: '';
  45. position: absolute;
  46. width: 100%;
  47. height: 100%;
  48. top: 0;
  49. left: -100%;
  50. background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
  51. transition: 0.5s;
  52. }
  53.  
  54. .copy-button.clicked::after {
  55. left: 100%;
  56. }
  57.  
  58. .success-animation {
  59. position: absolute;
  60. top: 50%;
  61. left: 50%;
  62. transform: translate(-50%, -50%) scale(0);
  63. width: 20px;
  64. height: 20px;
  65. background-color: #4CAF50;
  66. border-radius: 50%;
  67. opacity: 0;
  68. }
  69.  
  70. .copy-button.success .success-animation {
  71. animation: successPop 0.5s ease-out forwards;
  72. }
  73.  
  74. @keyframes successPop {
  75. 0% {
  76. transform: translate(-50%, -50%) scale(0);
  77. opacity: 1;
  78. }
  79. 50% {
  80. transform: translate(-50%, -50%) scale(1.2);
  81. opacity: 0.5;
  82. }
  83. 100% {
  84. transform: translate(-50%, -50%) scale(1);
  85. opacity: 0;
  86. }
  87. }
  88. .tag-row {
  89. transition: transform 0.2s ease, box-shadow 0.2s ease;
  90. position: relative;
  91. z-index: 1;
  92. }
  93.  
  94. .tag-row:hover {
  95. transform: translateX(5px);
  96. box-shadow: -2px 2px 5px rgba(0,0,0,0.2);
  97. }
  98.  
  99. .tag-row.dragging {
  100. opacity: 0.5;
  101. box-shadow: 0 5px 15px rgba(0,0,0,0.3);
  102. z-index: 10;
  103. }
  104.  
  105. .tag-row.drag-over {
  106. transform: translateY(40px);
  107. }
  108.  
  109. .tag-row.drag-over-up {
  110. transform: translateY(-40px);
  111. }
  112.  
  113. .placeholder {
  114. height: 40px;
  115. background-color: rgba(46, 81, 162, 0.2);
  116. border: 2px dashed #2e51a2;
  117. border-radius: 3px;
  118. margin: 5px 0;
  119. transition: all 0.2s ease;
  120. }
  121. `;
  122. document.head.appendChild(style);
  123.  
  124. createInterface();
  125.  
  126. // 加载保存的选择状态和顺序
  127. function loadSavedState(tagList) {
  128. const savedOrder = JSON.parse(localStorage.getItem('tagOrder') || '[]');
  129. const savedChecked = JSON.parse(localStorage.getItem('tagChecked') || '{}');
  130.  
  131. if (savedOrder.length > 0) {
  132. // 按保存的顺序重新排列标签
  133. savedOrder.forEach(tagName => {
  134. const tagRow = Array.from(tagList.children).find(row =>
  135. row.querySelector('span').textContent === tagName
  136. );
  137. if (tagRow) {
  138. tagList.appendChild(tagRow);
  139. }
  140. });
  141. }
  142.  
  143. // 恢复选中状态
  144. Array.from(tagList.children).forEach(tagRow => {
  145. const label = tagRow.querySelector('span').textContent;
  146. const checkbox = tagRow.querySelector('input');
  147. if (savedChecked[label] !== undefined) {
  148. checkbox.checked = savedChecked[label];
  149. }
  150. });
  151. }
  152.  
  153. // 保存当前状态
  154. function saveCurrentState(tagList) {
  155. const currentOrder = Array.from(tagList.children).map(row =>
  156. row.querySelector('span').textContent
  157. );
  158. const currentChecked = {};
  159. Array.from(tagList.children).forEach(row => {
  160. const label = row.querySelector('span').textContent;
  161. currentChecked[label] = row.querySelector('input').checked;
  162. });
  163.  
  164. localStorage.setItem('tagOrder', JSON.stringify(currentOrder));
  165. localStorage.setItem('tagChecked', JSON.stringify(currentChecked));
  166. }
  167.  
  168. function createInterface() {
  169. // 创建主容器
  170. const container = document.createElement('div');
  171. container.style.position = 'fixed';
  172. container.style.bottom = '20px';
  173. container.style.right = '10px';
  174. container.style.zIndex = '9999';
  175. container.style.backgroundColor = '#284a81';
  176. container.style.borderRadius = '5px';
  177. container.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
  178.  
  179. // 创建头部(始终可见的部分)
  180. const header = document.createElement('div');
  181. header.style.padding = '10px';
  182. header.style.borderBottom = '1px solid #2e51a2';
  183. header.style.display = 'flex';
  184. header.style.gap = '10px';
  185. header.style.alignItems = 'center';
  186.  
  187. // 创建展开/收起按钮
  188. const toggleButton = document.createElement('button');
  189. toggleButton.textContent = '展开';
  190. toggleButton.className = 'copy-button';
  191. toggleButton.style.padding = '5px 10px';
  192. toggleButton.style.backgroundColor = '#2e51a2';
  193. toggleButton.style.color = 'white';
  194. toggleButton.style.border = 'none';
  195. toggleButton.style.borderRadius = '3px';
  196. toggleButton.style.cursor = 'pointer';
  197.  
  198. // 创建复制按钮
  199. const copyButton = document.createElement('button');
  200. copyButton.textContent = '复制标签';
  201. copyButton.className = 'copy-button';
  202. copyButton.style.padding = '5px 10px';
  203. copyButton.style.backgroundColor = '#2e51a2';
  204. copyButton.style.color = 'white';
  205. copyButton.style.border = 'none';
  206. copyButton.style.borderRadius = '3px';
  207. copyButton.style.cursor = 'pointer';
  208.  
  209. // 创建标签列表容器
  210. const content = document.createElement('div');
  211. content.style.display = 'none';
  212. content.style.padding = '10px';
  213.  
  214. const tagList = document.createElement('div');
  215. tagList.style.display = 'flex';
  216. tagList.style.flexDirection = 'column';
  217. tagList.style.gap = '5px';
  218.  
  219. const tagTypes = [
  220. { id: 'general', name: '一般标签 (General)', class: 'tag-type-0' },
  221. { id: 'artist', name: '艺术家标签 (Artist)', class: 'tag-type-1' },
  222. { id: 'character', name: '角色标签 (Character)', class: 'tag-type-4' },
  223. { id: 'species', name: '物种标签 (Species)', class: 'tag-type-5' },
  224. { id: 'copyright', name: '版权标签 (Copyright)', class: 'tag-type-3' },
  225. { id: 'meta', name: '元标签 (Meta)', class: 'tag-type-7' }
  226. ];
  227.  
  228. tagTypes.forEach((type, index) => {
  229. const tagRow = document.createElement('div');
  230. tagRow.className = 'tag-row';
  231. tagRow.draggable = true;
  232. tagRow.style.display = 'flex';
  233. tagRow.style.alignItems = 'center';
  234. tagRow.style.padding = '5px';
  235. tagRow.style.backgroundColor = '#1d3b8f';
  236. tagRow.style.borderRadius = '3px';
  237. tagRow.style.cursor = 'move';
  238. tagRow.dataset.class = type.class;
  239.  
  240. const checkbox = document.createElement('input');
  241. checkbox.type = 'checkbox';
  242. checkbox.style.marginRight = '10px';
  243. // 添加 change 事件监听器
  244. checkbox.addEventListener('change', () => {
  245. saveCurrentState(tagList);
  246. });
  247.  
  248.  
  249. const label = document.createElement('span');
  250. label.textContent = type.name;
  251. label.style.color = 'white';
  252. label.style.flex = '1';
  253.  
  254. tagRow.appendChild(checkbox);
  255. tagRow.appendChild(label);
  256. tagList.appendChild(tagRow);
  257.  
  258. // 添加拖拽事件
  259. tagRow.addEventListener('dragstart', handleDragStart);
  260. tagRow.addEventListener('dragover', handleDragOver);
  261. tagRow.addEventListener('dragend', handleDragEnd)
  262. tagRow.addEventListener('drop', handleDrop);
  263. tagRow.addEventListener('dragenter', (e) => e.preventDefault());
  264. });
  265.  
  266. // 事件处理
  267. toggleButton.addEventListener('click', function() {
  268. const isHidden = content.style.display === 'none';
  269. content.style.display = isHidden ? 'block' : 'none';
  270. toggleButton.textContent = isHidden ? '收起' : '展开';
  271. });
  272.  
  273. copyButton.addEventListener('click', function() {
  274. extractTags();
  275. copyButton.style.backgroundColor = '#1d3b8f';
  276. setTimeout(() => {
  277. copyButton.style.backgroundColor = '#2e51a2';
  278. }, 200);
  279. });
  280.  
  281. // 加载保存的状态
  282. loadSavedState(tagList);
  283.  
  284. // 组装界面
  285. header.appendChild(toggleButton);
  286. header.appendChild(copyButton);
  287. content.appendChild(tagList);
  288. container.appendChild(header);
  289. container.appendChild(content);
  290. document.body.appendChild(container);
  291. }
  292.  
  293. let dragSrcElement = null;
  294. let placeholder = null;
  295.  
  296. function createPlaceholder() {
  297. const div = document.createElement('div');
  298. div.className = 'placeholder';
  299. return div;
  300. }
  301.  
  302. function handleDragStart(e) {
  303. dragSrcElement = this;
  304. e.dataTransfer.effectAllowed = 'move';
  305. e.dataTransfer.setData('text/html', this.outerHTML);
  306.  
  307. // 创建占位符
  308. placeholder = createPlaceholder();
  309.  
  310. // 添加拖动样式
  311. this.classList.add('dragging');
  312.  
  313. // 延迟添加占位符,使动画更流畅
  314. requestAnimationFrame(() => {
  315. this.parentNode.insertBefore(placeholder, this);
  316. this.style.display = 'none';
  317. });
  318. }
  319.  
  320. function handleDragOver(e) {
  321. e.preventDefault();
  322. e.dataTransfer.dropEffect = 'move';
  323.  
  324. const tagRow = this;
  325. if (tagRow === dragSrcElement || tagRow === placeholder) {
  326. return;
  327. }
  328.  
  329. // 获取鼠标在元素上的相对位置
  330. const rect = tagRow.getBoundingClientRect();
  331. const midpoint = rect.top + (rect.height / 2);
  332. const moveUp = e.clientY < midpoint;
  333.  
  334. // 移除所有其他元素的过渡类
  335. document.querySelectorAll('.tag-row').forEach(row => {
  336. row.classList.remove('drag-over', 'drag-over-up');
  337. });
  338.  
  339. // 移动占位符
  340. if (placeholder) {
  341. if (moveUp) {
  342. tagRow.parentNode.insertBefore(placeholder, tagRow);
  343. } else {
  344. tagRow.parentNode.insertBefore(placeholder, tagRow.nextSibling);
  345. }
  346. }
  347.  
  348. return false;
  349. }
  350.  
  351. function handleDragEnd(e) {
  352. this.classList.remove('dragging');
  353. this.style.display = '';
  354.  
  355. // 移除所有过渡类
  356. document.querySelectorAll('.tag-row').forEach(row => {
  357. row.classList.remove('drag-over', 'drag-over-up');
  358. });
  359.  
  360. // 移除占位符
  361. if (placeholder && placeholder.parentNode) {
  362. placeholder.parentNode.insertBefore(this, placeholder);
  363. placeholder.remove();
  364. }
  365.  
  366. placeholder = null;
  367. dragSrcElement = null;
  368. }
  369.  
  370. function handleDrop(e) {
  371. e.preventDefault();
  372. e.stopPropagation();
  373.  
  374. if (dragSrcElement !== this) {
  375. // 位置已经由 dragover 事件处理,这里只需确保元素可见
  376. dragSrcElement.style.display = '';
  377. }
  378.  
  379. // 保存当前状态
  380. const tagList = this.parentNode;
  381.  
  382. saveCurrentState(tagList);
  383.  
  384. return false;
  385. }
  386.  
  387. // 修改复制函数
  388. function extractTags() {
  389. const tagContainer = document.querySelector('#tag-list');
  390. if (!tagContainer) {
  391. alert('未找到标签容器!');
  392. return;
  393. }
  394.  
  395. let allTags = [];
  396. const tagClasses = {
  397. 'general': '.general-tag-list .search-tag',
  398. 'artist': '.artist-tag-list .search-tag',
  399. 'character': '.character-tag-list .search-tag',
  400. 'species': '.species-tag-list .search-tag',
  401. 'copyright': '.copyright-tag-list .search-tag',
  402. 'meta': '.meta-tag-list .search-tag'
  403. };
  404.  
  405. const tagRows = document.querySelectorAll('.tag-row');
  406. tagRows.forEach(row => {
  407. if (row.querySelector('input').checked) {
  408. const label = row.querySelector('span').textContent;
  409. let type = '';
  410.  
  411. if (label.includes('一般标签')) type = 'general';
  412. else if (label.includes('艺术家标签')) type = 'artist';
  413. else if (label.includes('角色标签')) type = 'character';
  414. else if (label.includes('物种标签')) type = 'species';
  415. else if (label.includes('版权标签')) type = 'copyright';
  416. else if (label.includes('元标签')) type = 'meta';
  417.  
  418. if (type && tagClasses[type]) {
  419. const tags = Array.from(document.querySelectorAll(tagClasses[type]))
  420. .map(tag => tag.textContent.trim())
  421. .filter(tag => tag)
  422. .sort();
  423. allTags = allTags.concat(tags);
  424. }
  425. }
  426. });
  427.  
  428. if (allTags.length === 0) {
  429. alert('请至少选择一个标签类型!');
  430. return;
  431. }
  432.  
  433. const tagsText = allTags.join(',') + ',';
  434.  
  435. navigator.clipboard.writeText(tagsText).then(() => {
  436. const copyButton = document.querySelector('#copy-button');
  437. if (copyButton) {
  438. // 添加点击动画效果
  439. copyButton.classList.add('clicked');
  440. copyButton.classList.add('success');
  441.  
  442. // 创建成功动画元素
  443. const successAnim = document.createElement('div');
  444. successAnim.className = 'success-animation';
  445. copyButton.appendChild(successAnim);
  446.  
  447. // 重置动画状态
  448. setTimeout(() => {
  449. copyButton.classList.remove('clicked');
  450. copyButton.classList.remove('success');
  451. if (successAnim.parentNode === copyButton) {
  452. copyButton.removeChild(successAnim);
  453. }
  454. }, 1000);
  455. }
  456. }).catch(err => {
  457. console.error('复制失败:', err);
  458. alert('复制失败,请手动复制:\n' + tagsText);
  459. });
  460. }
  461.  
  462. // 创建按钮
  463. function createCopyButton() {
  464. const button = document.createElement('button');
  465. button.id = 'copy-button';
  466. button.className = 'copy-button';
  467. button.textContent = '复制标签';
  468. button.onclick = extractTags;
  469. return button;
  470. }
  471. })();