// ==UserScript==
// @name FF14 鱼糕增强插件
// @namespace ffxiv-yugao-buffer-plugin
// @version 1.1.3
// @author 毛呆
// @description 为鱼糕网页版增加自动标记已完成的功能。
// @license MIT
// @match https://fish.ffmomola.com/*
// ==/UserScript==
(function () {
'use strict';
(function() {
let href = decodeURIComponent(location.href);
let wsUrl = /[\?&]OVERLAY_WS=([^&]+)/.exec(href);
let ws = null;
let queue = [];
let rseqCounter = 0;
let responsePromises = {};
let subscribers = {};
let sendMessage = null;
let eventsStarted = false;
if (!wsUrl) {
wsUrl = [
"?OVERLAY_WS=ws://127.0.0.1:10501/ws",
"ws://127.0.0.1:10501/ws"
];
}
if (wsUrl) {
let connectWs2 = function() {
ws = new WebSocket(wsUrl[1]);
ws.addEventListener("error", (e) => {
console.error(e);
});
ws.addEventListener("open", () => {
console.log("Connected!");
processEvent({ type: "open" });
let q = queue;
queue = null;
for (let msg of q)
sendMessage(msg);
});
ws.addEventListener("message", (msg) => {
try {
msg = JSON.parse(msg.data);
} catch (e) {
console.error("Invalid message received: ", msg);
return;
}
if (msg.rseq !== void 0 && responsePromises[msg.rseq]) {
responsePromises[msg.rseq](msg);
delete responsePromises[msg.rseq];
} else {
processEvent(msg);
}
});
ws.addEventListener("close", () => {
processEvent({ type: "close" });
queue = [];
console.log("Trying to reconnect...");
setTimeout(() => {
connectWs2();
}, 300);
});
};
sendMessage = (obj) => {
if (queue)
queue.push(obj);
else
ws.send(JSON.stringify(obj));
};
connectWs2();
} else {
let waitForApi2 = function() {
if (!window.OverlayPluginApi || !window.OverlayPluginApi.ready) {
setTimeout(waitForApi2, 300);
return;
}
let q = queue;
queue = null;
window.__OverlayCallback = processEvent;
for (let [msg, resolve] of q)
sendMessage(msg, resolve);
};
sendMessage = (obj, cb) => {
if (queue)
queue.push([obj, cb]);
else
OverlayPluginApi.callHandler(JSON.stringify(obj), cb);
};
waitForApi2();
}
function processEvent(msg) {
if (subscribers[msg.type]) {
for (let sub of subscribers[msg.type])
sub(msg);
}
}
window.dispatchOverlayEvent = processEvent;
window.addOverlayListener = (event, cb) => {
if (eventsStarted && subscribers[event]) {
console.warn(`A new listener for ${event} has been registered after event transmission has already begun.
Some events might have been missed and no cached values will be transmitted.
Please register your listeners before calling startOverlayEvents().`);
}
if (!subscribers[event]) {
subscribers[event] = [];
}
subscribers[event].push(cb);
};
window.removeOverlayListener = (event, cb) => {
if (subscribers[event]) {
let list = subscribers[event];
let pos = list.indexOf(cb);
if (pos > -1)
list.splice(pos, 1);
}
};
window.callOverlayHandler = (msg) => {
let p;
if (ws) {
msg.rseq = rseqCounter++;
p = new Promise((resolve) => {
responsePromises[msg.rseq] = resolve;
});
sendMessage(msg);
} else {
p = new Promise((resolve) => {
sendMessage(msg, (data) => {
resolve(data == null ? null : JSON.parse(data));
});
});
}
return p;
};
window.startOverlayEvents = () => {
eventsStarted = false;
sendMessage({
call: "subscribe",
events: Object.keys(subscribers)
});
};
})();
let tagRef;
window.addOverlayListener("open", () => {
if (tagRef)
return;
const contentRef = document.querySelector(".v-toolbar__content");
const titleRef = document.querySelector(".v-toolbar__title");
tagRef = document.createElement("div");
contentRef.insertBefore(tagRef, titleRef.nextElementSibling);
tagRef.innerHTML = `
<span class="v-badge v-badge--inline theme--dark">
<span class="v-badge__wrapper">
<span
role="status"
class="v-badge__badge primary"
>ACT 已连接</span
>
</span>
</span>
`;
});
window.addOverlayListener("close", () => {
if (!tagRef)
return;
const contentRef = document.querySelector(".v-toolbar__content");
contentRef.removeChild(tagRef);
tagRef = void 0;
});
function insertAchieveCheckbox({ store: store2, fishMap: fishMap2 }) {
const achieveMap = {
愿者上钩: [],
净界太公: [],
太公仙路: [],
晓月太公: []
};
store2.state.bigFish.forEach((id) => {
const fish2 = fishMap2.get(id);
if (!fish2) {
console.debug("[Log 未找到鱼王]", id);
return;
}
if (fish2.patch < 5) {
achieveMap["愿者上钩"].push(id);
achieveMap["太公仙路"].push(id);
}
if (fish2.patch >= 5 && fish2.patch < 6) {
achieveMap["净界太公"].push(id);
achieveMap["太公仙路"].push(id);
}
if (fish2.patch >= 6 && fish2.patch < 7) {
achieveMap["晓月太公"].push(id);
}
});
Array.from(document.querySelectorAll(".v-subheader")).forEach((el) => {
const title = el.innerText.trim();
if (!(title in achieveMap))
return;
const button = document.createElement("button");
el.appendChild(button);
button.innerHTML = "标记为已获得";
button.title = "将该成就下所有鱼王标记为已获得";
button.style.border = "1px solid #979797";
button.style.borderRadius = "4px";
button.style.background = "rgba(128, 128, 128, 0.5)";
button.style.marginLeft = "20px";
button.style.padding = "0 4px";
button.addEventListener("click", () => {
store2.commit("batchSetFishCompleted", {
fishIds: achieveMap[title],
completed: true
});
});
});
}
function getInstance() {
const app = document.getElementById("app");
return app.__vue__;
}
function getStore() {
const instance = getInstance();
return instance.$store;
}
window.addOverlayListener("LogLine", onLogLine);
window.startOverlayEvents();
const store = getStore();
const fish = store.state.fish;
const fishNameToIdMap = /* @__PURE__ */ new Map();
const fishMap = /* @__PURE__ */ new Map();
Object.keys(fish).forEach((id) => {
const name = store.getters.getItemName(id);
if (name) {
const _id = Number(id.length > 6 ? id.slice(-6) : id);
fishNameToIdMap.set(name, _id);
fishMap.set(_id, fish[id]);
}
});
const fishReg = /(.+?)??((.+?)星寸)。$/;
function onLogLine(data) {
const { type, line, rawLine } = data;
if (type !== "LogLine")
return;
if (!Array.isArray(line))
return;
const [logType, timestamp, code, name, message] = line;
if (logType !== "00")
return;
if (name)
return;
if (code !== "0843")
return;
const match = message.match(fishReg);
if (!match) {
return;
}
const [, fishName] = match;
console.log(`成功钓上了 ${fishName}`);
const id = fishNameToIdMap.get(fishName);
if (!id) {
return;
}
store.commit("setFishCompleted", {
fishId: id,
completed: true
});
}
let achieveTimer;
getInstance().$watch(
"$route.path",
function(path) {
if (path === "/wiki") {
let whileFind2 = function() {
if (achieveTimer)
clearTimeout(achieveTimer);
const el = Array.from(document.querySelectorAll(".v-subheader")).find(
(el2) => el2.innerText.trim() === "专研钓鱼笔记"
);
if (el) {
insertAchieveCheckbox({ store, fishMap });
} else {
achieveTimer = setTimeout(whileFind2, 1e3);
}
};
whileFind2();
} else {
if (achieveTimer)
clearTimeout(achieveTimer);
}
},
{
immediate: true
}
);
})();