石之家logs成分查询

一个通过石之家查询fflogs的小插件

  1. // ==UserScript==
  2. // @name 石之家logs成分查询
  3. // @name:en stonesLogsViewer
  4. // @namespace Umi
  5. // @version 1.0
  6. // @description 一个通过石之家查询fflogs的小插件
  7. // @description:en stonesLogsViewer--
  8. // @author Umi
  9. // @match *://ff14risingstones.web.sdo.com/*
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=undefined.
  11. // @grant GM_xmlhttpRequest
  12. // @license MIT
  13. // ==/UserScript==
  14. const delay = 500;
  15. //api token 可以使用自己的token
  16. const apiToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiI5YWUxMzk2ZC0xNmVmLTQ4NjEtOGIwZC05ZTc4NDRkM2JjMWMiLCJqdGkiOiJmZmVkMmI4ZTllNjUwNWI0ZTk4ZGNmN2Y0ZGZkZTlmZjg0MjE3YWIxMjE1ZDk1ZjhkNDBmYjIyMjlkNjE0MTQwODYwNjU1YTdmMzlmYjExMiIsImlhdCI6MTcwMjkyMTY1OS43MTIzNjIsIm5iZiI6MTcwMjkyMTY1OS43MTIzNjUsImV4cCI6MTczNDAyNTY1OS43MDMxOTcsInN1YiI6IiIsInNjb3BlcyI6WyJ2aWV3LXVzZXItcHJvZmlsZSIsInZpZXctcHJpdmF0ZS1yZXBvcnRzIl19.TQvn0mwLoEEppHcxuPnXINVLRjrLM0givJ-sltQfHEUFXVJj_r_QLS57ieDAlsjkzOH9ZMlrdBcK0fbMnDa93Jru95-CwNgjl261nYuBYmemAIkNWgVAwsOjXvNm4feoelRHtz8bpbZmCQ1S8FM8d0gwolueZp5t6Kku2Cpb2qaWv_cERh-uO44smPgiER4Z9j33JlfWvkC8brljVALcKjnu7i27R-qD6hBQhicZN7DJVnJkdZGceu9Kaoq0fJO0wr0E8s8BnWQTLJwW3uGSzXrQGUWATxrwALAqIa_2kjKnaC0sXGUnUGQZdckpW6VGHPnfCGslNarviK72n9kfIOlpJVhL-7T360-CgpEVpnFVxfQCTz0ZnRhqxwy9ixZ7OyAY8ZpQTerkGDb2HlZTTCBB-gOdrUmQvDDEfKnGPfuuHSL4TrY6Ndo-Ips8NAUy67nqGaqAUBmUi9MqA-ZL8oqtW3J_Q0HfbTVPk8RvpCelij9zQpC_LSc0kz46vnr47teiEd_4eY8zW6ataCRP4B7epp0aEp6e-UkJjZ05DBgzYwB-aromyrBB6cJPHOX61ywWoEJIYMb8RL6HdGE8vSxhYTrXZfcUBcmkvVZHFHkvmR4MIJ5zjpESr0gq8_gp1ol_FapBmt39Mpuk0GDRUsTCNJAtp6FRcAY8ul4PUJs";
  17. const url = "https://cn.fflogs.com/api/v2/client";
  18. const zones = [
  19. {
  20. "id": 54,
  21. "name": "万魔殿 荒天之狱",
  22. "difficulty": 101
  23. },
  24. {
  25. "id": 49,
  26. "name": "万魔殿 炼净之狱",
  27. "difficulty": 101
  28. },
  29. {
  30. "id": 44,
  31. "name": "万魔殿 边境之狱",
  32. "difficulty": 101
  33. },
  34. {
  35. "id": 53,
  36. "name": "欧米茄绝境验证战",
  37. "difficulty": 100
  38. },
  39. {
  40. "id": 45,
  41. "name": "幻想龙诗绝境战",
  42. "difficulty": 100
  43. },
  44. {
  45. "id": 43,
  46. "name": "绝境战(旧版本)",
  47. "difficulty": 100
  48. }
  49. ];
  50. const defaultZone = 54;
  51. (function() {
  52.  
  53. function ProcessLocationIcon(startingIcon,key)
  54. {
  55. let parentElement = startingIcon.parentElement.parentElement.parentElement;
  56. let usernameElement = FindUsernameElement(parentElement);
  57. //console.log(`获取的用户名: ${usernameElement ? usernameElement.innerText : '未找到'}`);
  58. let server = FindServer(startingIcon);
  59. //console.log(`获取的服务器: ${server}`);
  60. if (usernameElement && server) {
  61. InsertLogsIcon(usernameElement, server, key)
  62. }
  63. }
  64. function FindUsernameElement(parentElement)
  65. {
  66. // 非个人页:第一个能被鼠标点击、仅包含文本的 span 元素
  67. let elements = parentElement.querySelectorAll('span.cursor');
  68. //console.log("找到的元素数量(非个人页): ", elements.length);
  69. for (let element of elements)
  70. {
  71. if (element.childNodes.length == 1 && element.innerText.length >= 1 && element.innerText.length <= 6)
  72. {
  73. return element;
  74. }
  75. }
  76. // 个人页
  77. elements = parentElement.querySelectorAll('span.ft24.ftw');
  78. for (let element of elements)
  79. {
  80. if (element.childNodes.length == 1 && element.innerText.length >= 1 && element.innerText.length <= 6)
  81. {
  82. return element;
  83. }
  84. }
  85. return null;
  86. }
  87. /*
  88. * 结构 1:
  89. * <i>
  90. * <span>
  91. * <span>区</span>
  92. * <span>服</span>
  93. * </span>
  94. * 结构 2:
  95. * <i>
  96. * <span>区 服</span>
  97. * 结构 3:
  98. * <i>
  99. * <span>区</span>
  100. * <span>服</span>
  101. */
  102. function FindServer(startingIcon)
  103. {
  104. let element = startingIcon.nextElementSibling;
  105. if (element && element.querySelector('span'))
  106. {
  107. element = element.querySelector('span:first-child');
  108. }
  109. let text = element.textContent.trim();
  110. if (text.includes(' '))
  111. {
  112. return text.split(' ')[1];
  113. }
  114. else
  115. {
  116. return element.nextElementSibling ? element.nextElementSibling.textContent : '';
  117. }
  118. }
  119.  
  120. async function getData(name,server,zoneId){
  121.  
  122. var difficulty = zones.find((zone) => {
  123. return zone.id == zoneId
  124. }).difficulty
  125.  
  126. var graphqlQuery = `
  127. {
  128. characterData {
  129. character(name: "${name}", serverRegion: "cn", serverSlug: "${server}") {
  130. zoneRankings(zoneID: ${zoneId}, difficulty: ${difficulty})
  131. }
  132. }
  133. }
  134. `
  135.  
  136. try {
  137. let response = await fetch(url, {
  138. method: 'POST',
  139. headers: {
  140. 'Content-Type': 'application/json charset=UTF-8',
  141. 'Authorization': 'Bearer '+ apiToken
  142. },
  143. body: JSON.stringify({ query: graphqlQuery }),
  144. })
  145. return await response.json();
  146. } catch (error) {
  147. console.log('Request Failed', error);
  148. }
  149. }
  150.  
  151. function getRankColor(rank){
  152. if(rank == '-') return "#666"
  153. if (rank === 100) return "#e5cc80";
  154. if (rank >= 99) return "#e268a8";
  155. if (rank >= 95) return "#ff8000";
  156. if (rank >= 75) return "#a335ee";
  157. if (rank >= 50) return "#0070ff";
  158. if (rank >= 25) return "#1eff00";
  159. else return "#666";
  160. }
  161.  
  162. function getBossUrl(bossId){
  163. return `https://assets.rpglogs.cn/img/ff/bosses/${bossId}-icon.jpg?v=2`
  164. }
  165.  
  166. function getJobIconUrl(jobName){
  167. return `https://assets.rpglogs.cn/img/ff/icons/${jobName}.png`
  168. }
  169.  
  170. function buildBossLogs(boosId,rank,jobName){
  171. var bossDiv = document.createElement('td');
  172. var bossNode = document.createElement('img');
  173. var rankNode = document.createElement('a');
  174. bossNode.setAttribute('src',getBossUrl(boosId));
  175. bossNode.style.width = '24px';
  176. bossNode.style.height = '24px';
  177. rankNode.innerText = rank?Math.round(rank):'-';;
  178. rankNode.style.color = getRankColor(rankNode.innerHTML);
  179. rankNode.style.textAlign = 'center'
  180. rankNode.style.marginLeft = '10px'
  181. bossDiv.style.display = 'flex'
  182. bossDiv.style.justifyContent = 'center'
  183. bossDiv.style.alignItems = 'center'
  184. bossDiv.style.textAlign = 'center'
  185. bossDiv.style.border = '2px solid #333'
  186.  
  187. bossDiv.append(bossNode);
  188. bossDiv.append(rankNode);
  189.  
  190. if(jobName){
  191. var jobNode = document.createElement('img');
  192. jobNode.setAttribute('src',getJobIconUrl(jobName));
  193. jobNode.style.width = '24px';
  194. jobNode.style.height = '24px';
  195. jobNode.style.marginLeft = '4px';
  196. jobNode.style.marginRight = '5px';
  197. jobNode.style.border = '1px solid #555555';
  198. bossDiv.append(jobNode);
  199. }
  200.  
  201. return bossDiv
  202. }
  203.  
  204. async function updateData(usernameElement,charaKey,server,zoneId){
  205. var data = await getData(usernameElement.innerText,server,zoneId)
  206. console.log(data)
  207. var rankings = data?.data?.characterData?.character?.zoneRankings.rankings
  208. var logsNode = document.getElementById('chara'+charaKey)
  209. logsNode.style.display = 'flex'
  210. console.log(rankings)
  211. logsNode.innerHTML = ''
  212. rankings?.forEach((ranking) => {
  213. var node = buildBossLogs(ranking.encounter.id,ranking.rankPercent,ranking.spec)
  214. logsNode.append(node);
  215. })
  216. }
  217. function InsertLogsIcon(usernameElement, server,key)
  218. {
  219. var viewerDiv = document.createElement('div')
  220. var selectNode = document.createElement('select')
  221. selectNode.setAttribute('id','chara-select'+key)
  222. selectNode.style.backgroundColor = '#000'
  223. selectNode.style.color = 'white'
  224. selectNode.style.border = 'none'
  225. selectNode.style.outline = 'none'
  226. selectNode.addEventListener('change', () => {
  227. console.log(selectNode.options[selectNode.selectedOptions])
  228. updateData(usernameElement,key,server,selectNode.options[selectNode.selectedIndex].value)
  229. })
  230. zones.forEach((zone) => {
  231. var zoneOption = document.createElement('option')
  232. zoneOption.text = zone.name
  233. zoneOption.value = zone.id
  234. selectNode.add(zoneOption)
  235. if(zone.id === defaultZone){
  236. selectNode.selectedIndex = defaultZone
  237. }
  238. })
  239. var logsNode = document.createElement('tr')
  240. logsNode.setAttribute('id','chara'+key)
  241. logsNode.style.borderCollapse = 'collapse'
  242. logsNode.style.borderTop = '2px solid #333'
  243. logsNode.style.borderBottom = '2px solid #333'
  244. logsNode.style.backgroundColor = '#000'
  245. updateData(usernameElement,key,server,defaultZone)
  246. viewerDiv.style.display = 'flex'
  247. viewerDiv.append(selectNode)
  248. viewerDiv.append(logsNode)
  249.  
  250. var newNode = document.createElement('a');
  251. var username = usernameElement.innerText;
  252. newNode.id = 'ff-icon';
  253. newNode.href = `https://cn.fflogs.com/character/CN/${server}/${username}`;
  254. newNode.innerHTML = `<img src="https://assets.rpglogs.cn/img/ff/favicon.png" height="30px" width="30px">`;
  255. newNode.style.backgroundColor = '#000'
  256. newNode.border = '2px solid #333'
  257. viewerDiv.append(newNode);
  258.  
  259. console.log(usernameElement.parentElement.parentElement.parentElement.children.length)
  260. if(usernameElement.parentElement.parentElement.parentElement.children.length == 2){
  261. usernameElement.parentElement.parentElement.insertAdjacentElement('afterend', viewerDiv);
  262. }
  263. else{
  264. usernameElement.parentElement.insertAdjacentElement('afterend', viewerDiv);
  265. }
  266.  
  267. console.log(`FFLogs 图标已添加到:${username}@${server}`);
  268. }
  269. setInterval(function() {
  270. var elements = document.querySelectorAll('.icon-location.dwcolor'); // 搜索服务器前的图标作为定位特征
  271. elements.forEach(function(iconElement,key) {
  272. if (!iconElement.hasAttribute('data-logs-processed'))
  273. {
  274. ProcessLocationIcon(iconElement,key);
  275. iconElement.setAttribute('data-logs-processed', 'true'); // 标记已处理
  276. }
  277. });
  278. }, delay);
  279. })();

QingJ © 2025

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