BottleNeko WS Deck Output

output deck from bottleneko

  1. // ==UserScript==
  2. // @name BottleNeko WS Deck Output
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.2.0
  5. // @description output deck from bottleneko
  6. // @author Chatgpt 4.0
  7. // @match https://bottleneko.app/deck/*
  8. // @grant GM_xmlhttpRequest
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. async function downloadImage(url) {
  13. return new Promise((resolve, reject) => {
  14. GM_xmlhttpRequest({
  15. method: "GET",
  16. url: url,
  17. responseType: "blob",
  18. onload: function (response) {
  19. resolve(response.response);
  20. },
  21. onerror: function (err) {
  22. reject(err);
  23. }
  24. });
  25. });
  26. }
  27.  
  28. async function loadImage(blob) {
  29. return new Promise((resolve, reject) => {
  30. const img = new Image();
  31. img.src = URL.createObjectURL(blob);
  32. img.onload = () => resolve(img);
  33. img.onerror = (err) => reject(err);
  34. });
  35. }
  36.  
  37. async function processImage(image, num) {
  38. const canvas = document.createElement('canvas');
  39. const ctx = canvas.getContext('2d');
  40.  
  41. if (image.width > image.height) {
  42. canvas.width = 1400;
  43. canvas.height = 1000;
  44. ctx.rotate(Math.PI / 2);
  45. ctx.drawImage(image, 0, -1400, 1000, 1400);
  46. } else {
  47. canvas.width = 1000;
  48. canvas.height = 1400;
  49. ctx.drawImage(image, 0, 0, 1000, 1400);
  50. }
  51.  
  52. const processedImages = [];
  53. for (let i = 0; i < num; i++) {
  54. processedImages.push(canvas.toDataURL());
  55. }
  56.  
  57. return processedImages;
  58. }
  59.  
  60.  
  61. async function createDeckImage(images) {
  62. const canvas = document.createElement('canvas');
  63. const ctx = canvas.getContext('2d');
  64.  
  65. canvas.width = 10000;
  66. canvas.height = 7000;
  67.  
  68. let xPos = 0;
  69. let yPos = 0;
  70.  
  71. const imgPromises = images.map(async (image) => {
  72. return new Promise(async (resolve) => {
  73. const img = new Image();
  74. img.src = image;
  75. img.onload = () => {
  76. ctx.drawImage(img, xPos, yPos, 1000, 1400);
  77. xPos += 1000;
  78.  
  79. if (xPos >= 10000) {
  80. xPos = 0;
  81. yPos += 1400;
  82. }
  83. resolve();
  84. };
  85. });
  86. });
  87.  
  88. await Promise.all(imgPromises);
  89.  
  90. return new Promise((resolve) => {
  91. canvas.toBlob((blob) => {
  92. resolve(blob);
  93. }, 'image/jpeg');
  94. });
  95. }
  96.  
  97. function generateCardURL(input) {
  98. // 分隔输入的字符串并提取需要的部分
  99. let cardID = input.split(' ')[1];
  100.  
  101. // 转换为小写并替换 "/" 和 "-" 为 "_"
  102. let lowerCaseID = cardID.toLowerCase();
  103. lowerCaseID = lowerCaseID.replace(/[-/]/g, '_');
  104.  
  105. // 提取变量a和变量b
  106. let a = lowerCaseID.charAt(0);
  107. // 判斷是否包含 "ws02",然後相應地設定变量b 電擊文庫的path有特例
  108. let b;
  109. if (lowerCaseID.includes("ws02")) {
  110. b = a + '_' + lowerCaseID.split('_')[1];
  111. } else {
  112. b = lowerCaseID.split('_')[0] + '_' + lowerCaseID.split('_')[1];
  113. }
  114.  
  115. // 提取变量c
  116. let c = lowerCaseID;
  117.  
  118. // 生成并返回最终的URL
  119. return 'https://ws-tcg.com/wordpress/wp-content/images/cardlist/' + a + '/' + b + '/' + c + '.png';
  120. }
  121.  
  122. async function ButtonClickAction(zEvent) {
  123. const deckList = [];
  124. let deckImages = [];
  125.  
  126. var imgs = document.querySelectorAll('.deck-grid img');
  127. var quantities = document.querySelectorAll('.deck-grid .position-absolute');
  128.  
  129. for (var i = 0; i < imgs.length; i++) {
  130. var imgAlt = imgs[i].alt; // 圖片名稱是儲存在 alt 屬性中的
  131. var quantity = quantities[i].innerText.trim(); // 數量是儲存在 textContent 中的,我們用 trim() 來去除多餘的空白
  132. deckList.push({
  133. imageUrl: generateCardURL(imgAlt),
  134. num: quantity
  135. });
  136. }
  137. console.log(deckList);
  138.  
  139. for (const item of deckList) {
  140. const imageBlob = await downloadImage(item.imageUrl);
  141. const image = await loadImage(imageBlob);
  142. const processedImages = await processImage(image, item.num);
  143. deckImages = deckImages.concat(processedImages);
  144. }
  145.  
  146. const deckBlob = await createDeckImage(deckImages);
  147. const deckFileName = window.location.pathname.split('/').pop() + '.jpg';
  148.  
  149. const a = document.createElement('a');
  150. a.href = URL.createObjectURL(deckBlob);
  151. a.download = deckFileName;
  152. a.click();
  153. URL.revokeObjectURL(a.href);
  154. }
  155.  
  156. function addButtonWhenAvailable() {
  157. const viewBtnContainer = document.querySelector('.d-flex.align-items-center.flex-wrap.mr-5.mr-lg-0');
  158.  
  159. if (viewBtnContainer) {
  160. const deckImageButton = document.createElement('button');
  161. deckImageButton.setAttribute('type', 'button');
  162. deckImageButton.setAttribute('class', 'btn btn-warning btn-sm');
  163. deckImageButton.textContent = 'Create Deck Image';
  164. viewBtnContainer.appendChild(deckImageButton);
  165. deckImageButton.addEventListener("click", ButtonClickAction, false);
  166. } else {
  167. setTimeout(addButtonWhenAvailable, 500);
  168. }
  169. }
  170.  
  171. addButtonWhenAvailable();