Customizable Bazaar Filler

On click, auto-fills bazaar item quantities and prices based on your preferences

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

  1. // ==UserScript==
  2. // @name Customizable Bazaar Filler
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.60
  5. // @description On click, auto-fills bazaar item quantities and prices based on your preferences
  6. // @match https://www.torn.com/bazaar.php*
  7. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js
  8. // @grant GM_getValue
  9. // @grant GM_setValue
  10. // @grant GM_registerMenuCommand
  11. // @grant GM_xmlhttpRequest
  12. // @connect tornpal.com
  13. // ==/UserScript==
  14.  
  15.  
  16. (function () {
  17. "use strict";
  18.  
  19. const styleBlock = `
  20. .item-toggle {
  21. width: 18px;
  22. height: 18px;
  23. border-radius: 4px;
  24. -webkit-appearance: none;
  25. -moz-appearance: none;
  26. appearance: none;
  27. outline: none;
  28. }
  29. .item-toggle::after {
  30. content: '\\2713';
  31. position: absolute;
  32. font-size: 14px;
  33. top: 50%;
  34. left: 50%;
  35. transform: translate(-50%, -50%);
  36. display: none;
  37. }
  38. .item-toggle:checked::after {
  39. display: block;
  40. }
  41.  
  42. body:not(.dark-mode) .item-toggle {
  43. border: 1px solid #ccc;
  44. background: #fff;
  45. }
  46. body:not(.dark-mode) .item-toggle:checked {
  47. background: #007bff;
  48. }
  49. body:not(.dark-mode) .item-toggle:checked::after {
  50. color: #fff;
  51. }
  52.  
  53. body.dark-mode .item-toggle {
  54. border: 1px solid #4e535a;
  55. background: #2f3237;
  56. }
  57. body.dark-mode .item-toggle:checked {
  58. background: #4e535a;
  59. }
  60. body.dark-mode .item-toggle:checked::after {
  61. color: #fff;
  62. }
  63.  
  64. .checkbox-wrapper {
  65. position: absolute;
  66. top: 50%;
  67. right: 8px;
  68. width: 30px;
  69. height: 30px;
  70. transform: translateY(-50%);
  71. cursor: pointer;
  72. }
  73. .checkbox-wrapper input.item-toggle {
  74. position: absolute;
  75. top: 6px;
  76. left: 6px;
  77. }
  78.  
  79. .settings-modal-overlay {
  80. position: fixed;
  81. top: 0; left: 0;
  82. width: 100%; height: 100%;
  83. background: rgba(0,0,0,0.5);
  84. z-index: 9999;
  85. display: flex;
  86. align-items: center;
  87. justify-content: center;
  88. }
  89. .settings-modal {
  90. background: #fff;
  91. padding: 20px;
  92. border-radius: 8px;
  93. min-width: 300px;
  94. box-shadow: 0 2px 10px rgba(0,0,0,0.3);
  95. color: #000;
  96. }
  97. .settings-modal h2 {
  98. margin-top: 0;
  99. }
  100. .settings-modal label {
  101. display: block;
  102. margin: 10px 0 5px;
  103. }
  104. .settings-modal input, .settings-modal select {
  105. width: 100%;
  106. padding: 5px;
  107. box-sizing: border-box;
  108. }
  109. .settings-modal button {
  110. margin-top: 15px;
  111. padding: 5px 10px;
  112. }
  113. .settings-modal div[style*="text-align:right"] {
  114. text-align: right;
  115. }
  116. body.dark-mode .settings-modal {
  117. background: #2f3237;
  118. color: #fff;
  119. box-shadow: 0 2px 10px rgba(0,0,0,0.7);
  120. }
  121. body.dark-mode .settings-modal input,
  122. body.dark-mode .settings-modal select {
  123. background: #3c3f41;
  124. color: #fff;
  125. border: 1px solid #555;
  126. }
  127. body.dark-mode .settings-modal button {
  128. background: #555;
  129. color: #fff;
  130. border: none;
  131. }
  132.  
  133. /* Black Friday mode styling */
  134. .black-friday-active {
  135. color: #28a745 !important;
  136. }
  137. .black-friday-active .black-friday-icon {
  138. color: #28a745 !important;
  139. fill: #28a745 !important;
  140. }
  141. .black-friday-icon {
  142. color: inherit;
  143. fill: currentColor;
  144. }
  145. `;
  146. $("<style>")
  147. .prop("type", "text/css")
  148. .html(styleBlock)
  149. .appendTo("head");
  150.  
  151. let apiKey = GM_getValue("tornApiKey", "");
  152. let pricingSource = GM_getValue("pricingSource", "Market Value");
  153. let itemMarketOffset = GM_getValue("itemMarketOffset", -1);
  154. let itemMarketMarginType = GM_getValue("itemMarketMarginType", "absolute");
  155. let itemMarketListing = GM_getValue("itemMarketListing", 1);
  156. let itemMarketClamp = GM_getValue("itemMarketClamp", false);
  157. let marketMarginOffset = GM_getValue("marketMarginOffset", 0);
  158. let marketMarginType = GM_getValue("marketMarginType", "absolute");
  159. let bazaarCalcMethod = GM_getValue("bazaarCalcMethod", "cheapest");
  160. let bazaarMarginOffset = GM_getValue("bazaarMarginOffset", 0);
  161. let bazaarMarginType = GM_getValue("bazaarMarginType", "absolute");
  162. let bazaarClamp = GM_getValue("bazaarClamp", false);
  163. let blackFridayMode = GM_getValue("blackFridayMode", false);
  164. const validPages = ["#/add", "#/manage"];
  165. let currentPage = window.location.hash;
  166. let itemMarketCache = {};
  167. let tornPalCache = { time: 0, data: null };
  168.  
  169. function getItemIdByName(itemName) {
  170. const storedItems = JSON.parse(localStorage.getItem("tornItems") || "{}");
  171. for (const [id, info] of Object.entries(storedItems)) {
  172. if (info.name === itemName)
  173. return id;
  174. }
  175. return null;
  176. }
  177. function getPriceColor(listedPrice, marketValue) {
  178. if (marketValue <= 0)
  179. return "";
  180. const ratio = listedPrice / marketValue;
  181. const lowerBound = 0.998;
  182. const upperBound = 1.002;
  183. const isDarkMode = document.body.classList.contains("dark-mode");
  184. if (ratio >= lowerBound && ratio <= upperBound) {
  185. return "";
  186. }
  187. if (ratio < lowerBound) {
  188. const diff = lowerBound - ratio;
  189. const t = Math.min(diff / 0.05, 1.2);
  190. if (isDarkMode) {
  191. const r = Math.round(255 - t * (255 - 190));
  192. const g = Math.round(255 - t * (255 - 70));
  193. const b = Math.round(255 - t * (255 - 70));
  194. return `rgb(${r},${g},${b})`;
  195. }
  196. else {
  197. const r = Math.round(180 - t * 40);
  198. const g = Math.round(60 - t * 40);
  199. const b = Math.round(60 - t * 40);
  200. return `rgb(${r},${g},${b})`;
  201. }
  202. }
  203. else {
  204. const diff = ratio - upperBound;
  205. const t = Math.min(diff / 0.05, 1.2);
  206. if (isDarkMode) {
  207. const r = Math.round(255 - t * (255 - 70));
  208. const g = Math.round(255 - t * (255 - 190));
  209. const b = Math.round(255 - t * (255 - 70));
  210. return `rgb(${r},${g},${b})`;
  211. }
  212. else {
  213. const r = Math.round(60 - t * 40);
  214. const g = Math.round(160 - t * 40);
  215. const b = Math.round(60 - t * 40);
  216. return `rgb(${r},${g},${b})`;
  217. }
  218. }
  219. }
  220. async function fetchItemMarketData(itemId) {
  221. if (!apiKey) {
  222. console.error("No API key set for Item Market calls.");
  223. alert("No API key set. Please set your Torn API key in Bazaar Filler Settings before continuing.");
  224. return null;
  225. }
  226. const now = Date.now();
  227. if (itemMarketCache[itemId] && now - itemMarketCache[itemId].time < 30000) {
  228. return itemMarketCache[itemId].data;
  229. }
  230. const url = `https://api.torn.com/v2/market/${itemId}/itemmarket?comment=wBazaarFiller`;
  231. try {
  232. const res = await fetch(url, {
  233. headers: { Authorization: "ApiKey " + apiKey },
  234. });
  235. const data = await res.json();
  236. if (data.error) {
  237. console.error("Item Market API error:", data.error);
  238. alert("Item Market API error: " + data.error.error);
  239. return null;
  240. }
  241. itemMarketCache[itemId] = { time: now, data };
  242. return data;
  243. }
  244. catch (err) {
  245. console.error("Failed fetching Item Market data:", err);
  246. alert("Failed to fetch Item Market data. Check your API key or try again later.");
  247. return null;
  248. }
  249. }
  250. async function fetchTornPalData() {
  251. const now = Date.now();
  252. if (tornPalCache.data && now - tornPalCache.time < 60000) {
  253. return tornPalCache.data;
  254. }
  255. return new Promise((resolve, reject) => {
  256. GM_xmlhttpRequest({
  257. method: "GET",
  258. url: "https://tornpal.com/api/v1/markets/allprices?comment=wBazaarFiller",
  259. onload: function (response) {
  260. try {
  261. const data = JSON.parse(response.responseText);
  262. tornPalCache = { time: now, data };
  263. resolve(data);
  264. }
  265. catch (err) {
  266. console.error("Parsing error:", err);
  267. reject(err);
  268. }
  269. },
  270. onerror: function (err) {
  271. console.error("Failed fetching TornPal data:", err);
  272. reject(err);
  273. },
  274. });
  275. });
  276. }
  277. function updatePriceFieldColor($priceInput) {
  278. var _a;
  279. let $row = $priceInput.closest("li.clearfix");
  280. let itemName = "";
  281. if ($row.length) {
  282. itemName = $row.find(".name-wrap span.t-overflow").text().trim();
  283. }
  284. else {
  285. $row = $priceInput.closest(".item___jLJcf");
  286. itemName = $row.length ? $row.find(".desc___VJSNQ b").text().trim() : "";
  287. }
  288. if (!itemName)
  289. return;
  290. const storedItems = JSON.parse(localStorage.getItem("tornItems") || "{}");
  291. const matchedItem = Object.values(storedItems).find((i) => i.name === itemName);
  292. if (!matchedItem || !matchedItem.market_value)
  293. return;
  294. const raw = ((_a = $priceInput.val()) === null || _a === void 0 ? void 0 : _a.replace(/,/g, "")) || "";
  295. const typedPrice = Number(raw);
  296. if (isNaN(typedPrice)) {
  297. $priceInput.css("color", "");
  298. return;
  299. }
  300. $priceInput.css("color", getPriceColor(typedPrice, matchedItem.market_value));
  301. }
  302. function attachPriceFieldObservers() {
  303. $(".price input").each(function () {
  304. const $el = $(this);
  305. if ($el.data("listenerAttached"))
  306. return;
  307. $el.on("input", function () {
  308. updatePriceFieldColor($(this));
  309. });
  310. $el.data("listenerAttached", true);
  311. updatePriceFieldColor($el);
  312. });
  313. $(".price___DoKP7 .input-money-group.success input.input-money").each(function () {
  314. const $el = $(this);
  315. if ($el.data("listenerAttached"))
  316. return;
  317. $el.on("input", function () {
  318. updatePriceFieldColor($(this));
  319. });
  320. $el.data("listenerAttached", true);
  321. updatePriceFieldColor($el);
  322. });
  323. $("[class*=bottomMobileMenu___] [class*=priceMobile___] .input-money-group.success input.input-money").each(function () {
  324. const $el = $(this);
  325. if ($el.data("listenerAttached"))
  326. return;
  327. $el.on("input", function () {
  328. updatePriceFieldColor($(this));
  329. });
  330. $el.data("listenerAttached", true);
  331. updatePriceFieldColor($el);
  332. });
  333. }
  334.  
  335. async function updateAddRow($row, isChecked) {
  336. var _a, _b;
  337. const $qtyInput = $row.find(".amount input").first();
  338. const $priceInput = $row.find(".price input").first();
  339. const $choiceCheckbox = $row.find("div.amount.choice-container input");
  340. if (!isChecked) {
  341. if ($choiceCheckbox.length && $choiceCheckbox.prop("checked")) {
  342. $choiceCheckbox.click();
  343. }
  344. if ($qtyInput.data("orig") !== undefined) {
  345. $qtyInput.val($qtyInput.data("orig"));
  346. $qtyInput.removeData("orig");
  347. }
  348. else {
  349. $qtyInput.val("");
  350. }
  351. $qtyInput[0].dispatchEvent(new Event("keyup", { bubbles: true }));
  352. if ($priceInput.data("orig") !== undefined) {
  353. $priceInput.val($priceInput.data("orig"));
  354. $priceInput.removeData("orig");
  355. $priceInput.css("color", "");
  356. }
  357. else {
  358. $priceInput.val("");
  359. }
  360. $priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
  361. $priceInput[0].dispatchEvent(new Event("keyup", { bubbles: true }));
  362. return;
  363. }
  364. if (!$qtyInput.data("orig"))
  365. $qtyInput.data("orig", $qtyInput.val());
  366. if (!$priceInput.data("orig"))
  367. $priceInput.data("orig", $priceInput.val());
  368. if (blackFridayMode) {
  369. if ($choiceCheckbox.length) {
  370. if (!$choiceCheckbox.prop("checked")) {
  371. $choiceCheckbox.click();
  372. }
  373. }
  374. else {
  375. const qty = $row.find(".item-amount.qty").text().trim();
  376. $qtyInput.val(qty);
  377. $qtyInput[0].dispatchEvent(new Event("keyup", { bubbles: true }));
  378. }
  379. $priceInput.val("1");
  380. $priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
  381. $priceInput[0].dispatchEvent(new Event("keyup", { bubbles: true }));
  382. return;
  383. }
  384. const itemName = $row.find(".name-wrap span.t-overflow").text().trim();
  385. const itemId = getItemIdByName(itemName);
  386. const storedItems = JSON.parse(localStorage.getItem("tornItems") || "{}");
  387. const matchedItem = Object.values(storedItems).find((i) => i.name === itemName);
  388. if ($choiceCheckbox.length) {
  389. if (!$choiceCheckbox.prop("checked")) {
  390. $choiceCheckbox.click();
  391. }
  392. }
  393. else {
  394. const qty = $row.find(".item-amount.qty").text().trim();
  395. $qtyInput.val(qty);
  396. $qtyInput[0].dispatchEvent(new Event("keyup", { bubbles: true }));
  397. }
  398. if (pricingSource === "Market Value" && matchedItem) {
  399. const mv = matchedItem.market_value;
  400. let finalPrice = mv;
  401. if (marketMarginType === "absolute") {
  402. finalPrice += marketMarginOffset;
  403. }
  404. else if (marketMarginType === "percentage") {
  405. finalPrice = Math.round(mv * (1 + marketMarginOffset / 100));
  406. }
  407. $priceInput.val(finalPrice.toLocaleString("en-US"));
  408. $priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
  409. $priceInput[0].dispatchEvent(new Event("keyup", { bubbles: true }));
  410. $priceInput.css("color", getPriceColor(finalPrice, mv));
  411. }
  412. else if (pricingSource === "Item Market" && itemId) {
  413. const data = await fetchItemMarketData(itemId);
  414. if (!data || !((_b = (_a = data.itemmarket) === null || _a === void 0 ? void 0 : _a.listings) === null || _b === void 0 ? void 0 : _b.length))
  415. return;
  416. const listings = data.itemmarket.listings;
  417. const $checkbox = $row.find(".checkbox-wrapper input.item-toggle").first();
  418. const listingsText = listings
  419. .slice(0, 5)
  420. .map((x, i) => `${i + 1}) $${x.price.toLocaleString("en-US")} x${x.amount}`)
  421. .join("\n");
  422. $checkbox.attr("title", listingsText);
  423. setTimeout(() => {
  424. $checkbox.removeAttr("title");
  425. }, 30000);
  426. const baseIndex = Math.min(itemMarketListing - 1, listings.length - 1);
  427. const listingPrice = listings[baseIndex].price;
  428. let finalPrice;
  429. if (itemMarketMarginType === "absolute") {
  430. finalPrice = listingPrice + itemMarketOffset;
  431. }
  432. else if (itemMarketMarginType === "percentage") {
  433. finalPrice = Math.round(listingPrice * (1 + itemMarketOffset / 100));
  434. }
  435. else {
  436. finalPrice = listingPrice;
  437. }
  438. if (itemMarketClamp && matchedItem && matchedItem.market_value) {
  439. finalPrice = Math.max(finalPrice, matchedItem.market_value);
  440. }
  441. if (!$choiceCheckbox.length) {
  442. const qty = $row.find(".item-amount.qty").text().trim();
  443. $qtyInput.val(qty);
  444. $qtyInput[0].dispatchEvent(new Event("keyup", { bubbles: true }));
  445. }
  446. else {
  447. if (!$choiceCheckbox.prop("checked")) {
  448. $choiceCheckbox.click();
  449. }
  450. }
  451. $priceInput.val(finalPrice.toLocaleString("en-US"));
  452. $priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
  453. $priceInput[0].dispatchEvent(new Event("keyup", { bubbles: true }));
  454. if (matchedItem && matchedItem.market_value) {
  455. $priceInput.css("color", getPriceColor(finalPrice, matchedItem.market_value));
  456. }
  457. }
  458. else if (pricingSource === "Bazaars/TornPal") {
  459. const data = await fetchTornPalData();
  460. if (!data || !data.items)
  461. return;
  462. if (!matchedItem)
  463. return;
  464. const tornPalItem = Object.values(data.items).find((item) => item.name === itemName);
  465. if (!tornPalItem)
  466. return;
  467. let basePrice;
  468. if (bazaarCalcMethod === "cheapest") {
  469. basePrice = tornPalItem.bazaar_cheapest;
  470. }
  471. else {
  472. basePrice = tornPalItem.bazaar_weighted_average_5;
  473. }
  474. let finalPrice;
  475. if (bazaarMarginType === "absolute") {
  476. finalPrice = basePrice + bazaarMarginOffset;
  477. }
  478. else if (bazaarMarginType === "percentage") {
  479. finalPrice = Math.round(basePrice * (1 + bazaarMarginOffset / 100));
  480. }
  481. else {
  482. finalPrice = basePrice;
  483. }
  484. if (bazaarClamp && matchedItem.market_value) {
  485. finalPrice = Math.max(finalPrice, matchedItem.market_value);
  486. }
  487. $priceInput.val(finalPrice.toLocaleString("en-US"));
  488. $priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
  489. $priceInput[0].dispatchEvent(new Event("keyup", { bubbles: true }));
  490. $priceInput.css("color", getPriceColor(finalPrice, matchedItem.market_value));
  491. }
  492. }
  493. async function updateManageRow($row, isChecked) {
  494. var _a, _b;
  495. const $priceInput = $row.find(".price___DoKP7 .input-money-group.success input.input-money").first();
  496. if (!isChecked) {
  497. if ($priceInput.data("orig") !== undefined) {
  498. $priceInput.val($priceInput.data("orig"));
  499. $priceInput.removeData("orig");
  500. $priceInput.css("color", "");
  501. }
  502. else {
  503. $priceInput.val("");
  504. }
  505. $priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
  506. return;
  507. }
  508. if (!$priceInput.data("orig"))
  509. $priceInput.data("orig", $priceInput.val());
  510. if (blackFridayMode) {
  511. $priceInput.val("1");
  512. $priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
  513. return;
  514. }
  515. const itemName = $row.find(".desc___VJSNQ b").text().trim();
  516. const itemId = getItemIdByName(itemName);
  517. const storedItems = JSON.parse(localStorage.getItem("tornItems") || "{}");
  518. const matchedItem = Object.values(storedItems).find((i) => i.name === itemName);
  519. if (pricingSource === "Market Value" && matchedItem) {
  520. const mv = matchedItem.market_value;
  521. let finalPrice = mv;
  522. if (marketMarginType === "absolute") {
  523. finalPrice += marketMarginOffset;
  524. }
  525. else if (marketMarginType === "percentage") {
  526. finalPrice = Math.round(mv * (1 + marketMarginOffset / 100));
  527. }
  528. $priceInput.val(finalPrice.toLocaleString("en-US"));
  529. $priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
  530. $priceInput.css("color", getPriceColor(finalPrice, mv));
  531. }
  532. else if (pricingSource === "Item Market" && itemId) {
  533. const data = await fetchItemMarketData(itemId);
  534. if (!data || !((_b = (_a = data.itemmarket) === null || _a === void 0 ? void 0 : _a.listings) === null || _b === void 0 ? void 0 : _b.length))
  535. return;
  536. const listings = data.itemmarket.listings;
  537. const $checkbox = $row.find(".checkbox-wrapper input.item-toggle").first();
  538. const listingsText = listings
  539. .slice(0, 5)
  540. .map((x, i) => `${i + 1}) $${x.price.toLocaleString("en-US")} x${x.amount}`)
  541. .join("\n");
  542. $checkbox.attr("title", listingsText);
  543. setTimeout(() => {
  544. $checkbox.removeAttr("title");
  545. }, 30000);
  546. const baseIndex = Math.min(itemMarketListing - 1, listings.length - 1);
  547. const listingPrice = listings[baseIndex].price;
  548. let finalPrice;
  549. if (itemMarketMarginType === "absolute") {
  550. finalPrice = listingPrice + itemMarketOffset;
  551. }
  552. else if (itemMarketMarginType === "percentage") {
  553. finalPrice = Math.round(listingPrice * (1 + itemMarketOffset / 100));
  554. }
  555. else {
  556. finalPrice = listingPrice;
  557. }
  558. if (itemMarketClamp && matchedItem && matchedItem.market_value) {
  559. finalPrice = Math.max(finalPrice, matchedItem.market_value);
  560. }
  561. $priceInput.val(finalPrice.toLocaleString("en-US"));
  562. $priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
  563. if (matchedItem && matchedItem.market_value) {
  564. $priceInput.css("color", getPriceColor(finalPrice, matchedItem.market_value));
  565. }
  566. }
  567. else if (pricingSource === "Bazaars/TornPal") {
  568. const data = await fetchTornPalData();
  569. if (!data || !data.items)
  570. return;
  571. if (!matchedItem)
  572. return;
  573. const tornPalItem = Object.values(data.items).find((item) => item.name === itemName);
  574. if (!tornPalItem)
  575. return;
  576. let basePrice;
  577. if (bazaarCalcMethod === "cheapest") {
  578. basePrice = tornPalItem.bazaar_cheapest;
  579. }
  580. else {
  581. basePrice = tornPalItem.bazaar_weighted_average_5;
  582. }
  583. let finalPrice;
  584. if (bazaarMarginType === "absolute") {
  585. finalPrice = basePrice + bazaarMarginOffset;
  586. }
  587. else if (bazaarMarginType === "percentage") {
  588. finalPrice = Math.round(basePrice * (1 + bazaarMarginOffset / 100));
  589. }
  590. else {
  591. finalPrice = basePrice;
  592. }
  593. if (bazaarClamp && matchedItem.market_value) {
  594. finalPrice = Math.max(finalPrice, matchedItem.market_value);
  595. }
  596. $priceInput.val(finalPrice.toLocaleString("en-US"));
  597. $priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
  598. if (matchedItem && matchedItem.market_value) {
  599. $priceInput.css("color", getPriceColor(finalPrice, matchedItem.market_value));
  600. }
  601. }
  602. }
  603. async function updateManageRowMobile($row, isChecked) {
  604. var _a, _b;
  605. const $priceInput = $row
  606. .find("[class*=bottomMobileMenu___] [class*=priceMobile___] .input-money-group.success input.input-money")
  607. .first();
  608. if (!$priceInput.length) {
  609. console.error("Mobile price field not found.");
  610. return;
  611. }
  612. if (!isChecked) {
  613. if ($priceInput.data("orig") !== undefined) {
  614. $priceInput.val($priceInput.data("orig"));
  615. $priceInput.removeData("orig");
  616. $priceInput.css("color", "");
  617. }
  618. else {
  619. $priceInput.val("");
  620. }
  621. $priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
  622. return;
  623. }
  624. if (!$priceInput.data("orig"))
  625. $priceInput.data("orig", $priceInput.val());
  626. if (blackFridayMode) {
  627. $priceInput.val("1");
  628. $priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
  629. return;
  630. }
  631. const itemName = $row.find(".desc___VJSNQ b").text().trim();
  632. const itemId = getItemIdByName(itemName);
  633. const storedItems = JSON.parse(localStorage.getItem("tornItems") || "{}");
  634. const matchedItem = Object.values(storedItems).find((i) => i.name === itemName);
  635. if (pricingSource === "Market Value" && matchedItem) {
  636. const mv = matchedItem.market_value;
  637. let finalPrice = mv;
  638. if (marketMarginType === "absolute") {
  639. finalPrice += marketMarginOffset;
  640. }
  641. else if (marketMarginType === "percentage") {
  642. finalPrice = Math.round(mv * (1 + marketMarginOffset / 100));
  643. }
  644. $priceInput.val(finalPrice.toLocaleString("en-US"));
  645. $priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
  646. $priceInput.css("color", getPriceColor(finalPrice, mv));
  647. }
  648. else if (pricingSource === "Item Market" && itemId) {
  649. const data = await fetchItemMarketData(itemId);
  650. if (!data || !((_b = (_a = data.itemmarket) === null || _a === void 0 ? void 0 : _a.listings) === null || _b === void 0 ? void 0 : _b.length))
  651. return;
  652. const listings = data.itemmarket.listings;
  653. const baseIndex = Math.min(itemMarketListing - 1, listings.length - 1);
  654. const listingPrice = listings[baseIndex].price;
  655. let finalPrice;
  656. if (itemMarketMarginType === "absolute") {
  657. finalPrice = listingPrice + itemMarketOffset;
  658. }
  659. else if (itemMarketMarginType === "percentage") {
  660. finalPrice = Math.round(listingPrice * (1 + itemMarketOffset / 100));
  661. }
  662. else {
  663. finalPrice = listingPrice;
  664. }
  665. if (itemMarketClamp && matchedItem && matchedItem.market_value) {
  666. finalPrice = Math.max(finalPrice, matchedItem.market_value);
  667. }
  668. $priceInput.val(finalPrice.toLocaleString("en-US"));
  669. $priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
  670. if (matchedItem && matchedItem.market_value) {
  671. $priceInput.css("color", getPriceColor(finalPrice, matchedItem.market_value));
  672. }
  673. }
  674. else if (pricingSource === "Bazaars/TornPal") {
  675. const data = await fetchTornPalData();
  676. if (!data || !data.items)
  677. return;
  678. if (!matchedItem)
  679. return;
  680. const tornPalItem = Object.values(data.items).find((item) => item.name === itemName);
  681. if (!tornPalItem)
  682. return;
  683. let basePrice;
  684. if (bazaarCalcMethod === "cheapest") {
  685. basePrice = tornPalItem.bazaar_cheapest;
  686. }
  687. else {
  688. basePrice = tornPalItem.bazaar_weighted_average_5;
  689. }
  690. let finalPrice;
  691. if (bazaarMarginType === "absolute") {
  692. finalPrice = basePrice + bazaarMarginOffset;
  693. }
  694. else if (bazaarMarginType === "percentage") {
  695. finalPrice = Math.round(basePrice * (1 + bazaarMarginOffset / 100));
  696. }
  697. else {
  698. finalPrice = basePrice;
  699. }
  700. if (bazaarClamp && matchedItem.market_value) {
  701. finalPrice = Math.max(finalPrice, matchedItem.market_value);
  702. }
  703. $priceInput.val(finalPrice.toLocaleString("en-US"));
  704. $priceInput[0].dispatchEvent(new Event("input", { bubbles: true }));
  705. if (matchedItem && matchedItem.market_value) {
  706. $priceInput.css("color", getPriceColor(finalPrice, matchedItem.market_value));
  707. }
  708. }
  709. }
  710.  
  711. function openSettingsModal() {
  712. $(".settings-modal-overlay").remove();
  713. const $overlay = $('<div class="settings-modal-overlay"></div>');
  714. const $modal = $(`
  715. <div class="settings-modal" style="width:400px; max-width:90%; font-family:Arial, sans-serif;">
  716. <h2 style="margin-bottom:6px;">Bazaar Filler Settings</h2>
  717. <hr style="border-top:1px solid #ccc; margin:8px 0;">
  718. <div style="margin-bottom:15px;">
  719. <label for="api-key-input" style="font-weight:bold; display:block;">Torn API Key</label>
  720. <input id="api-key-input" type="text" placeholder="Enter API key" style="width:100%; padding:6px; box-sizing:border-box;" value="${apiKey || ''}">
  721. </div>
  722. <hr style="border-top:1px solid #ccc; margin:8px 0;">
  723. <div style="margin-bottom:15px;">
  724. <label for="pricing-source-select" style="font-weight:bold; display:block;">Pricing Source</label>
  725. <select id="pricing-source-select" style="width:100%; padding:6px; box-sizing:border-box;">
  726. <option value="Market Value">Market Value</option>
  727. <option value="Bazaars/TornPal">Bazaars/TornPal</option>
  728. <option value="Item Market">Item Market</option>
  729. </select>
  730. </div>
  731. <div id="market-value-options" style="display:none; margin-bottom:15px;">
  732. <hr style="border-top:1px solid #ccc; margin:8px 0;">
  733. <h3 style="margin:0 0 10px 0; font-size:1em; font-weight:bold;">Market Value Options</h3>
  734. <div style="margin-bottom:10px;">
  735. <label for="market-margin-offset" style="display:block;">Margin (ie: -1 is either $1 less or 1% less depending on margin type)</label>
  736. <input id="market-margin-offset" type="number" style="width:100%; padding:6px; box-sizing:border-box;" value="${marketMarginOffset}">
  737. </div>
  738. <div style="margin-bottom:10px;">
  739. <label for="market-margin-type" style="display:block;">Margin Type</label>
  740. <select id="market-margin-type" style="width:100%; padding:6px; box-sizing:border-box;">
  741. <option value="absolute">Absolute ($)</option>
  742. <option value="percentage">Percentage (%)</option>
  743. </select>
  744. </div>
  745. </div>
  746. <div id="item-market-options" style="display:none; margin-bottom:15px;">
  747. <hr style="border-top:1px solid #ccc; margin:8px 0;">
  748. <h3 style="margin:0 0 10px 0; font-size:1em; font-weight:bold;">Item Market Options</h3>
  749. <div style="margin-bottom:10px;">
  750. <label for="item-market-listing" style="display:block;">Listing Index (1 = lowest, 2 = 2nd lowest, etc)</label>
  751. <input id="item-market-listing" type="number" style="width:100%; padding:6px; box-sizing:border-box;" value="${itemMarketListing}">
  752. </div>
  753. <div style="margin-bottom:10px;">
  754. <label for="item-market-offset" style="display:block;">Margin (ie: -1 is either $1 less or 1% less depending on margin type)</label>
  755. <input id="item-market-offset" type="number" style="width:100%; padding:6px; box-sizing:border-box;" value="${itemMarketOffset}">
  756. </div>
  757. <div style="margin-bottom:10px;">
  758. <label for="item-market-margin-type" style="display:block;">Margin Type</label>
  759. <select id="item-market-margin-type" style="width:100%; padding:6px; box-sizing:border-box;">
  760. <option value="absolute">Absolute ($)</option>
  761. <option value="percentage">Percentage (%)</option>
  762. </select>
  763. </div>
  764. <div style="display:inline-flex; align-items:center; margin-bottom:5px;">
  765. <input id="item-market-clamp" type="checkbox" style="margin-right:5px;" ${itemMarketClamp ? "checked" : ""}>
  766. <label for="item-market-clamp" style="margin:0; cursor:pointer;">Clamp minimum price to Market Value</label>
  767. </div>
  768. </div>
  769. <div id="tornpal-options" style="display:none; margin-bottom:15px;">
  770. <hr style="border-top:1px solid #ccc; margin:8px 0;">
  771. <h3 style="margin:0 0 10px 0; font-size:1em; font-weight:bold;">TornPal Options</h3>
  772. <div style="margin-bottom:10px;">
  773. <label for="tornpal-calc-method" style="display:block;">Calculation Method</label>
  774. <select id="tornpal-calc-method" style="width:100%; padding:6px; box-sizing:border-box;">
  775. <option value="cheapest">Cheapest</option>
  776. <option value="average">Average</option>
  777. </select>
  778. </div>
  779. <div style="margin-bottom:10px;">
  780. <label for="tornpal-margin-offset" style="display:block;">Margin (e.g., -1 for $1 less or 1% less)</label>
  781. <input id="tornpal-margin-offset" type="number" style="width:100%; padding:6px; box-sizing:border-box;" value="${bazaarMarginOffset}">
  782. </div>
  783. <div style="margin-bottom:10px;">
  784. <label for="tornpal-margin-type" style="display:block;">Margin Type</label>
  785. <select id="tornpal-margin-type" style="width:100%; padding:6px; box-sizing:border-box;">
  786. <option value="absolute">Absolute ($)</option>
  787. <option value="percentage">Percentage (%)</option>
  788. </select>
  789. </div>
  790. <div style="display:inline-flex; align-items:center; margin-bottom:5px;">
  791. <input id="tornpal-clamp" type="checkbox" style="margin-right:5px;" ${bazaarClamp ? "checked" : ""}>
  792. <label for="tornpal-clamp" style="margin:0; cursor:pointer;">Clamp minimum price to Market Value</label>
  793. </div>
  794. </div>
  795. <hr style="border-top:1px solid #ccc; margin:8px 0;">
  796. <div style="text-align:right;">
  797. <button id="settings-save" style="margin-right:8px; padding:6px 10px; cursor:pointer;">Save</button>
  798. <button id="settings-cancel" style="padding:6px 10px; cursor:pointer;">Cancel</button>
  799. </div>
  800. </div>
  801. `);
  802. $overlay.append($modal);
  803. $("body").append($overlay);
  804. $("#pricing-source-select").val(pricingSource);
  805. $("#item-market-margin-type").val(itemMarketMarginType);
  806. $("#market-margin-type").val(marketMarginType);
  807. $("#tornpal-calc-method").val(bazaarCalcMethod);
  808. $("#tornpal-margin-type").val(bazaarMarginType);
  809. function toggleFields() {
  810. const src = $("#pricing-source-select").val();
  811. $("#market-value-options").toggle(src === "Market Value");
  812. $("#item-market-options").toggle(src === "Item Market");
  813. $("#tornpal-options").toggle(src === "Bazaars/TornPal");
  814. }
  815. $("#pricing-source-select").change(toggleFields);
  816. toggleFields();
  817. $("#settings-save").click(function () {
  818. var _a;
  819. const oldPricingSource = pricingSource;
  820. apiKey = ((_a = $("#api-key-input").val()) === null || _a === void 0 ? void 0 : _a.trim()) || "";
  821. pricingSource = $("#pricing-source-select").val();
  822. if (oldPricingSource !== pricingSource) {
  823. itemMarketCache = {};
  824. tornPalCache = { time: 0, data: null };
  825. }
  826. if (pricingSource === "Bazaars/TornPal") {
  827. bazaarCalcMethod = $("#tornpal-calc-method").val();
  828. bazaarMarginOffset = Number($("#tornpal-margin-offset").val() || 0);
  829. bazaarMarginType = $("#tornpal-margin-type").val();
  830. bazaarClamp = $("#tornpal-clamp").is(":checked");
  831. GM_setValue("bazaarCalcMethod", bazaarCalcMethod);
  832. GM_setValue("bazaarMarginOffset", bazaarMarginOffset);
  833. GM_setValue("bazaarMarginType", bazaarMarginType);
  834. GM_setValue("bazaarClamp", bazaarClamp);
  835. }
  836. if (pricingSource === "Market Value") {
  837. marketMarginOffset = Number($("#market-margin-offset").val() || 0);
  838. marketMarginType = $("#market-margin-type").val();
  839. GM_setValue("marketMarginOffset", marketMarginOffset);
  840. GM_setValue("marketMarginType", marketMarginType);
  841. }
  842. if (pricingSource === "Item Market") {
  843. itemMarketListing = Number($("#item-market-listing").val() || 1);
  844. itemMarketOffset = Number($("#item-market-offset").val() || -1);
  845. itemMarketMarginType = $("#item-market-margin-type").val();
  846. itemMarketClamp = $("#item-market-clamp").is(":checked");
  847. GM_setValue("itemMarketListing", itemMarketListing);
  848. GM_setValue("itemMarketOffset", itemMarketOffset);
  849. GM_setValue("itemMarketMarginType", itemMarketMarginType);
  850. GM_setValue("itemMarketClamp", itemMarketClamp);
  851. }
  852. GM_setValue("tornApiKey", apiKey);
  853. GM_setValue("pricingSource", pricingSource);
  854. $overlay.remove();
  855. });
  856. $("#settings-cancel").click(() => $overlay.remove());
  857. }
  858. function addBlackFridayToggle() {
  859. if (document.getElementById("black-friday-toggle"))
  860. return;
  861. const linksContainer = document.querySelector(".linksContainer___LiOTN");
  862. if (!linksContainer)
  863. return;
  864. const link = document.createElement("a");
  865. link.id = "black-friday-toggle";
  866. link.href = "#";
  867. link.className = "linkContainer___X16y4 inRow___VfDnd greyLineV___up8VP iconActive___oAum9";
  868. if (blackFridayMode) {
  869. link.classList.add("black-friday-active");
  870. }
  871. link.target = "_self";
  872. link.rel = "noreferrer";
  873. const iconSpan = document.createElement("span");
  874. iconSpan.className = "iconWrapper___x3ZLe iconWrapper___COKJD svgIcon___IwbJV";
  875. iconSpan.innerHTML = `
  876. <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" class="black-friday-icon" style="color: ${blackFridayMode ? "#28a745" : "inherit"}; fill: ${blackFridayMode ? "#28a745" : "currentColor"};">
  877. <path d="M4 10.781c.148 1.667 1.513 2.85 3.591 3.003V15h1.043v-1.216c2.27-.179 3.678-1.438 3.678-3.3 0-1.59-.947-2.51-2.956-3.028l-.722-.187V3.467c1.122.11 1.879.714 2.07 1.616h1.47c-.166-1.6-1.54-2.748-3.54-2.875V1H7.591v1.233c-1.939.23-3.27 1.472-3.27 3.156 0 1.454.966 2.483 2.661 2.917l.61.162v4.031c-1.149-.17-1.94-.8-2.131-1.718H4zm3.391-3.836c-1.043-.263-1.6-.825-1.6-1.616 0-.944.704-1.641 1.8-1.828v3.495l-.2-.05zm1.591 1.872c1.287.323 1.852.859 1.852 1.769 0 1.097-.826 1.828-2.2 1.939V8.73l.348.086z"/>
  878. </svg>
  879. `;
  880. link.appendChild(iconSpan);
  881. const textSpan = document.createElement("span");
  882. textSpan.className = "linkTitle____NPyM";
  883. textSpan.textContent = blackFridayMode ? "Black Friday: ON" : "Black Friday: OFF";
  884. link.appendChild(textSpan);
  885. link.addEventListener("click", function (e) {
  886. e.preventDefault();
  887. blackFridayMode = !blackFridayMode;
  888. GM_setValue("blackFridayMode", blackFridayMode);
  889. textSpan.textContent = blackFridayMode ? "Black Friday: ON" : "Black Friday: OFF";
  890. const svg = this.querySelector(".black-friday-icon");
  891. if (svg) {
  892. svg.style.color = blackFridayMode ? "#28a745" : "inherit";
  893. svg.style.fill = blackFridayMode ? "#28a745" : "currentColor";
  894. }
  895. if (blackFridayMode) {
  896. link.classList.add("black-friday-active");
  897. }
  898. else {
  899. link.classList.remove("black-friday-active");
  900. }
  901. });
  902. const settingsButton = document.getElementById("pricing-source-button");
  903. if (settingsButton) {
  904. linksContainer.insertBefore(link, settingsButton);
  905. }
  906. else {
  907. linksContainer.insertBefore(link, linksContainer.firstChild);
  908. }
  909. }
  910. function addPricingSourceLink() {
  911. if (document.getElementById("pricing-source-button"))
  912. return;
  913. const linksContainer = document.querySelector(".linksContainer___LiOTN");
  914. if (!linksContainer)
  915. return;
  916. const link = document.createElement("a");
  917. link.id = "pricing-source-button";
  918. link.href = "#";
  919. link.className = "linkContainer___X16y4 inRow___VfDnd greyLineV___up8VP iconActive___oAum9";
  920. link.target = "_self";
  921. link.rel = "noreferrer";
  922. const iconSpan = document.createElement("span");
  923. iconSpan.className = "iconWrapper___x3ZLe iconWrapper___COKJD svgIcon___IwbJV";
  924. iconSpan.innerHTML = `
  925. <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
  926. <path d="M8 4.754a3.246 3.246 0 1 1 0 6.492 3.246 3.246 0 0 1 0-6.492zM5.754 8a2.246 2.246 0 1 0 4.492 0 2.246 2.246 0 0 0-4.492 0z"/>
  927. <path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 0-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 0-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 0 .52 1.255l-.16.292c-.892 1.64.901 3.433 2.54 2.54l.292-.16a.873.873 0 0 0 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 0 1.255-.52l.292.16c1.64.893 3.433-.902 2.54-2.541l-.16-.292a.873.873 0 0 0-.52-1.255l-.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 0-.52-1.255l-.16-.292c-.893-1.64-.902-3.433-2.54-2.54l-.292.16a.873.873 0 0 0-1.255-.52l-.094-.319zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.416 1.6.42 1.184 1.185l-.16.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.318.094a1.873 1.873 0 0 0-1.116 2.692l.16.292c.416.764-.42 1.6-1.185 1.184l-.291-.16a1.873 1.873 0 0 0-1.116-2.692l-.318-.094c-.835-.246-.835-1.428 0-1.674l.318-.094a1.873 1.873 0 0 0 1.116-2.692l-.16-.292c-.416-.764.42-1.6 1.185-1.184l.292.16a1.873 1.873 0 0 0 2.693-1.115l.094-.318z"/>
  928. </svg>
  929. `;
  930. link.appendChild(iconSpan);
  931. const textSpan = document.createElement("span");
  932. textSpan.className = "linkTitle____NPyM";
  933. textSpan.textContent = "Bazaar Filler Settings";
  934. link.appendChild(textSpan);
  935. link.addEventListener("click", function (e) {
  936. e.preventDefault();
  937. openSettingsModal();
  938. });
  939. linksContainer.insertBefore(link, linksContainer.firstChild);
  940. }
  941. function addAddPageCheckboxes() {
  942. $(".items-cont .title-wrap").each(function () {
  943. const $el = $(this);
  944. if ($el.find(".checkbox-wrapper").length)
  945. return;
  946. $el.css("position", "relative");
  947. const wrapper = $('<div class="checkbox-wrapper"></div>');
  948. const checkbox = $("<input>", {
  949. type: "checkbox",
  950. class: "item-toggle",
  951. click: async function (e) {
  952. e.stopPropagation();
  953. if (!GM_getValue("tornApiKey", "")) {
  954. alert("No Torn API key set. Please click the 'Bazaar Filler Settings' button to enter your API key.");
  955. $(this).prop("checked", false);
  956. openSettingsModal();
  957. return;
  958. }
  959. await updateAddRow($(this).closest("li.clearfix"), this.checked);
  960. },
  961. });
  962. wrapper.append(checkbox);
  963. $el.append(wrapper);
  964. });
  965. $(document)
  966. .off("dblclick", ".amount input")
  967. .on("dblclick", ".amount input", function () {
  968. const $row = $(this).closest("li.clearfix");
  969. const qty = $row.find(".item-amount.qty").text().trim();
  970. if (qty) {
  971. $(this).val(qty);
  972. $(this)[0].dispatchEvent(new Event("input", { bubbles: true }));
  973. $(this)[0].dispatchEvent(new Event("keyup", { bubbles: true }));
  974. }
  975. });
  976. }
  977. function addManagePageCheckboxes() {
  978. $(".item___jLJcf").each(function () {
  979. const $row = $(this);
  980. const $desc = $row.find(".desc___VJSNQ");
  981. if (!$desc.length || $desc.find(".checkbox-wrapper").length)
  982. return;
  983. $desc.css("position", "relative");
  984. const wrapper = $('<div class="checkbox-wrapper"></div>');
  985. const checkbox = $("<input>", {
  986. type: "checkbox",
  987. class: "item-toggle",
  988. click: async function (e) {
  989. e.stopPropagation();
  990. if (!GM_getValue("tornApiKey", "")) {
  991. alert("No Torn API key set. Please click the 'Bazaar Filler Settings' button to enter your API key.");
  992. $(this).prop("checked", false);
  993. openSettingsModal();
  994. return;
  995. }
  996. const $row = $(this).closest(".item___jLcf");
  997. if (window.innerWidth <= 784) {
  998. const $manageBtn = $row.find('button[aria-label="Manage"]').first();
  999. if ($manageBtn.length) {
  1000. if (!$manageBtn.find("span").hasClass("active___OTFsm")) {
  1001. $manageBtn.click();
  1002. }
  1003. setTimeout(async () => {
  1004. await updateManageRowMobile($row, this.checked);
  1005. }, 200);
  1006. return;
  1007. }
  1008. }
  1009. await updateManageRow($row, this.checked);
  1010. },
  1011. });
  1012. wrapper.append(checkbox);
  1013. $desc.append(wrapper);
  1014. });
  1015. }
  1016.  
  1017. const storedItems = localStorage.getItem("tornItems");
  1018. const lastUpdatedTime = GM_getValue("lastUpdatedTime", 0);
  1019. const now = Date.now();
  1020. const oneDayMs = 24 * 60 * 60 * 1000;
  1021. const lastUpdatedDate = new Date(lastUpdatedTime);
  1022. const todayUTC = new Date().toISOString().split("T")[0];
  1023. const lastUpdatedUTC = lastUpdatedDate.toISOString().split("T")[0];
  1024. if (apiKey &&
  1025. (!storedItems || lastUpdatedUTC < todayUTC || now - lastUpdatedTime >= oneDayMs)) {
  1026. fetch(`https://api.torn.com/torn/?key=${apiKey}&selections=items&comment=wBazaarFiller`)
  1027. .then((r) => r.json())
  1028. .then((data) => {
  1029. if (!data.items) {
  1030. console.error("Failed to fetch Torn items or no items found. Possibly invalid API key or rate limit.");
  1031. return;
  1032. }
  1033. const filtered = {};
  1034. for (const [id, item] of Object.entries(data.items)) {
  1035. if (item.tradeable) {
  1036. filtered[id] = {
  1037. name: item.name,
  1038. market_value: item.market_value,
  1039. };
  1040. }
  1041. }
  1042. localStorage.setItem("tornItems", JSON.stringify(filtered));
  1043. GM_setValue("lastUpdatedTime", now);
  1044. })
  1045. .catch((err) => {
  1046. console.error("Error fetching Torn items:", err);
  1047. });
  1048. }
  1049. const domObserver = new MutationObserver(() => {
  1050. if (window.location.hash === "#/add") {
  1051. addAddPageCheckboxes();
  1052. }
  1053. else if (window.location.hash === "#/manage") {
  1054. addManagePageCheckboxes();
  1055. }
  1056. addPricingSourceLink();
  1057. addBlackFridayToggle();
  1058. attachPriceFieldObservers();
  1059. });
  1060. domObserver.observe(document.body, { childList: true, subtree: true });
  1061. window.addEventListener("hashchange", () => {
  1062. currentPage = window.location.hash;
  1063. if (currentPage === "#/add") {
  1064. addAddPageCheckboxes();
  1065. }
  1066. else if (currentPage === "#/manage") {
  1067. addManagePageCheckboxes();
  1068. }
  1069. addPricingSourceLink();
  1070. addBlackFridayToggle();
  1071. attachPriceFieldObservers();
  1072. });
  1073. if (currentPage === "#/add") {
  1074. addAddPageCheckboxes();
  1075. }
  1076. else if (currentPage === "#/manage") {
  1077. addManagePageCheckboxes();
  1078. }
  1079. addPricingSourceLink();
  1080. addBlackFridayToggle();
  1081. attachPriceFieldObservers();
  1082. $(document).on("click", "button.undo___FTgvP", function (e) {
  1083. e.preventDefault();
  1084. $(".item___jLJcf .checkbox-wrapper input.item-toggle:checked").each(function () {
  1085. $(this).prop("checked", false);
  1086. const $row = $(this).closest(".item___jLJcf");
  1087. updateManageRow($row, false);
  1088. });
  1089. });
  1090. $(document).on("click", ".clear-action", function (e) {
  1091. e.preventDefault();
  1092. $("li.clearfix .checkbox-wrapper input.item-toggle:checked").each(function () {
  1093. $(this).prop("checked", false);
  1094. const $row = $(this).closest("li.clearfix");
  1095. updateAddRow($row, false);
  1096. });
  1097. });
  1098. $(document).ready(function () {
  1099. itemMarketCache = {};
  1100. tornPalCache = { time: 0, data: null };
  1101. });
  1102. })();

QingJ © 2025

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