FF Progs

Adds a Button to view logs in xivanalysis also some minor improvements.

当前为 2023-04-30 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name FF Progs
  3. // @name:en FF Progs
  4. // @namespace k_fizzel
  5. // @version 1.0.3
  6. // @author Chad Bradly
  7. // @description Adds a Button to view logs in xivanalysis also some minor improvements.
  8. // @description:en Adds a Button to view logs in xivanalysis also some minor improvements.
  9. // @website https://www.fflogs.com/character/id/12781922
  10. // @icon https://assets.rpglogs.com/img/ff/favicon.png?v=2
  11. // @match https://*.etro.gg/*
  12. // @match https://*.fflogs.com/*
  13. // @grant unsafeWindow
  14. // @grant GM_addStyle
  15. // @grant GM_getValue
  16. // @grant GM_setValue
  17. // @grant GM_deleteValue
  18. // @license MIT License
  19. // ==/UserScript==
  20.  
  21. (function () {
  22. "use strict";
  23. //check if the domain is fflogs.com
  24. if (/fflogs\.com/.test(location.hostname)) {
  25. // Adblocker
  26. $("#top-banner, .side-rail-ads, #bottom-banner, #subscription-message-tile-container, #playwire-video-container, #right-ad-box, #right-vertical-banner").remove();
  27. $("#table-container").css("margin", "0 0 0 0");
  28.  
  29. if (/\/reports\/.+/.test(location.pathname)) {
  30. // Add XIV Analysis Button
  31. $("#filter-analyze-tab").before(
  32. `<a href="https://xivanalysis.com/report-redirect/${location.href}" target="_blank" class="big-tab view-type-tab" id="xivanalysis-tab"><span class="zmdi zmdi-time-interval"></span> <span class="big-tab-text"><br>xivanalysis</span></a>`
  33. );
  34. $("#xivanalysis-tab").click(() => {
  35. $("#xivanalysis-tab").attr("href", `https://xivanalysis.com/report-redirect/${location.href}`);
  36. });
  37.  
  38. let jobs;
  39. const rankOnes = {};
  40. const tableContainer = document.querySelector("#table-container");
  41.  
  42. const getHash = () => {
  43. return location.hash
  44. .replace("#", "")
  45. .split("&")
  46. .reduce((res, item) => {
  47. const parts = item.split("=");
  48. res[parts[0]] = parts[1];
  49. return res;
  50. }, {});
  51. };
  52.  
  53. const characterAllStar = (rank, outOf, rDPS, rankOneRDPS) => {
  54. return Math.min(Math.max(100 * (rDPS / rankOneRDPS), 100 - (rank / outOf) * 100) + 20 * (rDPS / rankOneRDPS), 120);
  55. };
  56.  
  57. const addASPToTables = () => {
  58. if (getHash().view === "rankings") {
  59. if (!GM_getValue("apiKey")) return;
  60. const rows = [];
  61. if (!jobs) {
  62. fetch(`https://www.fflogs.com/v1/classes?api_key=${GM_getValue("apiKey")}`)
  63. .then((res) => res.json())
  64. .then((data) => {
  65. jobs = data[0].specs;
  66. rows.forEach((row) => {
  67. updatePoints(row);
  68. });
  69. })
  70. .catch((err) => console.error(err));
  71. } else {
  72. setTimeout(() => {
  73. rows.forEach((row) => {
  74. updatePoints(row);
  75. });
  76. }, 0);
  77. }
  78.  
  79. const updatePoints = async (row) => {
  80. const rank = Number($(row).find("td:nth-child(2)").text().replace("~", ""));
  81. const outOf = Number($(row).find("td:nth-child(3)").text().replace(",", ""));
  82. const dps = Number($(row).find("td:nth-child(6)").text().replace(",", ""));
  83. const jobName = $(row).find("td:nth-child(5) > a").attr("class") || "";
  84. const jobName2 = $(row).find("td:nth-child(5) > a:nth-last-child(1)").attr("class") || "";
  85. const playerMetric = getHash().playermetric || "rdps";
  86.  
  87. if (jobName2 !== "players-table-realm") {
  88. $(row)
  89. .find("td:nth-child(7)")
  90. .html(`<center><img src="https://cdn.7tv.app/emote/62523dbbbab59cfd1b8b889d/1x.webp" title="no api endpoint for combind damage" style="height: 15px;"></center>`);
  91. return;
  92. }
  93.  
  94. const updateCharecterAllStar = async () => {
  95. $(row).find("td:nth-child(7)").html(characterAllStar(rank, outOf, dps, rankOnes[jobName][playerMetric]).toFixed(2));
  96. };
  97.  
  98. if (!rankOnes[jobName]) {
  99. rankOnes[jobName] = {};
  100. }
  101.  
  102. if (!rankOnes[jobName][playerMetric]) {
  103. const url = `https://www.fflogs.com/v1/rankings/encounter/${reportsCache.filterFightBoss}?metric=${playerMetric}&spec=${
  104. jobs.find((job) => job.name.replace(" ", "") === jobName)?.id
  105. }&api_key=${GM_getValue("apiKey")}`;
  106. fetch(url)
  107. .then((res) => res.json())
  108. .then((data) => {
  109. rankOnes[jobName][playerMetric] = Number(data.rankings[0].total.toFixed(1));
  110. updateCharecterAllStar();
  111. })
  112. .catch((err) => console.error(err));
  113. } else {
  114. updateCharecterAllStar();
  115. }
  116. };
  117.  
  118. $(".player-table").each((_i, table) => {
  119. $(table)
  120. .find("thead tr th:nth-child(6)")
  121. .after(
  122. `<th class="sorting ui-state-default" tabindex="0" aria-controls="DataTables_Table_0" rowspan="1" colspan="1" aria-label="Patch: activate to sort column ascending"><div class="DataTables_sort_wrapper">Points<span class="DataTables_sort_icon css_right ui-icon ui-icon-caret-2-n-s"></span></div></th>`
  123. );
  124. $(table)
  125. .find("tbody tr")
  126. .each((_i, row) => {
  127. $(row)
  128. .find("td:nth-child(6)")
  129. .after(`<td class="rank-per-second primary main-table-number"><center><span class="zmdi zmdi-spinner zmdi-hc-spin" style="color:white font-size:24px"></center></span></td>`);
  130. rows.push(row);
  131. });
  132. });
  133. }
  134. };
  135.  
  136. if (tableContainer) {
  137. const observer = new MutationObserver(addASPToTables);
  138. observer.observe(tableContainer, { attributes: true, characterData: true, childList: true });
  139. }
  140. }
  141.  
  142. if (/\/zone\/.+/.test(location.pathname)) {
  143. const verticalContent = document.querySelector(".vertical-content");
  144. const contentAndAds = document.querySelector(".content--full-width");
  145. const pageContainer = document.querySelector(".progress-race-page-container");
  146.  
  147. const removeAdds = () => {
  148. const elements = document.querySelectorAll(".content-and-ads__ads");
  149. const resize = document.querySelector(".content-and-ads--with-ads");
  150. const resize2 = document.querySelector(".content-and-ads__content");
  151. if (resize) {
  152. resize.style = "grid-template-columns: 1fr !important;";
  153. resize2.style = "grid-template-columns: 1fr !important;";
  154. }
  155. for (const element of elements) {
  156. if (element) {
  157. element.remove();
  158. }
  159. }
  160. };
  161.  
  162. if (verticalContent) {
  163. const observer = new MutationObserver(removeAdds);
  164. observer.observe(verticalContent, { attributes: true, characterData: true, childList: true });
  165. }
  166.  
  167. if (contentAndAds) {
  168. const observer = new MutationObserver(removeAdds);
  169. observer.observe(contentAndAds, { attributes: true, characterData: true, childList: true });
  170. }
  171.  
  172. if (pageContainer) {
  173. const observer = new MutationObserver(removeAdds);
  174. observer.observe(pageContainer, { attributes: true, characterData: true, childList: true });
  175. }
  176. }
  177.  
  178. if (/\/character\/.+/.test(location.pathname)) {
  179. $(".table-icon").removeAttr("alt");
  180. const jobOrder = [
  181. "Paladin",
  182. "Warrior",
  183. "DarkKnight",
  184. "Gunbreaker",
  185. "WhiteMage",
  186. "Scholar",
  187. "Astrologian",
  188. "Sage",
  189. "Monk",
  190. "Dragoon",
  191. "Ninja",
  192. "Samurai",
  193. "Reaper",
  194. "Bard",
  195. "Machinist",
  196. "Dancer",
  197. "BlackMage",
  198. "Summoner",
  199. "RedMage",
  200. ];
  201. const jobList = $("#jobs-header-icons").children();
  202. const jobListSortedNumbers = [];
  203. const jobListSorted = [];
  204.  
  205. jobOrder.forEach((job) =>
  206. jobList
  207. .children()
  208. .toArray()
  209. .forEach((jobElement, i) => {
  210. if (jobElement.alt === job) jobListSortedNumbers.push(i);
  211. })
  212. );
  213. jobListSortedNumbers.forEach((i) => jobListSorted.push(jobList[i]));
  214. jobList.remove();
  215. $("#jobs-header-icons").append(jobListSorted);
  216.  
  217. // Chad Bradly's Profile Customization
  218. if (/\/character\/id\/12781922/.test(location.pathname)) {
  219. $("#character-portrait-image").attr("src", "https://media.tenor.com/epNMHGvRyHcAAAAd/gigachad-chad.gif");
  220. $("#portrait-and-basics, #character-header-customize-action-box, #update-box").addClass("slightly-transparent-box");
  221. $("#character-portrait-box").css("background-image", 'url("https://i.imgur.com/dbwqHIt.png")').addClass("with-banner");
  222. }
  223. }
  224.  
  225. if (/\/profile/.test(location.pathname)) {
  226. let apiKey = GM_getValue("apiKey");
  227.  
  228. $("#api").after(`<div id="extension" class="dialog-block"></div>`);
  229. $("#extension").append(`<div id="extension-title" class="dialog-title">FF Progs</div>`);
  230. $("#extension").append(`<div id="extension-content" style="margin:1em"></div>`);
  231.  
  232. const addApiInput = () => {
  233. $("#extension-content").append(`<div id="extension-contents" style="margin:1em">Enter your FFLogs API Key</div>`);
  234. $("#extension-content").append(`<input type=text id="apiKeyInput" style="margin-left: 10px">`);
  235. $("#extension-content").append(`<input type=button id="apiButton" style="margin-left: 10px" value="Save API Key">`);
  236. $("#apiButton").click(() => {
  237. apiKey = $("#apiKeyInput").val();
  238. GM_setValue("apiKey", apiKey);
  239. $("#apiButton").remove();
  240. $("#apiKeyInput").remove();
  241. $("#extension-content").append(`<div id="resloved-text" style="margin:1em">API Key Saved</div>`);
  242. setTimeout(() => {
  243. $("#extension-contents").remove();
  244. $("#resloved-text").remove();
  245. removeApiInput();
  246. }, 2000);
  247. });
  248. };
  249.  
  250. const removeApiInput = () => {
  251. $("#extension-content").append(`<input type=button id="apiDeleteButton" style="margin-left: 10px" value="Remove API Key">`);
  252. $("#apiDeleteButton").click(() => {
  253. GM_deleteValue("apiKey");
  254. $("#apiDeleteButton").remove();
  255. $("#extension-content").append(`<div id="resloved-text" style="margin:1em">API Key Removed</div>`);
  256. setTimeout(() => {
  257. $("#resloved-text").remove();
  258. addApiInput();
  259. }, 2000);
  260. });
  261. };
  262. if (!apiKey) {
  263. addApiInput();
  264. } else {
  265. removeApiInput();
  266. }
  267. }
  268. }
  269.  
  270. if (/etro\.gg/.test(location.hostname)) {
  271. document.querySelector(".mantine-Header-root").style.marginBottom = "0px";
  272. const body = document.querySelector(".mantine-AppShell-main");
  273. const removeAdGearset = () => {
  274. const elements = document.querySelectorAll('[id^="etro-ad"]');
  275.  
  276. for (const element of elements) {
  277. if (element) {
  278. element.remove();
  279. }
  280. }
  281. };
  282. if (body) {
  283. const observer = new MutationObserver(removeAdGearset);
  284. observer.observe(body, { attributes: true, characterData: true, childList: true });
  285. }
  286. }
  287. })();