VGMdb Info Generator

VGMdb BBcode-style album information generator

  1. // ==UserScript==
  2. // @name VGMdb Info Generator
  3. // @name:zh-CN VGMdb 信息生成
  4. // @namespace https://vgmdb.net/
  5. // @version 0.3.0
  6. // @description VGMdb BBcode-style album information generator
  7. // @description:zh-cn VGMdb BBcode 样式专辑信息生成
  8. // @author gkouen
  9. // @license MIT
  10. // @homepage https://blog.cya.moe/
  11. // @match *://vgmdb.net/album/*
  12. // @icon https://vgmdb.net/favicon.ico
  13. // @grant GM_setClipboard
  14. // ==/UserScript==
  15.  
  16. (function () {
  17. 'use strict';
  18.  
  19. const discussSpan = document.querySelector('span.label.smallfont > span#albumtools');
  20. if (!discussSpan) return;
  21.  
  22. // Info button
  23. const generateInfoButton = document.createElement('a');
  24. generateInfoButton.textContent = 'Info';
  25. generateInfoButton.style.cursor = 'pointer';
  26. generateInfoButton.style.marginLeft = '0px';
  27. generateInfoButton.style.color = '#ceffff';
  28. generateInfoButton.addEventListener('click', (event) => {
  29. event.preventDefault();
  30. event.stopPropagation();
  31. generateInfo();
  32. });
  33.  
  34. // Format button
  35. const formatButton = document.createElement('a');
  36. formatButton.textContent = 'Format';
  37. formatButton.style.cursor = 'pointer';
  38. formatButton.style.marginLeft = '0px';
  39. formatButton.style.color = '#ceffff';
  40. formatButton.addEventListener('click', (event) => {
  41. event.preventDefault();
  42. event.stopPropagation();
  43. formatInfo(formatButton);
  44. });
  45.  
  46. const separator1 = document.createTextNode(' | ');
  47. const separator2 = document.createTextNode(' | ');
  48. discussSpan.appendChild(separator1);
  49. discussSpan.appendChild(generateInfoButton);
  50. discussSpan.appendChild(separator2);
  51. discussSpan.appendChild(formatButton);
  52.  
  53. const months = { Jan: '01', Feb: '02', Mar: '03', Apr: '04', May: '05', Jun: '06', Jul: '07', Aug: '08', Sep: '09', Oct: '10', Nov: '11', Dec: '12' };
  54.  
  55. function generateInfo() {
  56. try {
  57. const coverArt = document.querySelector('#coverart').style.backgroundImage.match(/url\("?(.*?)"?\)/)?.[1];
  58. const title = getAlbumTitle().replace(/^\s*\/|\/\s*$/g, '');
  59. const catalogNumber = getSiblingValue('Catalog Number');
  60. const releaseDate = getSiblingValue('Release Date');
  61. const formattedDate = formatDate(releaseDate);
  62.  
  63. const tracklistRows = document.querySelectorAll('#tracklist tr.rolebit');
  64. const tracklist = Array.from(tracklistRows)
  65. .map(row => {
  66. const trackNumber = row.querySelector('.label')?.textContent.trim();
  67. const trackName = row.querySelector('td[width="100%"]')?.textContent.trim();
  68. return `${trackNumber}\t${trackName}`;
  69. })
  70. .join('\n');
  71.  
  72. const resultText = `[quote]
  73. [img]${coverArt}[/img]
  74. [b]Title:[/b]${title}
  75. [b]Catalog number:[/b] ${catalogNumber}
  76. [b]Release date:[/b] ${formattedDate}
  77. [b]Tracklist[/b]:
  78. [code]
  79. ${tracklist}
  80. [/code]
  81. [/quote]`;
  82.  
  83. showTemporaryModal(resultText);
  84. } catch (error) {
  85. console.error('Error:', error);
  86. alert('Error occurred, please check the console log!');
  87. }
  88. }
  89.  
  90. function formatInfo(buttonElement) {
  91. try {
  92. const title = getAlbumTitle();
  93. const releaseDate = getSiblingValue('Release Date');
  94. const formattedDate = formatDateForClipboard(releaseDate);
  95.  
  96. const sanitizedTitle = sanitizeTitle(title);
  97.  
  98. const formattedText = `[${formattedDate}]${sanitizedTitle}`;
  99. GM_setClipboard(formattedText);
  100.  
  101. showTemporaryTooltip(buttonElement, 'Copied to clipboard');
  102. } catch (error) {
  103. console.error('Error:', error);
  104. }
  105. }
  106.  
  107. function getAlbumTitle() {
  108. const jpnTitle = document.querySelector('.albumtitle[lang="ja"]');
  109. const engTitle = document.querySelector('.albumtitle[lang="en"]');
  110. const title = jpnTitle?.textContent.trim() || eng?.textContent.trim() || 'Unknown';
  111. return title;
  112. }
  113.  
  114. function sanitizeTitle(title) {
  115. return title
  116. .replace(/^\s*\/|\/\s*$/g, '')
  117. .replace(/\//g, '/')
  118. .replace(/:/g, ':')
  119. .replace(/\*/g, '*')
  120. .replace(/\?/g, '?')
  121. .replace(/"/g, '"')
  122. .replace(/</g, '<')
  123. .replace(/>/g, '>')
  124. .replace(/\|/g, '|');
  125. }
  126.  
  127. function getSiblingValue(labelText) {
  128. const labelCell = Array.from(document.querySelectorAll('#album_infobit_large td'))
  129. .find(td => td.textContent.trim() === labelText);
  130. return labelCell ? labelCell.nextElementSibling.textContent.trim() : '';
  131. }
  132.  
  133. function formatDate(dateString) {
  134. if (!dateString) return 'Unknown Date';
  135. const [month, day, year] = dateString.split(' ');
  136. return `${year}/${months[month]}/${day.replace(',', '')}`;
  137. }
  138.  
  139. function formatDateForClipboard(dateString) {
  140. if (!dateString) return 'Unknown Date';
  141. const [month, day, year] = dateString.split(' ');
  142. return `${year.substring(2)}${months[month]}${day.replace(',', '')}`;
  143. }
  144.  
  145. function showTemporaryTooltip(buttonElement, message) {
  146. const tooltip = document.createElement('div');
  147. tooltip.textContent = message;
  148. tooltip.style.position = 'absolute';
  149. tooltip.style.backgroundColor = '#000';
  150. tooltip.style.color = '#fff';
  151. tooltip.style.padding = '5px 10px';
  152. tooltip.style.borderRadius = '5px';
  153. tooltip.style.fontSize = '12px';
  154. tooltip.style.opacity = '0';
  155. tooltip.style.transition = 'opacity 0.5s';
  156. tooltip.style.zIndex = '1000';
  157.  
  158. const rect = buttonElement.getBoundingClientRect();
  159. tooltip.style.left = `${rect.left + window.scrollX}px`;
  160. tooltip.style.top = `${rect.bottom + window.scrollY + 5}px`;
  161.  
  162. document.body.appendChild(tooltip);
  163.  
  164. requestAnimationFrame(() => {
  165. tooltip.style.opacity = '1';
  166. });
  167.  
  168. setTimeout(() => {
  169. tooltip.style.opacity = '0';
  170. setTimeout(() => {
  171. tooltip.remove();
  172. }, 500);
  173. }, 500);
  174. }
  175.  
  176. function showTemporaryModal(text) {
  177. const modal = document.createElement('div');
  178. modal.style.position = 'fixed';
  179. modal.style.top = '50%';
  180. modal.style.left = '50%';
  181. modal.style.transform = 'translate(-50%, -50%)';
  182. modal.style.backgroundColor = '#fff';
  183. modal.style.padding = '20px';
  184. modal.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
  185. modal.style.zIndex = '9999';
  186. modal.style.opacity = '0';
  187. modal.style.transition = 'opacity 0.5s';
  188.  
  189. const textarea = document.createElement('textarea');
  190. textarea.style.width = '500px';
  191. textarea.style.height = '300px';
  192. textarea.textContent = text;
  193.  
  194. const closeButton = document.createElement('button');
  195. closeButton.textContent = 'Close';
  196. closeButton.style.marginTop = '10px';
  197. closeButton.addEventListener('click', () => {
  198. modal.style.opacity = '0';
  199. setTimeout(() => {
  200. modal.remove();
  201. }, 500);
  202. });
  203.  
  204. modal.appendChild(textarea);
  205. modal.appendChild(closeButton);
  206. document.body.appendChild(modal);
  207.  
  208. requestAnimationFrame(() => {
  209. modal.style.opacity = '1';
  210. });
  211. }
  212. })();