FFProgs

Minor improvements to FFlogs and progression tools.

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

  1. // ==UserScript==
  2. // @name FFProgs
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1.0
  5. // @description Minor improvements to FFlogs and progression tools.
  6. // @author You
  7. // @match https://www.fflogs.com/*
  8. // @icon https://assets.rpglogs.com/img/ff/favicon.png?v=2
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. if (/(^|\.)fflogs\.com$/.test(document.location.hostname) === false) { return; };
  14.  
  15. // Helper functions
  16. function elements(arr) {
  17. return arr.map(e => document.getElementById(e));
  18. }
  19.  
  20. function addGlobalEventListener(type, selector, callaBack) {
  21. document.addEventListener(type, e => {
  22. if (e.target.matches(selector)) callaBack(e);
  23. })
  24. }
  25.  
  26. function retrieveWindowVariables(variables) {
  27. const ret = {};
  28.  
  29. let scriptContent = "";
  30. for (let i = 0; i < variables.length; i++) {
  31. const currVariable = variables[i];
  32. scriptContent += `if (typeof ${currVariable} !== \"undefined\") $(\"body\").attr(\"tmp_${currVariable}\", JSON.stringify(${currVariable}));\n`
  33. }
  34.  
  35. const script = document.createElement("script");
  36. script.id = "tmpScript";
  37. script.appendChild(document.createTextNode(scriptContent));
  38. (document.body || document.head || document.documentElement).appendChild(script);
  39.  
  40. for (let i = 0; i < variables.length; i++) {
  41. const currVariable = variables[i];
  42. ret[currVariable] = $.parseJSON($("body").attr(`tmp_${currVariable}`));
  43. $("body").removeAttr(`tmp_${currVariable}`);
  44. }
  45.  
  46. $("#tmpScript").remove();
  47.  
  48. return ret;
  49. }
  50.  
  51. // Easter Egg/Credits
  52. const characterName = document.querySelector("#character-name > .character-name-link");
  53. if (characterName) {
  54. if (characterName.innerHTML === "Chad Bradly") {
  55. // GIGACHAD
  56. const characterPortrait = document.getElementById("character-portrait-image");
  57. characterPortrait.src = "https://c.tenor.com/epNMHGvRyHcAAAAd/gigachad-chad.gif";
  58.  
  59. // Adds background to your own page
  60. const addBackgroundList = ["portrait-and-basics", "character-header-customize-action-box", "update-box"];
  61. elements(addBackgroundList).forEach((e) => {
  62. if (e) {
  63. e.className = "slightly-transparent-box";
  64. }
  65. })
  66. const banner = document.getElementById("character-portrait-box");
  67. if (banner) {
  68. banner.style = "background-image: url(\"https://cdn.discordapp.com/attachments/613521566185029642/925189465868218368/ffxiv_dx11_sH6dfhzjue.jpg\");";
  69. banner.className = "with-banner";
  70. }
  71. }
  72. }
  73.  
  74. // Adblock
  75. const deleteList = ["top-banner", "bottom-banner", "playwire-video-container", "patron-box", "gear-box-ad"];
  76. elements(deleteList).forEach(e => {
  77. if (e) {
  78. e.outerHTML = "";
  79. }
  80. });
  81.  
  82. // Removes alt-text from item images.
  83. const imgs = document.getElementsByClassName("gear-img-cell");
  84. for (let i = 0; i < imgs.length; i++) {
  85. const img = imgs[i].firstChild;
  86. if (img) {
  87. img.alt = "";
  88. }
  89. }
  90.  
  91. // XIVAnalysis button
  92. const tabs = document.getElementById("top-level-view-tabs");
  93. if (tabs) {
  94. function getReportUrl() {
  95. const reportURL = document.location.href.split("/reports/")[1];
  96. const [report, reportInfo] = reportURL.split("#");
  97. let fight, job;
  98. if (reportInfo) {
  99. reportInfo.split("&").forEach((e) => {
  100. const [key, value] = e.split("=")
  101. if (key === "fight") {
  102. fight = value;
  103. }
  104. if (fight && key === "source") {
  105. job = value;
  106. }
  107. })
  108. }
  109. let url = "https://xivanalysis.com/fflogs";
  110. [report, fight, job].forEach((urlElement) => {
  111. if (urlElement) {
  112. url += `/${urlElement}`;
  113. }
  114. });
  115. return url;
  116. }
  117.  
  118. function refreshAnalysis() {
  119. const xivanalysisButton = document.getElementById("xivanalysis-tab");
  120. const url = getReportUrl();
  121. xivanalysisButton.href = url;
  122. }
  123.  
  124. const url = getReportUrl();
  125. const xivanalysisButton = document.createElement("a");
  126. xivanalysisButton.href = url;
  127. xivanalysisButton.target = "_blank";
  128. xivanalysisButton.classList.add("big-tab", "view-type-tab");
  129. xivanalysisButton.id = "xivanalysis-tab";
  130.  
  131. const icon = document.createElement("span");
  132. icon.classList.add("zmdi", "zmdi-time-interval");
  133. xivanalysisButton.appendChild(icon);
  134.  
  135. const text = document.createElement("span");
  136. text.classList.add("big-tab-text");
  137. text.innerHTML = "<br>xivanalysis";
  138. xivanalysisButton.appendChild(text);
  139.  
  140. tabs.firstChild.before(xivanalysisButton);
  141.  
  142. tabs.addEventListener("click", (e) => { refreshAnalysis(); });
  143. }
  144.  
  145. //Video Player Stuff
  146. const videoButton = document.querySelector(".replay-video");
  147. if (videoButton) {
  148. const streams = {};
  149. videoButton.addEventListener("click", (e) => {
  150. const selectVideoButton = document.getElementById("select-video");
  151. if (selectVideoButton) {
  152. // Functions for the multistream buttons.
  153. const videoFrame = document.getElementById("video-frame-inner");
  154.  
  155. function showMultistreamFrame() {
  156. const platform = "youTube"
  157. const iframe = document.createElement("iframe");
  158. iframe.id = "player";
  159. iframe.style = "border: none; width:100%; height: 100%;";
  160. iframe.allowFullscreen = "1";
  161. iframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture;";
  162. iframe.title = "YouTube video player";
  163.  
  164. videoFrame.innerHTML = iframe.outerHTML;
  165. }
  166.  
  167. function showMultistreamOptions() {
  168. const div = document.createElement("div");
  169. div.style = "text-align: center;";
  170.  
  171. const form = document.createElement("form");
  172. form.style = "margin: 0;";
  173. form.acceptCharset = "utf-8";
  174. form.method = "GET";
  175. form.action = "javascript:void(0);";
  176.  
  177. const table = document.createElement("table");
  178. table.style = "border-collapse: separate; border-spacing: 8px; margin: auto; text-align: left;";
  179.  
  180. //Table Infromation
  181. const infoRow = document.createElement("tr");
  182. const nameInfo = document.createElement("td");
  183. const URLInfo = document.createElement("td");
  184. const offsetInfo = document.createElement("td");
  185. nameInfo.innerHTML = "Name:";
  186. URLInfo.innerHTML = "Stream URL:";
  187. offsetInfo.innerHTML = "Offset:";
  188. offsetInfo.style = "max-width: 60px;";
  189.  
  190. infoRow.append(nameInfo, URLInfo, offsetInfo);
  191. table.appendChild(infoRow);
  192.  
  193. const raidFrames = document.querySelectorAll(".raid-frame-contents");
  194. const raiders = [];
  195. raidFrames.forEach((frame) => {
  196. raiders.push(frame.innerHTML);
  197. })
  198.  
  199. raiders.forEach((person) => {
  200. const id = person.replace(/ /g, "_");
  201.  
  202. const tableRow = document.createElement("tr");
  203. const nameData = document.createElement("td");
  204. nameData.innerHTML = person;
  205. tableRow.appendChild(nameData);
  206. const streamData = document.createElement("td");
  207. const streamUrl = document.createElement("input");
  208. streamUrl.style = "min-width: 260px;"
  209. streamUrl.type = "text";
  210. streamUrl.id = `${id}-stream_url`;
  211. streamUrl.name = `${id}-stream_url`;
  212. streamUrl.placeholder = "youtube.com/watch?v= or twitch.tv/videos/";
  213. if (streams[id] && streams[id].url) {
  214. streamUrl.setAttribute("value", streams[id].url);
  215. }
  216. streamUrl.classList.add("url-table-row");
  217. streamData.appendChild(streamUrl);
  218. tableRow.appendChild(streamData);
  219.  
  220. const offsetData = document.createElement("td");
  221. const offsetTime = document.createElement("input");
  222. offsetTime.size = "3";
  223. offsetTime.id = `${id}-stream_offset`;
  224. offsetTime.name = `${id}-stream_offset`;
  225. offsetTime.placeholder = "# in sec";
  226. if (streams[id] && streams[id].offset) {
  227. offsetTime.setAttribute("value", streams[id].offset)
  228. }
  229. offsetTime.classList.add("url-table-row");
  230.  
  231. offsetData.appendChild(offsetTime);
  232. tableRow.appendChild(offsetData);
  233.  
  234. table.appendChild(tableRow);
  235. })
  236.  
  237. form.appendChild(table);
  238. div.appendChild(form);
  239. videoFrame.innerHTML = div.outerHTML;
  240. }
  241.  
  242. addGlobalEventListener("input", ".url-table-row", (e) => {
  243. const [user, action] = e.target.id.split("-");
  244. const value = e.target.value;
  245. if (action === "stream_url") {
  246. if (!streams[user]) streams[user] = {};
  247. streams[user].url = value;
  248. }
  249. if (action === "stream_offset") {
  250. if (!streams[user]) streams[user] = {};
  251. streams[user].offset = value;
  252. }
  253. })
  254.  
  255. // Creates Menu Below Video Player
  256. const multiStreamOptions = document.getElementById("multistream-options");
  257. if (!multiStreamOptions) {
  258.  
  259. const videoFrameControls = document.getElementById("video-frame-controls");
  260. const multiStreamO = document.createElement("span");
  261. multiStreamO.style = "float: right; margin-right: 10px;";
  262. multiStreamO.id = "multistream-options";
  263. multiStreamO.classList.add("graph-legend-button");
  264. multiStreamO.onclick = showMultistreamOptions;
  265. multiStreamO.innerText = "Multistream options"
  266. videoFrameControls.appendChild(multiStreamO);
  267.  
  268. const multiStreamV = document.createElement("span");
  269. multiStreamV.style = "margin-right: -1px; float: right;";
  270. multiStreamV.id = "multistream-view";
  271. multiStreamV.classList.add("graph-legend-button");
  272. multiStreamV.onclick = showMultistreamFrame;
  273. multiStreamV.innerText = "Multistream View";
  274. videoFrameControls.appendChild(multiStreamV);
  275. }
  276.  
  277. // Save Video URL (OLD)
  278. selectVideoButton.addEventListener("click", (e) => {
  279. const windowVariables = retrieveWindowVariables(["videoID", "videoOffset"]);
  280. if (windowVariables.videoID !== "none") {
  281. const videoURLInput = document.getElementById("video_url");
  282. videoURLInput.value = `https://www.youtube.com/watch?v=${windowVariables.videoID}`;
  283. }
  284. if (windowVariables.videoOffset) {
  285. const videoOffsetInput = document.getElementById("video_offset");
  286. videoOffsetInput.value = windowVariables.videoOffset;
  287. }
  288. })
  289. }
  290. })
  291. }
  292.  
  293.  
  294.  
  295. /*
  296. Backlog:
  297. Make youtube/twitch livestream work
  298. Streams work across multiple fights in one log.
  299. <- Upload to greasy fork here 0.1.0 ->
  300. Multiple POV"s depending on selected player.
  301. Add way to share log viewer and save current streams to a log.
  302.  
  303. Doing:
  304. Make youtube videos work.
  305. Make Twitch VOD"s work.
  306. Import/export settings
  307. Use my Own I frames
  308.  
  309.  
  310. Done:
  311. Save video inputted into video player,
  312. Make settings be able to add streams to players
  313. Save Stream/Offset to session storage on edit for the zones so no need for submit
  314.  
  315. */
  316. })();