您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
鼠标悬停至水晶icon上即可查询,查询拥有24小时缓存。
当前为
// ==UserScript== // @name LOGS警察 // @namespace http://tampermonkey.net/ // @version 1.1.1 // @description 鼠标悬停至水晶icon上即可查询,查询拥有24小时缓存。 // @author Souma // @match *://ff14risingstones.web.sdo.com/* // @icon <$ICON$> // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_setValue // @grant GM_getValue // @license MIT // ==/UserScript== (function () { "use strict"; const jobs = { "Adventurer": "冒险者", "Gladiator": "剑术师", "Pugilist": "格斗家", "Marauder": "斧术师", "Lancer": "枪术师", "Archer": "弓箭手", "Conjurer": "幻术师", "Thaumaturge": "咒术师", "Carpenter": "刻木匠", "Blacksmith": "锻铁匠", "Armorer": "铸甲匠", "Goldsmith": "雕金匠", "Leatherworker": "制革匠", "Weaver": "裁衣匠", "Alchemist": "炼金术士", "Culinarian": "烹调师", "Miner": "采矿工", "Botanist": "园艺工", "Fisher": "捕鱼人", "Paladin": "骑士", "Monk": "武僧", "Warrior": "战士", "Dragoon": "龙骑士", "Bard": "吟游诗人", "White Mage": "白魔法师", "Black Mage": "黑魔法师", "Arcanist": "秘术师", "Summoner": "召唤师", "Scholar": "学者", "Rogue": "双剑师", "Ninja": "忍者", "Machinist": "机工士", "Dark Knight": "暗黑骑士", "Astrologian": "占星术士", "Samurai": "武士", "Red Mage": "赤魔法师", "Blue Mage": "青魔法师", "Gunbreaker": "绝枪战士", "Dancer": "舞者", "Reaper": "钐镰客", "Sage": "贤者", }; const getColor = (per) => { if (per === 100) return "#e5cc80"; //金色 if (per >= 99) return "#e268a8"; //粉色 if (per >= 95) return "#ff8000"; //橙色 if (per >= 75) return "#a335ee"; //紫色 if (per >= 50) return "#0070ff"; //蓝色 if (per >= 25) return "#1eff00"; //绿色 else return "#666"; //灰色 }; const STORAGE_KEY_LOGS = "szj-logs"; const STORAGE_KEY_API_KEY = "zj-api-key"; let api_key = GM_getValue(STORAGE_KEY_API_KEY, ""); const getApiKey = () => { window.open("https://cn.fflogs.com/profile#api-title", "_blank"); }; const setApiKey = () => { const newKey = prompt("请输入 V1 Client Key: ", api_key); if (newKey !== null) { GM_setValue(STORAGE_KEY_API_KEY, newKey); api_key = GM_getValue(STORAGE_KEY_API_KEY, ""); } }; GM_registerMenuCommand("获得API_KEY", getApiKey); GM_registerMenuCommand("设置API_KEY", setApiKey); const cache = localStorage.getItem(STORAGE_KEY_LOGS) ? JSON.parse(localStorage.getItem(STORAGE_KEY_LOGS)) : {}; for (const key in cache) { const item = cache[key]; if (item && item.time && item.time < Date.now() - 1000 * 60 * 60 * 24) { delete cache[key]; } } const targetNode = document.body; const config = { attributes: true, childList: true, subtree: true, }; const observer = new MutationObserver(callback); observer.observe(targetNode, config); function callback(mutationsList, _observer) { for (const mutation of mutationsList) { if (mutation.type === "childList") { mutation.addedNodes.forEach(function (addedNode) { if (addedNode.nodeType === 1 && addedNode.tagName === "DIV" && !addedNode.matches(".mt10")) { const node = addedNode.querySelector(".mt10>.el-row>.el-col>.alcenter") || addedNode.querySelector(".detail")?.querySelector(".mt3.flex.alcenter") || addedNode.querySelector(".flex>.info-main"); if (!node) return; const title = addedNode.querySelector(".mt3.flex.alcenter"); const name = node.querySelector(".name>span")?.innerText || node.querySelector(".ft24.ftw")?.innerText || title?.querySelector(".cursor")?.innerText; const group = node.querySelector(".line>.group")?.innerText || node.querySelector(".graycolor")?.children?.[1]?.innerText || title?.querySelector(".graycolor")?.children?.[1]?.innerText; if (!name || !group) return; let result = false; const div = document.createElement("div"); const img = document.createElement("img"); const loading = document.createElement("span"); const info = document.createElement("span"); const job = document.createElement("span"); const color = document.createElement("span"); div.appendChild(img); node.appendChild(div); div.appendChild(loading); img.src = "https://assets.rpglogs.cn/img/ff/favicon.png"; img.style.height = "20px"; div.style.cursor = "pointer"; div.onclick = () => window.open(`https://cn.fflogs.com/character/CN/${group}/${name}`, "_blank"); img.addEventListener("mouseenter", function () { if (api_key === "") { setApiKey(); return; } if (result) return; result = true; loading.innerText = "查询中..."; const c = cache[`${name}/${group}`]; if (c && Date.now() - c.time < 1000 * 60 * 60 * 24) { loading.innerText = ""; info.innerHTML = c.HTML; div.appendChild(info); } else { delete cache[`${name}/${group}`]; fetch( `https://www.fflogs.com/v1/rankings/character/${name}/${encodeURI( group )}/CN?metric=dps&timeframe=historical&api_key=${api_key}` ) .then((v) => { return v.json(); }) .then((v) => { if (v.length === 0) { loading.innerText = "无数据"; } else { const jobName = Object.entries( v.reduce( (pre, cur) => (pre[cur.spec] === undefined ? (pre[cur.spec] = 1) : pre[cur.spec]++) && pre, {} ) ).sort((a, b) => b[1] - a[1])[0][0]; const enc = v.filter((v) => v.spec === jobName); const needed = {}; enc.forEach((v) => { if (needed[v.encounterID] === undefined || v.percentile > needed[v.encounterID].percentile) needed[v.encounterID] = v; }); const enc_needed = Object.entries(needed).map((v) => v[1]); const per = enc_needed.map((v) => v.percentile).reduce((pre, cur) => pre + cur, 0) / enc_needed.length; job.innerText = jobs[jobName] ?? jobName; color.innerText = `${parseInt(per, 10)}%`; color.style.paddingLeft = "0.25em"; color.style.color = getColor(per); info.appendChild(job); info.appendChild(color); loading.innerText = ""; cache[`${name}/${group}`] = { HTML: info.innerHTML, time: Date.now() }; localStorage.setItem(STORAGE_KEY_LOGS, JSON.stringify(cache)); } div.appendChild(info); }) .catch(() => { loading.innerText = "查询失败"; }); } }); } }); } } } // observer.disconnect(); })();