Bazaar Directory - WTF is in there?

The new bazaar directory feature doesn't tell you anything about what is in each bazaar. This replaces the (largely) useless counter showing how many favorites a bazaar has with a button to show their bazaar's contents instead.

  1. // ==UserScript==
  2. // @name Bazaar Directory - WTF is in there?
  3. // @namespace Violentmonkey Scripts
  4. // @match https://www.torn.com/page.php*
  5. // @grant GM_xmlhttpRequest
  6. // @version 1.0.1
  7. // @author Titanic_
  8. // @license MIT
  9. // @description The new bazaar directory feature doesn't tell you anything about what is in each bazaar. This replaces the (largely) useless counter showing how many favorites a bazaar has with a button to show their bazaar's contents instead.
  10. // ==/UserScript==
  11.  
  12. let userApiKey = getData("API_KEY") || "";
  13.  
  14. const bazaarIconSVG = `<svg class="bazaar-icon" style="scale: 0.75;"></svg>`;
  15. window.MyCustomBazaarInterval = null;
  16.  
  17. async function fetchApi(endpoint, selections = "basic", apiKeyToUse = userApiKey) {
  18. if (!apiKeyToUse) {
  19. console.warn("API Key not set. Cannot fetch API.");
  20. return Promise.resolve({ error: { code: 0, error: "API Key not set" } });
  21. }
  22.  
  23. return new Promise((resolve) => {
  24. GM_xmlhttpRequest({
  25. method: "GET",
  26. url: `https://api.torn.com/${endpoint}?key=${apiKeyToUse}&selections=${selections}`,
  27. timeout: 15000,
  28. onload: function (response) {
  29. let parsedJson;
  30. try {
  31. parsedJson = JSON.parse(response.responseText);
  32. } catch (e) {
  33. console.error(`Error parsing JSON response:`, e, "Response:", response.responseText);
  34. resolve({ error: { error: "JSON Parse Error", details: e.message, responseText: response.responseText } });
  35. return;
  36. }
  37.  
  38. if (parsedJson?.error) {
  39. const errorMessage = parsedJson.error.error || JSON.stringify(parsedJson.error);
  40. if (parsedJson.error.error !== "API Key not set") console.error(`API Error (Status: ${response.status}): ${errorMessage}`);
  41. resolve(parsedJson);
  42. return;
  43. }
  44.  
  45. if (response.status >= 200 && response.status < 300) resolve(parsedJson);
  46. else {
  47. console.error(`HTTP Error ${response.status}: Non-success status without specific API error in JSON.`, "Response:", response.responseText);
  48. resolve({
  49. error: {
  50. error: `HTTP Error ${response.status}`,
  51. details: "Server returned non-2xx status without a Torn API error object in JSON.",
  52. responseText: response.responseText,
  53. },
  54. });
  55. }
  56. },
  57. onerror: function (response) {
  58. console.error("Network Error:", response.statusText || "Unknown network issue", response);
  59. resolve({ error: { error: "Network Error", details: response.statusText || "Unknown network issue" } });
  60. },
  61. ontimeout: function () {
  62. console.error("Request Timeout");
  63. resolve({ error: { error: "Request Timeout" } });
  64. },
  65. });
  66. });
  67. }
  68.  
  69. function checkUrl() {
  70. if (!window.location.href.includes("page.php?sid=bazaar")) {
  71. if (window.MyCustomBazaarInterval) {
  72. clearInterval(window.MyCustomBazaarInterval);
  73. window.MyCustomBazaarInterval = null;
  74. }
  75. return;
  76. }
  77.  
  78. addBazaarIcons();
  79. }
  80.  
  81. function addBazaarIcons() {
  82. document.querySelectorAll("li[class^=bazaarWrap]").forEach((row) => {
  83. const linkEl = row.querySelector("a[href*='bazaar.php']");
  84. if (!linkEl) return;
  85.  
  86. const statsWrap = linkEl.querySelector("div[class^=statsWrap]");
  87. if (statsWrap) statsWrap.remove();
  88.  
  89. if (!linkEl.querySelector(".bazaar-icon-container")) {
  90. const bazaarIcon = Object.assign(document.createElement("div"), {
  91. className: "bazaar-icon-container",
  92. innerHTML: bazaarIconSVG,
  93. style: "cursor: pointer; float: right; padding-left: 8px;",
  94. });
  95.  
  96. linkEl.append(bazaarIcon);
  97.  
  98. if (!bazaarIcon.dataset.listenerAttached) {
  99. bazaarIcon.addEventListener("click", (e) => {
  100. e.preventDefault();
  101. e.stopPropagation();
  102. toggleExpand(row);
  103. });
  104. bazaarIcon.dataset.listenerAttached = "true";
  105. }
  106. }
  107. });
  108. }
  109.  
  110. function toggleExpand(row) {
  111. const existingDetailsDiv = row.querySelector(".expanded-bazaar-details");
  112.  
  113. document.querySelectorAll(".expanded-bazaar-details").forEach((div) => {
  114. if (div.parentElement !== row) div.style.display = "none";
  115. });
  116.  
  117. if (existingDetailsDiv) existingDetailsDiv.style.display = existingDetailsDiv.style.display === "none" ? "block" : "none";
  118. else {
  119. const detailsDiv = Object.assign(document.createElement("div"), {
  120. className: "expanded-bazaar-details",
  121. style: "",
  122. });
  123.  
  124. const filterInput = Object.assign(document.createElement("input"), {
  125. type: "text",
  126. placeholder: "Filter item name",
  127. style: "width: calc(100% + 10px); text-align: center; background-color: #333333; color: #e0e0e0 !important; border: 1px outset #4f4f4f; padding: 3px;",
  128. });
  129. filterInput.addEventListener("input", () => {
  130. filterTable(table, filterInput.value);
  131. });
  132. detailsDiv.appendChild(filterInput);
  133.  
  134. const table = Object.assign(document.createElement("table"), {
  135. style: "width: 100%; border-collapse: collapse; background-color: #383838;",
  136. });
  137.  
  138. const headerRow = table.createTHead().insertRow();
  139. const columnHeaders = ["Name", "#", "$"];
  140. columnHeaders.forEach((header) => {
  141. headerRow.appendChild(
  142. Object.assign(document.createElement("th"), {
  143. textContent: header,
  144. style: `border: 1px solid #4F4F4F; padding: 5px; text-align: ${header == "Name" ? "left" : "right"}; background-color: #454545; color: #e0e0e0;`,
  145. })
  146. );
  147. });
  148.  
  149. const placeholderCell = table.createTBody().insertRow().insertCell();
  150. Object.assign(placeholderCell, {
  151. colSpan: columnHeaders.length,
  152. textContent: "Details will be loaded here.",
  153. style: "text-align: center; padding: 3px; font-style: italic; color: #a0a0a0; border: 1px solid #4F4F4F;",
  154. });
  155.  
  156. detailsDiv.appendChild(table);
  157. row.appendChild(detailsDiv);
  158. detailsDiv.style.display = "block";
  159.  
  160. populateBazaar(row, table);
  161. }
  162. }
  163.  
  164. function filterTable(table, searchText) {
  165. Array.from(table.querySelector("tbody").querySelectorAll("tr")).forEach((row) => {
  166. const nameCell = row.querySelector("td:first-child");
  167. if (nameCell) {
  168. const name = nameCell.textContent.toLowerCase();
  169. if (name.includes(searchText.toLowerCase())) row.style.display = "";
  170. else row.style.display = "none";
  171. }
  172. });
  173. }
  174.  
  175. async function populateBazaar(row, table) {
  176. const url = row.querySelector("a[href*='bazaar.php']").href;
  177. const userID = new URL(url).searchParams.get("userId");
  178. const data = await fetchApi(`user/${userID}`, "bazaar");
  179.  
  180. const tbody = table.querySelector("tbody");
  181. tbody.innerHTML = "";
  182.  
  183. if (data.error) {
  184. const errorRow = tbody.insertRow();
  185. const errorCell = errorRow.insertCell();
  186. errorCell.colSpan = 3;
  187. errorCell.style = "text-align: center; padding: 10px; font-style: italic; color: #a0a0a0; border: 1px solid #4F4F4F;";
  188.  
  189. if (data.error.error === "API Key not set") {
  190. const setKeyLink = Object.assign(document.createElement("a"), {
  191. href: "#",
  192. textContent: "Click to set Public API",
  193. style: "color: #88C9F2; cursor: pointer;",
  194. });
  195.  
  196. setKeyLink.onclick = async (e) => {
  197. e.preventDefault();
  198. const newApiKeyInput = prompt("Please enter your Torn API (Public) key:");
  199. if (newApiKeyInput) {
  200. const trimmedKey = newApiKeyInput.trim();
  201. if (trimmedKey !== "") {
  202. setData("API_KEY", trimmedKey);
  203. userApiKey = trimmedKey;
  204.  
  205. tbody.innerHTML = "";
  206. const loadingRow = tbody.insertRow();
  207. const loadingCell = loadingRow.insertCell();
  208. loadingCell.colSpan = 3;
  209. loadingCell.textContent = "Reloading bazaar data...";
  210. loadingCell.style = "text-align: center; padding: 10px; font-style: italic; color: #a0a0a0; border: 1px solid #4F4F4F;";
  211.  
  212. await populateBazaar(row, table);
  213. } else {
  214. alert("API Key cannot be empty.");
  215. }
  216. }
  217. };
  218. errorCell.innerHTML = "";
  219. errorCell.appendChild(setKeyLink);
  220. } else errorCell.textContent = `Error loading bazaar: ${data.error.error}`;
  221. return;
  222. }
  223.  
  224. if (!data.bazaar || data.bazaar.length === 0) {
  225. const noItemsRow = tbody.insertRow();
  226. const noItemsCell = noItemsRow.insertCell();
  227. noItemsCell.colSpan = 3;
  228. noItemsCell.textContent = "No items available in this bazaar.";
  229. noItemsCell.style = "text-align: center; padding: 10px; font-style: italic; color: #a0a0a0; border: 1px solid #4F4F4F;";
  230. return;
  231. }
  232.  
  233. const items = data.bazaar.map((item) => ({
  234. name: item.name,
  235. amount: item.quantity,
  236. price: item.price,
  237. }));
  238.  
  239. const sortedItems = items.sort((a, b) => a.name.localeCompare(b.name));
  240.  
  241. for (const item of sortedItems) {
  242. const itemRow = tbody.insertRow();
  243.  
  244. itemRow.appendChild(
  245. Object.assign(document.createElement("td"), {
  246. textContent: item.name,
  247. style:
  248. "border: 1px solid #4F4F4F; padding: 5px; color: #E0E0E0 !important; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100px;",
  249. })
  250. );
  251.  
  252. itemRow.appendChild(
  253. Object.assign(document.createElement("td"), {
  254. textContent: item.amount.toLocaleString(),
  255. style: "border: 1px solid #4F4F4F; padding: 5px; color: #E0E0E0 !important; text-align: right;",
  256. })
  257. );
  258.  
  259. itemRow.appendChild(
  260. Object.assign(document.createElement("td"), {
  261. textContent: "$" + item.price.toLocaleString(),
  262. style: "border: 1px solid #4F4F4F; padding: 5px; color: #E0E0E0 !important; text-align: right;",
  263. })
  264. );
  265. }
  266.  
  267. const filterInput = row.querySelector('.expanded-bazaar-details > input[type="text"]');
  268. if (filterInput) {
  269. filterTable(table, filterInput.value);
  270. }
  271. }
  272.  
  273. function getData(key) {
  274. return localStorage.getItem(key);
  275. }
  276.  
  277. function setData(key, value) {
  278. localStorage.setItem(key, value);
  279. }
  280.  
  281. function addStyle(css) {
  282. const styleEl = Object.assign(document.createElement("style"), { type: "text/css" });
  283. styleEl.appendChild(document.createTextNode(css));
  284. document.head.appendChild(styleEl);
  285. }
  286.  
  287. if (window.MyCustomBazaarInterval) clearInterval(window.MyCustomBazaarInterval);
  288. window.MyCustomBazaarInterval = setInterval(checkUrl, 1000);
  289. checkUrl();
  290.  
  291. addStyle(`
  292. .bazaarWrap___XXYgz {
  293. flex-direction: column;
  294. height: fit-content !important;
  295. }
  296. .expanded-bazaar-details {
  297. display: block;
  298. max-width: 100%;
  299. width: 100%;
  300. max-height: 200px;
  301. overflow-y: scroll;
  302. overflow-x: hidden;
  303. background-color: #383838;
  304. border-top: 1px solid #222222;
  305. clear: both; color: #cccccc;
  306. padding-right: 10px;
  307. }
  308. `);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址