// ==UserScript== // @name MWI QQShow Offline // @namespace http://tampermonkey.net/ // @version 1.0 // @description QQ Show offline. // @author guch8017 // @match https://www.milkywayidle.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=milkywayidle.com // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/536405/MWI%20QQShow%20Offline.user.js // @updateURL https://update.greasyfork.icu/scripts/536405/MWI%20QQShow%20Offline.meta.js // ==/UserScript== /* * QQ秀插件-离线版 * 由Ratatata的Magic Way Idle的代码精简而来,仅保留了QQ秀功能。 * 该插件不包含联网相关代码,仅对本地游戏账户有效,如需联网功能请考虑使用在线版。 */ (function() { 'use strict'; const hasMagicWayIdle = false; const QQSHOW_CLS = { qqshow_setting: "qqshow_md3", qqshow_url_input: "qqshow_url_input_md3", qqshow_key: "qqshow_offline_url", }; const buttonThor = 1000; let globalVariable = { qqShow:{ // 保存玩家QQ秀链接 replacementTargets : {}, // 图标替换观察者 observer : null, characterName : null } } let lastTimeClick = 0; function hookWebSocket() { const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data"); const oriGet = dataProperty.get; dataProperty.get = hookedGet; Object.defineProperty(MessageEvent.prototype, "data", dataProperty); function hookedGet() { const socket = this.currentTarget; if (!(socket instanceof WebSocket)) { return oriGet.call(this); } if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1 && socket.url.indexOf("api-test.milkywayidle.com/ws") <= -1) { return oriGet.call(this); } const message = oriGet.call(this); Object.defineProperty(this, "data", { value: message }); return handleMessage(message); } } function handleMessage(message,debug=false) { let obj = JSON.parse(message); if (obj && obj.type === "init_character_data") { // 读取角色名称用于上传QQ秀 globalVariable.qqShow.characterName=obj.character.name; } return message; } // Helper function 显示提醒 // showToast() // Source: **助手 // Author: Trutn_Light Stella const toastQueues = Array.from({ length: 5 }, () => []); const maxVisibleToasts = Math.floor(window.innerHeight / 2 / 50); let isToastVisible = Array(5).fill(false); function displayNextToast(queueIndex) { if (isToastVisible[queueIndex] || toastQueues[queueIndex].length === 0) return; const { message, duration } = toastQueues[queueIndex].shift(); isToastVisible[queueIndex] = true; const toast = createToastElement(message, queueIndex); toast.style.opacity = '0'; requestAnimationFrame(() => { toast.style.opacity = '1'; }); setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => { document.body.removeChild(toast); isToastVisible[queueIndex] = false; displayNextToast(queueIndex); }, 500); }, duration); } function showToast(message, duration = 2000) { const queueIndex = toastQueues.findIndex(queue => queue.length < maxVisibleToasts); if (queueIndex === -1) return; toastQueues[queueIndex].push({ message, duration }); displayNextToast(queueIndex); } function createToastElement(message, queueIndex) { const toast = document.createElement('div'); toast.className = 'toast'; toast.style.position = 'fixed'; toast.style.bottom = `${20 + queueIndex * 60}px`; toast.style.left = '50%'; toast.style.transform = 'translateX(-50%)'; toast.style.backgroundColor = '#333'; toast.style.color = '#fff'; toast.style.padding = '10px 20px'; toast.style.borderRadius = '5px'; toast.style.zIndex = '1000'; toast.style.textAlign = 'center'; toast.style.transition = 'opacity 0.5s'; toast.textContent = message; document.body.appendChild(toast); return toast; } function addQQshowButton() { const targetNode = document.querySelector("div.SettingsPanel_infoGrid__2nh1u"); const isqqshowFlagExist = document.querySelector(`div.${QQSHOW_CLS.qqshow_setting}`); if(targetNode&&!isqqshowFlagExist){ const nameColor=targetNode.querySelectorAll("div.SettingsPanel_value__2nsKD")[2]; let qqshowtitlediv = document.createElement("div"); let qqshowdiv = document.createElement("div"); let qqshowdivflag = document.createElement("div"); qqshowtitlediv.setAttribute("class", "SettingsPanel_label__24LRD"); qqshowtitlediv.innerHTML="更新QQ秀【离线版】"; qqshowdiv.setAttribute("class", "SettingsPanel_value__2nsKD"); qqshowdiv.style=nameColor.style; qqshowdivflag.setAttribute("class", QQSHOW_CLS.qqshow_setting); let qqshowURLInput = document.createElement("input"); qqshowURLInput.type = "text"; qqshowURLInput.setAttribute("class", QQSHOW_CLS.qqshow_url_input); qqshowURLInput.placeholder = "图床url/提交空白视为删除"; let qqshowSubmitButton = document.createElement("button"); qqshowSubmitButton.setAttribute("class", "Button_button__1Fe9z"); qqshowSubmitButton.textContent = "提交"; qqshowSubmitButton.addEventListener("click", qqshowSubmit); qqshowdiv.appendChild(qqshowdivflag); qqshowdiv.appendChild(qqshowURLInput); qqshowdiv.appendChild(qqshowSubmitButton); let readmetitlediv = document.createElement("div"); let readme = document.createElement("div"); readmetitlediv.setAttribute("class", "SettingsPanel_label__24LRD"); readme.setAttribute("class", "SettingsPanel_value__2nsKD"); readme.innerHTML="先去tupian.li等图床上传图片,再提交url。
直接提交空白将删除QQ秀。刷新后生效。
若依然无效请点击强制刷新缓存后,再次刷新页面。" nameColor.parentNode.insertBefore(readme, nameColor.nextSibling); nameColor.parentNode.insertBefore(readmetitlediv, nameColor.nextSibling); nameColor.parentNode.insertBefore(qqshowdiv, nameColor.nextSibling); nameColor.parentNode.insertBefore(qqshowtitlediv, nameColor.nextSibling); } } function qqshowSubmit(){ const now = Date.now(); if (now - lastTimeClick < buttonThor) return; lastTimeClick = now; let qqshowURLInput=document.querySelector(`input.${QQSHOW_CLS.qqshow_url_input}`); let url=qqshowURLInput.value function isValidURL(str) { try { new URL(str); return true; } catch (err) { return false; } } if(url==''){ showToast("已删除,刷新生效"); updateqqshow(url); }else if(isValidURL(url)){ showToast("已提交,刷新生效"); updateqqshow(url); }else{ showToast("url不合法"); } } //更新QQ秀 function updateqqshow(face_url){ if (document.URL.includes("test.milkywayidle.com"))return; if (globalVariable.qqShow.characterName == "" || typeof globalVariable.qqShow.characterName === "undefined") { showToast("非法更新,请刷新页面"); return; } let qqshow_data = localStorage.getItem(QQSHOW_CLS.qqshow_key); if (qqshow_data == null) { qqshow_data = {}; } else { qqshow_data = JSON.parse(qqshow_data); } if (face_url == null || face_url == "") { delete qqshow_data[globalVariable.qqShow.characterName]; } else { qqshow_data[globalVariable.qqShow.characterName] = face_url; } localStorage.setItem(QQSHOW_CLS.qqshow_key, JSON.stringify(qqshow_data)); } // Source: MWI玩家图标替换 // Author: Ak4r1 ChatGpt Stella bot7420 function replaceIconsIn(node) { const iconElements = node.querySelectorAll(`div.FullAvatar_fullAvatar__3RB2h`); for (const elem of iconElements) { if (elem.closest("div.CowbellStorePanel_avatarsTab__1nnOY")) { continue; // 商店页面 } const playerId = findPlayerIdByAvatarElem(elem); if (!playerId) { //console.error("ICONS: replaceIconsIn can't find playerId"); //设置页面下面两个小人会引发异常,不要大惊小怪 //console.log(elem); continue; // 找不到 playerId } if (!globalVariable.qqShow.replacementTargets.hasOwnProperty(playerId)) { continue; // 没有配置图片地址 } const newImgElement = document.createElement("img"); newImgElement.src = globalVariable.qqShow.replacementTargets[playerId]; newImgElement.style.width = "100%"; newImgElement.style.height = "100%"; elem.innerHTML = ""; elem.appendChild(newImgElement); } } function findPlayerIdByAvatarElem(avatarElem) { // Profile 窗口页 const profilePageDiv = avatarElem.closest("div.SharableProfile_modal__2OmCQ"); if (profilePageDiv) { return profilePageDiv.querySelector(".CharacterName_name__1amXp")?.textContent.trim(); } // 网页右上角 const headerDiv = avatarElem.closest("div.Header_header__1DxsV"); if (headerDiv) { return headerDiv.querySelector(".CharacterName_name__1amXp")?.textContent.trim(); } // 战斗页面 const combatDiv = avatarElem.closest("div.CombatUnit_combatUnit__1m3XT"); if (combatDiv) { return combatDiv.querySelector(".CombatUnit_name__1SlO1")?.textContent.trim(); } // 组队页面 const partyDiv = avatarElem.closest("div.Party_partySlot__1xuiq"); if (partyDiv) { return partyDiv.querySelector(".CharacterName_name__1amXp")?.textContent.trim(); } return null; } //初始化观察者,分配替换目标 function initQQShowObserver(){ globalVariable.qqShow.observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if ( node.tagName === "DIV" && !node.classList.contains("ProgressBar_innerBar__3Z_sf") && !node.classList.contains("CountdownOverlay_countdownOverlay__2QRmL") && !node.classList.contains("ChatMessage_chatMessage__2wev4") && !node.classList.contains("Header_loot__18Cbe") && !node.classList.contains("script_itemLevel") && !node.classList.contains("script_key") && !node.classList.contains("dps-info") && !node.classList.contains("MuiTooltip-popper") ) { replaceIconsIn(node); } }); }); }); } function gameMain(){ // 拦截WebSocket hookWebSocket(); // 优先从缓存加载QQ秀 if(QQSHOW_CLS.qqshow_key in localStorage){ globalVariable.qqShow.replacementTargets=JSON.parse(localStorage.getItem(QQSHOW_CLS.qqshow_key)); } // 初始化观察者,分配替换目标 initQQShowObserver(); // 启动观察者,替换QQ秀 globalVariable.qqShow.observer.observe(document, { attributes: false, childList: true, subtree: true }); // 设置页面仍然需要添加新的图标 初始化设置页面观察者 let globalObserver=new MutationObserver(function (mutationsList, observer) { addQQshowButton(); }); globalObserver.observe(document,{ childList: true, subtree: true }); } gameMain() })();