VNDB JAST USA Enhancer

Enhances JAST app pages with data from VNDB.

  1. // ==UserScript==
  2. // @name VNDB JAST USA Enhancer
  3. // @namespace https://vndb.org/
  4. // @version 1.3
  5. // @description Enhances JAST app pages with data from VNDB.
  6. // @author darklinkpower
  7. // @match https://jastusa.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=jastusa.com
  9. // @grant GM_xmlhttpRequest
  10. // @connect api.vndb.org
  11. // @run-at document-end
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. const VN_LENGTH = {
  19. 0: { txt: 'Unknown', time: '', low: 0, high: 0 },
  20. 1: { txt: 'Very short', time: 'Less than 2 hours', low: 1, high: 2 * 60 },
  21. 2: { txt: 'Short', time: '2 - 10 hours', low: 2 * 60, high: 10 * 60 },
  22. 3: { txt: 'Medium', time: '10 - 30 hours', low: 10 * 60, high: 30 * 60 },
  23. 4: { txt: 'Long', time: '30 - 50 hours', low: 30 * 60, high: 50 * 60 },
  24. 5: { txt: 'Very long', time: 'More than 50 hours', low: 50 * 60, high: 32767 }
  25. };
  26. function formatMinutes(minutes) {
  27. if (minutes < 60) {
  28. return "" + minutes + "m";
  29. }
  30. return "" + Math.floor(minutes / 60) + "h" + (minutes % 60) + "m";
  31. }
  32. function getMinutesMatchingLength(minutes) {
  33. for (let key in VN_LENGTH) {
  34. const vnLenght = VN_LENGTH[key];
  35. if (minutes >= vnLenght.low && minutes < vnLenght.high) {
  36. return vnLenght;
  37. }
  38. }
  39. return VN_LENGTH[0];
  40. }
  41.  
  42. var CurrentAppID;
  43. function GetAppIDFromUrl() {
  44. var currentURL = window.location.href;
  45. var idPattern = /\/games\/([^/]+)/;
  46. var match = currentURL.match(idPattern);
  47.  
  48. if (match && match.length > 1) {
  49. return match[1];
  50. } else {
  51. console.log("Id not found from url");
  52. return null;
  53. }
  54. }
  55.  
  56. function GetCurrentAppID() {
  57. if (!CurrentAppID) {
  58. CurrentAppID = GetAppIDFromUrl();
  59. }
  60.  
  61. return CurrentAppID;
  62. }
  63.  
  64. function makeRow(rowClass, subtitle, linkText, linkUrl) {
  65. const row = document.createElement("div");
  66. row.className = "info-row";
  67.  
  68. const label = document.createElement("span");
  69. label.className = "info-row__label";
  70. label.textContent = subtitle;
  71.  
  72. const value = document.createElement("div");
  73. value.className = "info-row__value";
  74.  
  75. let linkEl;
  76. if (linkUrl) {
  77. linkEl = document.createElement("a");
  78. linkEl.className = "info-row__link";
  79. linkEl.textContent = linkText;
  80. linkEl.href = linkUrl;
  81. } else {
  82. linkEl = document.createElement("span");
  83. linkEl.className = "info-row__text";
  84. linkEl.textContent = linkText;
  85. }
  86.  
  87. value.appendChild(linkEl);
  88. row.appendChild(label);
  89. row.appendChild(value);
  90.  
  91. return row;
  92. }
  93.  
  94. function insertPanel(result) {
  95. let item = result.results[0];
  96.  
  97. const vndbIdRow = makeRow(
  98. "vndb_id",
  99. "Id",
  100. item.id,
  101. "https://vndb.org/" + item.id
  102. );
  103.  
  104. const rows = [
  105. vndbIdRow,
  106. ];
  107.  
  108. if (item.rating) {
  109. rows.push(makeRow(
  110. "vndb_rating",
  111. "Rating",
  112. "" + item.rating + " (" + item.votecount + ")"
  113. ));
  114. }
  115.  
  116. let lengthText = ""
  117. if (item.length_minutes && item.length_votes)
  118. {
  119. const timeDescription = getMinutesMatchingLength(item.length_minutes).txt
  120. const formattedMinutes = formatMinutes(item.length_minutes)
  121. lengthText = `${timeDescription} (${formattedMinutes} from ${item.length_votes} votes)`;
  122. }
  123. else if (item.length) {
  124. const mappedLength = VN_LENGTH[item.length];
  125. lengthText = mappedLength.txt;
  126. if (item.length != 0)
  127. {
  128. lengthText += ` (${mappedLength.time})`;
  129. }
  130. }
  131.  
  132. if (lengthText != "")
  133. {
  134. rows.push(makeRow(
  135. "vndb_length",
  136. "Play time",
  137. lengthText
  138. ));
  139. }
  140.  
  141. let vndbInfoContainer = document.createElement("div");
  142. vndbInfoContainer.className = "game-info vndb-infos";
  143.  
  144. let panelTitle = document.createElement('h3');
  145. panelTitle.classList.add('game-info__title');
  146. panelTitle.textContent = 'VNDB';
  147. vndbInfoContainer.appendChild(panelTitle);
  148.  
  149. let panelContent = document.createElement('div');
  150. panelContent.classList.add('game-info__content');
  151. vndbInfoContainer.appendChild(panelContent);
  152.  
  153. rows.forEach(function(row) {
  154. panelContent.appendChild(row);
  155. });
  156.  
  157. let gameInfoPanel = document.querySelector('.game-info.game-info--basic');
  158. if (gameInfoPanel) {
  159. gameInfoPanel.parentNode.insertBefore(vndbInfoContainer, gameInfoPanel);
  160. } else {
  161. console.log("Game info panel not found");
  162. }
  163. }
  164.  
  165. function fetchVNDBData() {
  166. let appId = GetCurrentAppID();
  167. if (appId == null) {
  168. return;
  169. }
  170.  
  171. GM_xmlhttpRequest({
  172. method: "POST",
  173. url: "https://api.vndb.org/kana/vn",
  174. data: JSON.stringify({
  175. "filters": ["release", "=", ["extlink", "=", ["jastusa", appId]]],
  176. "fields": "length,length_votes,length_minutes,rating,votecount"
  177. }),
  178. headers: {
  179. "Content-Type": "application/json"
  180. },
  181. onload: function(response) {
  182. let result = JSON.parse(response.responseText);
  183. if (!result.results || result.results.length == 0) {
  184. console.log("VNDB search did not yield results");
  185. return;
  186. }
  187.  
  188. insertPanel(result);
  189. }
  190. });
  191. }
  192.  
  193. function observeUrlChanges() {
  194. let lastUrl = window.location.href;
  195.  
  196. const onUrlChange = () => {
  197. const currentUrl = window.location.href;
  198. if (currentUrl !== lastUrl) {
  199. lastUrl = currentUrl;
  200. console.log("URL changed to " + currentUrl);
  201. CurrentAppID = null;
  202. fetchVNDBData();
  203. }
  204. };
  205.  
  206. const observer = new MutationObserver(onUrlChange);
  207. observer.observe(document.body, { childList: true, subtree: true });
  208. }
  209.  
  210. function onInitialLoad() {
  211. window.removeEventListener('load', onInitialLoad);
  212. fetchVNDBData();
  213. observeUrlChanges();
  214. }
  215.  
  216. window.addEventListener('load', onInitialLoad);
  217. })();