[GC] - Virtupets.net Quest Calculator

Calculate cost of quests using Virtupets API without having to check each item and do mental math. Staff approved script via ticket.

当前为 2025-05-05 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name [GC] - Virtupets.net Quest Calculator
  3. // @namespace https://greasyfork.org/en/users/1225524-kaitlin
  4. // @grant GM.getValue
  5. // @grant GM.setValue
  6. // @match https://www.grundos.cafe/winter/snowfaerie/*
  7. // @match https://www.grundos.cafe/island/kitchen/*
  8. // @match https://www.grundos.cafe/halloween/esophagor/*
  9. // @match https://www.grundos.cafe/halloween/witchtower/*
  10. // @version 1.2.0
  11. // @license MIT
  12. // @description Calculate cost of quests using Virtupets API without having to check each item and do mental math. Staff approved script via ticket.
  13. // @author Cupkait
  14. // @require https://update.greasyfork.org/scripts/512407/1463866/GC%20-%20Virtupets%20API%20library.js
  15. // @icon https://i.imgur.com/4Hm2e6z.png
  16. // ==/UserScript==
  17.  
  18.  
  19. const path = window.location.pathname;
  20.  
  21. async function fetchQuestData(questDiv) {
  22. const questList = document.querySelectorAll('.quest_text').length > 0
  23. ? document.querySelectorAll('.quest_text')
  24. : document.querySelectorAll('.quest-item').length > 0
  25. ? document.querySelectorAll('.quest-item')
  26. : document.querySelectorAll('.centered-item');
  27.  
  28. // Extract items in their original order, preserving duplicates
  29. const questArray = Array.from(questList).map(item => item.querySelector('strong').textContent);
  30.  
  31. // Get unique items for API call
  32. const uniqueItems = [...new Set(questArray)];
  33.  
  34. try {
  35. const response = await bulkShopWizardPrices(uniqueItems);
  36. const data = await response.json();
  37.  
  38. // Convert API data to a map for easier lookup
  39. const priceMap = {};
  40. data.forEach(item => {
  41. priceMap[item.name] = {
  42. price: item.price,
  43. time: item.time
  44. };
  45. });
  46.  
  47. // Create table with original order and proper counting of duplicates
  48. const tableHTML = createTableHTML(questArray, priceMap);
  49. questDiv.innerHTML = tableHTML;
  50. } catch (error) {
  51. console.error('Error:', error);
  52. questDiv.innerHTML = `<p>Error fetching quest data. Please try again later.</p>`;
  53. }
  54. }
  55.  
  56. function createTableHTML(orderedItems, priceMap) {
  57. let tableHTML = '<table><tr><th colspan="4" style="background-color: #4abdb8; font-size: 16px;"><strong>Virtupets.net Quest Calculator</strong></th></tr>';
  58. tableHTML += '<tr><th>Item Name</th><th>Quantity</th><th>Price Each</th><th>Date Priced</th></tr>';
  59.  
  60. let totalPrice = 0;
  61.  
  62. // Create a map to track items we've already added to the table
  63. const itemsInTable = new Map();
  64.  
  65. // Process items in their original order
  66. orderedItems.forEach(itemName => {
  67. const itemData = priceMap[itemName];
  68. if (!itemData) return; // Skip if no price data found
  69.  
  70. const date = new Date(itemData.time);
  71. const formattedDate = `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`;
  72.  
  73. // Each occurrence of the item contributes to the total price
  74. totalPrice += itemData.price;
  75.  
  76. // If we've already added this item to the table in its original order position
  77. if (itemsInTable.has(itemName)) {
  78. // Update quantity
  79. const existingRow = itemsInTable.get(itemName);
  80. existingRow.quantity += 1;
  81. existingRow.totalItemPrice += itemData.price;
  82. } else {
  83. // Add new item to tracking
  84. itemsInTable.set(itemName, {
  85. name: itemName,
  86. quantity: 1,
  87. priceEach: itemData.price,
  88. totalItemPrice: itemData.price,
  89. date: formattedDate
  90. });
  91. }
  92. });
  93.  
  94. // Add rows to table in original order
  95. for (const itemName of orderedItems) {
  96. // Only add each unique item once
  97. if (itemsInTable.has(itemName)) {
  98. const item = itemsInTable.get(itemName);
  99. tableHTML += `<tr>
  100. <td>${item.name}</td>
  101. <td>${item.quantity}</td>
  102. <td>${item.priceEach.toLocaleString()} NP</td>
  103. <td>${item.date}</td>
  104. </tr>`;
  105.  
  106. // Remove the item so we don't add it again
  107. itemsInTable.delete(itemName);
  108. }
  109. }
  110.  
  111. tableHTML += `<tr><td colspan="3"><strong>Total Estimated Cost: ${totalPrice.toLocaleString()} NP</strong></td><td></td></tr>`;
  112. tableHTML += '</table><span class="disclaimer">Prices always subject to change. To update an outdated price, download <a href="https://greasyfork.org/en/scripts/490596-gc-virtupets-data-collector">this script</a> before searching it on the Shop Wizard. Report errors or suggestions to @Cupkait!</span>';
  113. return tableHTML;
  114. }
  115.  
  116. function appendQuestDiv(anchor) {
  117. const questDiv = document.createElement('div');
  118. questDiv.classList.add('quest-div');
  119. anchor.append(questDiv);
  120. return questDiv;
  121. }
  122.  
  123. if (path.includes('/accept/')) {
  124. const anchor = document.querySelector('.itemList').previousElementSibling;
  125. const questDiv = appendQuestDiv(anchor);
  126. fetchQuestData(questDiv);
  127.  
  128. } else if (path.includes('/complete/') && document.querySelector('#page_content .flex-column').textContent.includes("Deadline")) {
  129. const anchor = document.querySelector('#quest_grid').previousElementSibling;
  130. const questDiv = appendQuestDiv(anchor);
  131. fetchQuestData(questDiv);
  132.  
  133. } else if (path.includes('/complete/')) {
  134. // do nothing, all done!
  135.  
  136. } else if (path.includes('/snowfaerie/')) {
  137. const anchor = document.querySelector('#taelia_grid').previousElementSibling;
  138. const questDiv = appendQuestDiv(anchor);
  139. fetchQuestData(questDiv);
  140.  
  141. } else {
  142. const anchor = document.querySelector('.itemList').previousElementSibling;
  143. const questDiv = appendQuestDiv(anchor);
  144. fetchQuestData(questDiv);
  145. }
  146.  
  147. const questStyle = document.createElement('style');
  148. questStyle.innerHTML = `
  149. .quest-div {
  150. display: grid;
  151. border: 2px solid black;
  152. margin-top: 20px;
  153. margin-left: 30px;
  154. margin-right: 30px;
  155. }
  156.  
  157. .quest-div td {
  158. padding: 5px;
  159. }
  160.  
  161. .disclaimer {
  162. font-style:italic;
  163. font-size:10px;
  164. margin-top:-5px;
  165. margin-bottom:2px;
  166. padding-left:10%;
  167. padding-right:10%;
  168. }
  169. `;
  170.  
  171. document.body.append(questStyle);