YATA

Displays various informations from YATA's API

当前为 2023-10-17 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YATA
  3. // @namespace yata.yt
  4. // @version 0.12
  5. // @grant GM_addStyle
  6. // @description Displays various informations from YATA's API
  7. // @author Kivou [2000607]
  8. // @grant GM.xmlHttpRequest
  9. // @match https://www.torn.com/factions.php*
  10. // @match https://www.torn.com/preferences.php*
  11. // @match https://www.torn.com/profiles.php*
  12. // @icon https://yata.yt/media/yata-small.png
  13. // @require https://gf.qytechs.cn/scripts/477604-kiv-lib/code/kiv-lib.js?version=1266053
  14. // @run-at document-end
  15. // @license WTFPL
  16. // ==/UserScript==
  17.  
  18. // Copyright © 2023 Kivou [2000607] <contact@yata.yt>
  19. // This work is free. You can redistribute it and/or modify it under the
  20. // terms of the Do What The Fuck You Want To Public License, Version 2,
  21. // as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
  22.  
  23.  
  24. // ------------- //
  25. // SETUP API KEY //
  26. // ------------- //
  27. const display_status = (element) => {
  28. const key = localStorage.getItem('key');
  29. let innerHTML = "";
  30. innerHTML += `<div>`;
  31. innerHTML += `<b>[YATA]</b> API key used <span style="font-family: monospace; font-weight: bold;">${key}</span>`;
  32. if (key) {
  33. innerHTML += ` | Status <b id="yata-status" style="color: var(--default-green-color); font-weight: bold;">enabled</b>`;
  34. innerHTML += ` | Click <span id="yata-api-key-rm" class="t-blue" style="cursor: pointer;">here to disable</span> the script`;
  35. } else {
  36. innerHTML += ` | Status <b id="yata-status" style="color: var(--default-red-color); font-weight: bold;">disabled</b>`;
  37. innerHTML += ` | Click on a key to enable the script`;
  38. }
  39. innerHTML += `</div>`;
  40. innerHTML += `<div class="clear"></div>`;
  41. innerHTML += `<hr class="page-head-delimiter m-top10">`;
  42. element.innerHTML = innerHTML;
  43. };
  44.  
  45. waitFor(document, "div.preferences-container").then(div => {
  46.  
  47. let injected = false;
  48. const display_element = document.createElement("div");
  49. div.insertAdjacentElement('beforebegin', display_element);
  50.  
  51. // triggered by clicking on crimes tab
  52. const callback = (mutations, observer) => {
  53. [...mutations].forEach(mutation => {
  54. [...mutation.addedNodes].filter(n => n.className && n.className.includes("keyRow___")).forEach(node => {
  55. const key_node = node.querySelector("input");
  56. key_node.style.cursor = "pointer";
  57. // if(!localStorage.getItem('key')) {
  58. // localStorage.setItem('key', key_node.value);
  59. // document.getElementById("yata-api-key").innerHTML = localStorage.getItem('key')
  60. // }
  61. });
  62. });
  63. if (!injected) {
  64. display_status(display_element);
  65. injected = true;
  66. }
  67. };
  68. const observer = new MutationObserver(callback);
  69. observer.observe(div, { childList: true, subtree: true });
  70.  
  71. document.querySelector("div.content-wrapper").addEventListener('click', e => {
  72. const button = e.target;
  73. if (button.tagName == 'INPUT' && button.id.includes('key-row')) {
  74. localStorage.setItem('key', button.value);
  75. }
  76. else if (button.tagName == 'SPAN' && button.id == 'yata-api-key-rm') {
  77. localStorage.clear();
  78. }
  79. display_status(display_element);
  80. });
  81.  
  82. });
  83.  
  84.  
  85. // -------------- //
  86. // OC: NNB + rank //
  87. // -------------- //
  88. const display_nnb = (members, player) => {
  89. const urlParams = new URLSearchParams(player.children[0].children[0].href.split("?")[1]);
  90. const lvl = player.children[1].innerText.trim();
  91. if (members.members && members.members.hasOwnProperty(urlParams.get("XID"))) {
  92. const m = members.members[urlParams.get("XID")];
  93. if (m.nnb_share > 0) {
  94. player.children[1].innerHTML = `<span>#<b>${m.crimes_rank}</b> / <b>${m.nnb}</b> / ${lvl}</span>`;
  95. } else if (m.nnb_share < 0) {
  96. player.children[1].innerHTML = `<span title="Not on YATA">#<b>${m.crimes_rank}</b> / <b>!</b> / ${lvl}</span>`;
  97. } else {
  98. player.children[1].innerHTML = `<span title="Not sharing NNB">#<b>${m.crimes_rank}</b> / <b>?</b> / ${lvl}</span>`;
  99. }
  100. } else {
  101. player.children[1].innerHTML = `<span title="Not found">#<b>?</b> / <b>err</b> / ${lvl}</span>`;
  102. }
  103. };
  104.  
  105. waitFor(document, "div#faction-crimes").then(div => {
  106.  
  107. const key = localStorage.getItem('key');
  108.  
  109. if (!key) { return; }
  110.  
  111. const profile_url = new URLSearchParams(window.location.search);
  112. const target_id = profile_url.get("XID");
  113.  
  114. gmGet(`https://yata.yt/api/v1/faction/members/?key=${key}`, 'nnb').then(members => {
  115.  
  116. // triggered if directly landing on crimes
  117. div.querySelectorAll("ul.details-list, ul.plans-list").forEach(ul => {
  118. ul.querySelectorAll("ul.item").forEach(player => {
  119. display_nnb(members, player);
  120. });
  121. });
  122. div.querySelectorAll("ul.title li.level").forEach(t => {
  123. t.innerHTML = 'Rank / NNB / Level';
  124. });
  125.  
  126. // triggered by clicking on crimes tab
  127. const callback = (mutations, observer) => {
  128. [...mutations].forEach(mutation => {
  129. [...mutation.addedNodes].filter(n => n.className && n.className.includes("faction-crimes-wrap")).forEach(node => {
  130. node.querySelectorAll("ul.details-list, ul.plans-list").forEach(ul => {
  131. const ocs = ul.querySelectorAll("ul.item");
  132. console.log(`[yata] displaying NNB for OC: ${ocs.length}`);
  133. ocs.forEach(player => {
  134. display_nnb(members, player);
  135. });
  136. });
  137. node.querySelectorAll("ul.title li.level").forEach(t => {
  138. t.innerHTML = 'Rank / NNB / Level';
  139. });
  140. });
  141. });
  142. };
  143. const observer = new MutationObserver(callback);
  144. observer.observe(div, { childList: true });
  145.  
  146.  
  147. }).catch(error => {
  148. alert(`[yata] ${error.message}`);
  149. if (error.message == "Incorrect key") {
  150. localStorage.removeItem('key');
  151. }
  152. });
  153. });
  154.  
  155.  
  156. // ---------------------- //
  157. // PROFILE //
  158. // ---------------------- //
  159.  
  160. waitFor(document, "a.profile-button-report").then(a => {
  161.  
  162. const div = document.getElementById("profileroot");
  163. const key = localStorage.getItem('key');
  164. const profile_url = new URLSearchParams(a.href.split("?")[1]);
  165. const target_id = profile_url.get("userID");
  166.  
  167. if (!target_id) { return; }
  168. if (!key) { return; }
  169.  
  170. gmGet(`https://yata.yt/api/v1/bs/${target_id}/?key=${key}`, `bs-${target_id}`).then(bs => {
  171.  
  172. let innerHTML = "";
  173. innerHTML += `<hr class="page-head-delimiter m-top10 m-bottom10">`;
  174. innerHTML += `<div>`;
  175. innerHTML += `<b>[YATA]</b> <b>Battle stats</b> ${floatFormat(bs[target_id].total, 3)}`;
  176. innerHTML += ` | <b>Build</b> ${bs[target_id].type} (${bs[target_id].skewness}%)`;
  177. innerHTML += `</div>`;
  178. innerHTML += `<hr class="page-head-delimiter m-top10 m-bottom10">`;
  179. innerHTML += `<div class="clear"></div>`;
  180. const bs_node = document.createElement("div");
  181. bs_node.innerHTML = innerHTML;
  182. div.querySelector("div.profile-wrapper").insertAdjacentElement('afterend', bs_node);
  183. console.log(`[yata] battle stats estimate on profile page: ${target_id}`);
  184.  
  185. }).catch(error => {
  186. alert(`[yata] ${error.message}`);
  187. if (error.code == 4) {
  188. localStorage.removeItem('key');
  189. }
  190. });
  191.  
  192. });
  193.  
  194.  
  195. // -------------------------- //
  196. // FACTIONS: Helper functions //
  197. // -------------------------- //
  198. const bse_html = (id, bs) => {
  199.  
  200. if (!bs.hasOwnProperty("type")) {
  201. return `<a style="text-decoration: none; display: inline-block;" href="/loader.php?sid=attack&user2ID=${id}" target="_blank"><span title='${bs["message"]}' style="color: var(--default-red-color);">err</span></a>`;
  202. }
  203.  
  204. let color = "var(--default-blue-color)";
  205. if (bs.type == "Offensive" && bs.skewness > 20) {
  206. color = "var(--default-red-color)";
  207. } else if (bs.type == "Defensive" && bs.skewness > 20) {
  208. color = "var(--default-green-color)";
  209. }
  210. const title = `Total stats: ${bs.total.toLocaleString("en-GB")} Build: ${bs.type} (${bs.skewness}%)`;
  211. return `<a style="text-decoration: none; display: inline-block;" href="/loader.php?sid=attack&user2ID=${id}" target="_blank"><span title="${title}" style="color: ${color};">${floatFormat(bs.total, 3)}</span></a>`;
  212. };
  213.  
  214. // ---------------------- //
  215. // FACTIONS: Members list //
  216. // ---------------------- //
  217. const members_list_filter = (div) => {
  218. if (div.tagName != "DIV") { return false; }
  219. if (div.classList.contains("faction-info-wrap")) { return true; }
  220. return Boolean(div.querySelector("div.faction-info-wrap"));
  221. };
  222.  
  223. const members_list_display = (members, key) => {
  224. console.log(`[yata] battle stats estimate for members list: ${members.length}`);
  225. [...members].forEach(member => {
  226. const url = new URLSearchParams(member.querySelector("div.member.icons").querySelector("div[class^=userWrap]").querySelector("a[class^=linkWrap]").href.split("?")[1]);
  227. const member_id = url.get("XID");
  228.  
  229. gmGet(`https://yata.yt/api/v1/bs/${member_id}/?key=${key}`, `bs-${member_id}`).then(bs => {
  230. const node = document.createElement("span");
  231. node.innerHTML = bse_html(member_id, bs[member_id]);
  232. node.style.width = "4em";
  233. // member.querySelector("div.member-icons").insertAdjacentElement('afterbegin', node);
  234. member.querySelector("div.position").insertAdjacentElement('afterbegin', node);
  235. }).catch((error) => {
  236. const node = document.createElement("span");
  237. node.innerHTML = bse_html(member_id, error);
  238. node.style.width = "4em";
  239. // member.querySelector("div.member-icons").insertAdjacentElement('afterbegin', node);
  240. member.querySelector("div.position").insertAdjacentElement('afterbegin', node);
  241. });
  242. });
  243. };
  244.  
  245. // -------------- //
  246. // FACTIONS: Wars //
  247. // -------------- //
  248. const wars_list_filter = (node) => {
  249. if (node.tagName != "DIV") { return false; }
  250. return node.tagName == "DIV" && node.classList.contains("faction-war");
  251. };
  252.  
  253. const wars_list_display = (members, type, key) => {
  254. console.log(`[yata] battle stats estimate for ${type}: ${members.length}`);
  255.  
  256. [...members].forEach(member => {
  257. // STEP 1: get target ID
  258.  
  259. let member_id = undefined;
  260.  
  261. if (type == "wall") {
  262. const url = new URLSearchParams(
  263. member.querySelector("a.user.name").href.split("?")[1]
  264. );
  265. member_id = url.get("XID");
  266. } else {
  267. const url = new URLSearchParams(
  268. member.querySelector("div.member.icons")
  269. .querySelector("div[class^=userWrap]")
  270. .querySelector("a[class^=linkWrap]").href.split("?")[1]
  271. );
  272. member_id = url.get("XID");
  273. }
  274.  
  275. // STEP 2: make call
  276. gmGet(`https://yata.yt/api/v1/bs/${member_id}/?key=${key}`, `bs-${member_id}`).then(bs => {
  277. const node = document.createElement("span");
  278. node.innerHTML = bse_html(member_id, bs[member_id]);
  279.  
  280. if (type == "wall") {
  281. // replace attack link
  282. node.style.paddingRight = "0.5em";
  283. member.children[4].innerHTML = node.outerHTML;
  284. } else if (type == "rank-right") {
  285. // prepend to level
  286. node.style.paddingRight = "1em";
  287. member.children[1].insertAdjacentElement('afterbegin', node);
  288. } else if (type == "rank-left") {
  289. // replace link
  290. node.style.paddingRight = "0.5";
  291. member.children[4].innerHTML = node.outerHTML;
  292. } else {
  293. // replace attack link
  294. node.style.paddingRight = "0.5em";
  295. member.children[5].innerHTML = node.outerHTML;
  296. }
  297. }).catch((error) => {
  298. const node = document.createElement("span");
  299. node.innerHTML = bse_html(member_id, error);
  300.  
  301. if (type == "wall") {
  302. // replace attack link
  303. node.style.paddingRight = "0.5em";
  304. member.children[4].innerHTML = node.outerHTML;
  305. } else if (type == "rank-right") {
  306. // prepend to level
  307. node.style.paddingRight = "1em";
  308. member.children[1].insertAdjacentElement('afterbegin', node);
  309. } else if (type == "rank-left") {
  310. // replace link
  311. node.style.paddingRight = "0.5";
  312. member.children[4].innerHTML = node.outerHTML;
  313. } else {
  314. // replace attack link
  315. node.style.paddingRight = "0.5em";
  316. member.children[5].innerHTML = node.outerHTML;
  317. }
  318. });
  319. });
  320. };
  321.  
  322. const chain_list_filter = (node) => {
  323. if (node.tagName != "UL") { return false; }
  324. return node.tagName == "UL" && node.classList.contains("chain-attacks-list");
  325. };
  326.  
  327. const chain_list_display = (attacks, key) => {
  328. console.log(`[yata] battle stats estimate for chain: ${attacks.length}`);
  329.  
  330. const _f = (e) => { return Boolean(e.querySelector("div.right-player div[class^=userInfoBox]")); };
  331. [...attacks].filter(_f).forEach(attack => {
  332.  
  333. const target = attack.querySelector("div.right-player");
  334. const url = new URLSearchParams(
  335. target.querySelector("div.member.icons")
  336. .querySelector("div[class^=userWrap]")
  337. .querySelector("a[class^=linkWrap]").href.split("?")[1]
  338. );
  339. const target_id = url.get("XID");
  340. const respect = attack.querySelector("div.respect");
  341.  
  342. gmGet(`https://yata.yt/api/v1/bs/${target_id}/?key=${key}`, `bs-${target_id}`).then(bs => {
  343. const node = document.createElement("span");
  344. node.innerHTML = bse_html(target_id, bs[target_id]);
  345. node.style.float = 'right';
  346. respect.insertAdjacentElement('beforeend', node);
  347. }).catch((error) => {
  348. const node = document.createElement("span");
  349. node.innerHTML = bse_html(target_id, error);
  350. node.style.float = 'right';
  351. respect.insertAdjacentElement('beforeend', node);
  352. });
  353.  
  354. });
  355. };
  356.  
  357. // ------------------- //
  358. // FACTIONS: observers //
  359. // ------------------- //
  360. const callback_factions = (key) => {
  361. return (mutations, observer) => {
  362.  
  363. const tab = window.location.hash.replace("#/tab=", "");
  364. if (["territory", "rank", "upgrades", "armoury", "controls"].includes(tab)) { return; }
  365.  
  366. [...mutations].forEach(mutation => {
  367.  
  368. [...mutation.addedNodes].forEach(node => {
  369.  
  370. // members list
  371. if (members_list_filter(node)) {
  372. const members = node.querySelectorAll("li.table-row");
  373. if (members.length) { members_list_display(members, key); }
  374. }
  375.  
  376. // chains
  377. if (chain_list_filter(node)) {
  378. chain_list_display(node.childNodes, key);
  379.  
  380. // observe new attacks
  381. const callback_chain = (key) => {
  382. return (mutations, observer) => {
  383. [...mutations].forEach(mutation => {
  384. const attacks = [...mutation.addedNodes];
  385. chain_list_display(attacks, key);
  386. });
  387. };
  388. };
  389. const walls_observer = new MutationObserver(callback_chain(key));
  390. walls_observer.observe(node, { childList: true });
  391. }
  392.  
  393. // wars
  394. if (wars_list_filter(node)) {
  395.  
  396. // 2 lists for RW, 1 for walls and 1 for raids
  397. [...node.querySelectorAll("div.members-cont > ul.members-list")].forEach(faction => {
  398. const members = faction.querySelectorAll("li.your, li.enemy");
  399.  
  400. if (tab.includes("rank")) { // RW
  401. if (members[0].classList.contains("your")) {
  402. wars_list_display(members, "rank-right", key);
  403. } else {
  404. wars_list_display(members, "rank-left", key);
  405. }
  406. } else if (tab.includes("raid")) { // Raids
  407. wars_list_display(members, "raid", key);
  408. } else { // walls
  409. wars_list_display(members, "wall", key);
  410.  
  411. // observe wall jumps
  412. const callback_walls = (key) => {
  413. return (mutations, observer) => {
  414. const _f = (e) => { return e.className.includes("your") || e.className.includes("enemy"); };
  415. [...mutations].forEach(mutation => {
  416. const members = [...mutation.addedNodes].filter(_f);
  417. wars_list_display(members, "wall", key);
  418. });
  419. };
  420. };
  421. const walls_observer = new MutationObserver(callback_walls(key));
  422. walls_observer.observe(faction, { childList: true });
  423. }
  424. });
  425. }
  426.  
  427. });
  428. });
  429. };
  430. };
  431.  
  432. waitFor(document, "div#factions").then(factions => {
  433.  
  434. const key = localStorage.getItem('key');
  435. if (!key) { return; }
  436.  
  437. const obs = new MutationObserver(callback_factions(key));
  438. obs.observe(factions, { childList: true, subtree: true });
  439.  
  440. });

QingJ © 2025

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