// ==UserScript==
// @name MWISubscribe
// @namespace http://tampermonkey.net/
// @version 0.6
// @description Subscribe Market Item
// @author Lhiok
// @license MIT
// @match https://www.milkywayidle.com/*
// @match https://test.milkywayidle.com/*
// @icon https://www.milkywayidle.com/favicon.svg
// @supportURL https://github.com/Lhiok/MWIScript/
// @grant none
// ==/UserScript==
(function() {
"use strict";
let mwi_common = null;
let mwi_subscribe_items = [];
const storage_key = "mwi_subscribe_items";
const subscribeItemsStorage = localStorage.getItem(storage_key);
if (subscribeItemsStorage) {
mwi_subscribe_items = JSON.parse(subscribeItemsStorage);
}
// https://greasyfork.org/zh-CN/scripts/536205-%E9%93%B6%E6%B2%B3%E5%A5%B6%E7%89%9B-%E5%BA%B7%E5%BA%B7%E8%BF%90%E6%B0%94
const Tooltip = new class {
root = null;
tooltip = null;
constructor() { this.init(); }
applyOptions(elem, options) {
if (typeof options === 'object') {
Object.entries(options ?? {}).forEach(([key, value]) => {
if (key === 'style' && typeof value === 'object') {
Object.entries(value ?? {}).forEach(([k, v]) => { elem.style[k] = v; });
} else elem[key] = value;
});
} else elem.className = options;
}
elem(tagName, options = null, child = null) {
const elem = document.createElement(tagName);
this.applyOptions(elem, options);
if (typeof child === 'object') {
if (Array.isArray(child)) child.forEach(child => { elem.appendChild(child); });
else if (child) elem.appendChild(child);
} else if (typeof child === 'string') elem.innerHTML = child;
return elem;
}
div(options = null, childList = null) {
return this.elem('div', options, childList);
}
init() {
const rootClass = 'link-tooltip MuiPopper-root MuiTooltip-popper css-112l0a2';
const tooltipClass = 'MuiTooltip-tooltip MuiTooltip-tooltipPlacementBottom css-1spb1s5';
this.tooltip = this.div(tooltipClass);
this.root = this.div({ className: rootClass, style: { zIndex: 100000, position: 'absolute' } }, this.tooltip);
document.body.appendChild(this.root);
this.hide();
}
attach(target, content, align = 'left') {
const contentGen = typeof content === 'function' ? content : (() => content);
target.addEventListener('mouseover', (e) => {
this.show(contentGen().outerHTML, target, align);
});
target.addEventListener('mouseout', () => {
this.hide();
});
}
show(innerHTML, target = null, align = 'left') {
const gap = 2;
this.root.style.display = 'block';
this.root.style.left = 0;
this.root.style.top = 0;
this.tooltip.innerHTML = innerHTML;
if (target) {
const targetRect = target.getBoundingClientRect();
const tooltipRootRect = this.root.getBoundingClientRect();
const tooltipRect = this.tooltip.getBoundingClientRect();
let left = targetRect.left;
if (align === 'center') left -= (tooltipRect.width - targetRect.width) / 2;
let top = targetRect.bottom + gap;
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight + window.scrollY;
if (left + tooltipRect.width > windowWidth) left = windowWidth - tooltipRect.width;
if (left < 0) left = 0;
if (top + tooltipRect.height > windowHeight) top = targetRect.top - tooltipRect.height - gap;
this.root.style.left = `${left - (tooltipRootRect.width - tooltipRect.width) / 2}px`;
this.root.style.top = `${top - (tooltipRootRect.height - tooltipRect.height) / 2}px`;
}
}
hide() { this.root.style.display = 'none'; }
formatNumber(value) {
return value.toString().replace(/\d+/, function (n) {
return n.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')
})
}
formatNumberWithUnit(value) {
if (value > 10_000_000_000_000) return `${(value / 1_000_000_000_000).toFixed(1)}T`;
if (value > 10_000_000_000) return `${(value / 1_000_000_000).toFixed(1)}B`;
if (value > 10_000_000) return `${(value / 1_000_000).toFixed(1)}M`;
if (value > 10_000) return `${(value / 1_000).toFixed(1)}K`;
return value;
}
item(hrid, level, count) {
const ask = mwi_common.getItemPriceByHrid(hrid, level, 'ask');
const bid = mwi_common.getItemPriceByHrid(hrid, level, 'bid');
return this.div('ItemTooltipText_itemTooltipText__zFq3A', [
this.div('ItemTooltipText_name__2JAHA', mwi_common.getItemNameByHrid(hrid, mwi_common.isZh) + (level? `+${level}`: '')),
this.div(null, `拥有数量: ${this.formatNumber(count)}`),
this.div({ style: { color: '#804600' } },
`日均价: ${this.formatNumberWithUnit(ask)} / ${this.formatNumberWithUnit(bid)} (${this.formatNumberWithUnit(ask * count)} / ${this.formatNumberWithUnit(bid * count)})`
),
]);
}
}
function updateSubscribedList() {
const displayContainer = document.querySelector("div#displayContainer141");
if (!displayContainer) {
return;
}
// 移除收藏物品
displayContainer.innerHTML = "";
// 创建收藏物品
mwi_subscribe_items.forEach(itemHridLevel => {
const itemInfo = itemHridLevel.split("::");
const itemHrid = itemInfo[0];
const itemLevel = itemInfo[1]? Number(itemInfo[1]): 0;
if (!itemHrid || itemHrid === "") {
return;
}
const itemCount = mwi_common.getItemNumByHrid(itemHrid, itemLevel);
const item = document.createElement("div");
item.setAttribute("class", "Item_itemContainer__x7kH1");
item.innerHTML = `<div>
<div class="Item_item__2De2O Item_clickable__3viV6" style="position: relative;">
<div class="Item_iconContainer__5z7j4">
<svg role="img" aria-label="${mwi_common.getItemNameByHrid(itemHrid, mwi_common.isZh)}" class="Icon_icon__2LtL_" width="100%" height="100%">
<use href="/static/media/items_sprite.6d12eb9d.svg#${itemHrid.substr(7)}"></use>
</svg>
</div>
<div class="Item_count__1HVvv" style="position: absolute; bottom: 2px; right: 2px;">
${Tooltip.formatNumberWithUnit(itemCount)}
</div>
<div class="Item_enhancementLevel__19g-e" style="position: absolute; top: 2px; left: 2px;">
${itemLevel > 0? `+${itemLevel}`: ""}
</div>
</div>
</div>`;
displayContainer.appendChild(item);
// 物品点击事件
item.addEventListener("click", () => mwi_common.gotoMarket(itemHrid, itemLevel));
// 鼠标悬浮事件
Tooltip.attach(item, Tooltip.item(itemHrid, itemLevel, itemCount));
});
}
function createSubscribeButton(marketPanel) {
const displayContainer = marketPanel.querySelector(".MarketplacePanel_currentItem__3ercC");
if (!displayContainer) {
return;
}
// 创建收藏按钮
const subscribeButton = document.createElement("button");
subscribeButton.setAttribute("id", "SubscribeButton141");
subscribeButton.className = "subscribe-btn";
subscribeButton.style.position = "absolute";
subscribeButton.style.padding = "0";
subscribeButton.style.marginLeft = "6px";
subscribeButton.style.background = "none";
subscribeButton.style.border = "none";
subscribeButton.style.outline = "none";
subscribeButton.style.zIndex = "2"; /** make sure it's on top of the item level div created by MWITools */
// 创建图标
const svgNS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(svgNS, "svg");
svg.setAttribute("viewBox", "0 0 24 24");
svg.setAttribute("width", "24");
svg.setAttribute("height", "24");
// 未收藏
const heartUnsubscribed = document.createElementNS(svgNS, "path");
heartUnsubscribed.setAttribute("d", "M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z");
heartUnsubscribed.setAttribute("fill", "#aaaaaa");
heartUnsubscribed.setAttribute("stroke", "#333");
heartUnsubscribed.setAttribute("transition", "all 0.3s");
// 已收藏
const heartSubscribed = document.createElementNS(svgNS, "path");
heartSubscribed.setAttribute("d", "M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z");
heartSubscribed.setAttribute("fill", "#ff4d4f");
heartSubscribed.setAttribute("opacity", "0");
heartSubscribed.setAttribute("transition", "opacity 0.3s");
// 添加图标
svg.appendChild(heartUnsubscribed);
svg.appendChild(heartSubscribed);
subscribeButton.appendChild(svg);
displayContainer.prepend(subscribeButton);
// 物品等级
let itemHrid = "";
let itemLevel = 0;
let itemHridLevel = "";
let isSubscribed = false;
const updateSubscribedButton = function() {
if (isSubscribed) {
heartUnsubscribed.setAttribute("stroke", "#ff4d4f");
heartSubscribed.setAttribute("opacity", "1");
} else {
heartUnsubscribed.setAttribute("stroke", "#333");
heartSubscribed.setAttribute("opacity", "0");
}
};
const updateMarketItem = function() {
itemHrid = "";
const displayItem = displayContainer.querySelector(".Item_iconContainer__5z7j4");
if (displayItem && displayItem.firstChild) {
const itemName = displayItem.firstChild.getAttribute("aria-label");
if (itemName && itemName !== "") {
itemHrid = mwi_common.getItemHridByName(itemName);
}
}
itemLevel = 0;
const levelDiv = displayContainer.querySelector(".Item_enhancementLevel__19g-e");
if (levelDiv) {
itemLevel = Number(levelDiv.textContent.replace("+", ""));
if (isNaN(itemLevel)) itemLevel = 0;
}
itemHridLevel = itemLevel > 0? `${itemHrid}::${itemLevel}`: itemHrid;
isSubscribed = mwi_subscribe_items.includes(itemHridLevel);
updateSubscribedButton();
}
updateMarketItem();
// 绑定点击
subscribeButton.addEventListener("click", function() {
if (!itemHridLevel || itemHridLevel === "") {
return;
}
isSubscribed = !isSubscribed;
updateSubscribedButton();
if (isSubscribed) {
console.info("[MWISubscribe] add item " + itemHridLevel);
mwi_subscribe_items.push(itemHridLevel);
}
else {
console.info("[MWISubscribe] remove item " + itemHridLevel);
const idx = mwi_subscribe_items.indexOf(itemHridLevel);
(~idx) && mwi_subscribe_items.splice(idx, 1);
}
localStorage.setItem(storage_key, JSON.stringify(mwi_subscribe_items));
updateSubscribedList();
});
new MutationObserver(updateMarketItem).observe(displayContainer, { childList: true, subtree: true });
}
function createDisplayButton(marketPanel) {
const tabPanelContainer = marketPanel.querySelector(".TabsComponent_tabPanelsContainer__26mzo");
if (!tabPanelContainer) {
return;
}
const tabList = tabPanelContainer.querySelector("[role=tablist]");
const panelList = tabPanelContainer.querySelector(".TabsComponent_tabPanelsContainer__26mzo");
if (!tabList || !panelList) {
return;
}
// 创建收藏按钮
const displayButton = document.createElement("button");
displayButton.setAttribute("class", "MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary css-1q2h7u5");
displayButton.setAttribute("tabindex", -1);
displayButton.setAttribute("role", "tab");
displayButton.setAttribute("aria-selected", false);
displayButton.setAttribute("id", "displayButton141");
displayButton.textContent = "收藏";
tabList.appendChild(displayButton);
// 创建收藏面板
const displayPanel = document.createElement("div");
displayPanel.setAttribute("class", "TabPanel_tabPanel__tXMJF TabPanel_hidden__26UM3");
panelList.appendChild(displayPanel);
// 创建收藏容器
const displayContainer = document.createElement("div");
displayContainer.setAttribute("class", "MarketplacePanel_marketItems__D4k7e");
displayContainer.setAttribute("id", "displayContainer141");
displayPanel.appendChild(displayContainer);
updateSubscribedList();
// 设置按钮点击事件
tabList.childNodes.forEach((childBtn, btnIdx) => {
childBtn.addEventListener("click", function() {
// 按钮样式更改
tabList.childNodes.forEach(otherBtn => {
if (otherBtn === childBtn) {
otherBtn.setAttribute("class", "MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary Mui-selected css-1q2h7u5");
otherBtn.setAttribute("tabindex", 0);
otherBtn.setAttribute("aria-selected", true);
}
else {
otherBtn.setAttribute("class", "MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary css-1q2h7u5");
otherBtn.setAttribute("tabindex", -1);
otherBtn.setAttribute("aria-selected", false);
}
});
// 面板样式更改
panelList.childNodes.forEach((otherPanel, panelIdx) => {
if (panelIdx === btnIdx) {
otherPanel.setAttribute("class", "TabPanel_tabPanel__tXMJF");
}
else {
otherPanel.setAttribute("class", "TabPanel_tabPanel__tXMJF TabPanel_hidden__26UM3");
}
});
// 更新收藏列表
if (childBtn === displayButton) updateSubscribedList();
});
});
}
function addButton() {
const marketPanel = document.querySelector(".MarketplacePanel_marketplacePanel__21b7o ");
if (!marketPanel) {
return;
}
const subscribeButton = marketPanel.querySelector("button#SubscribeButton141");
subscribeButton || createSubscribeButton(marketPanel);
const displayButton = marketPanel.querySelector("button#displayButton141");
displayButton || createDisplayButton(marketPanel);
}
function start() {
console.info("[MWISubscribe] start");
mwi_common = window.mwi_common;
if (!mwi_common) {
console.error("[MWISubscribe] mwi_common not found");
return;
}
setInterval(addButton, 500);
}
if (window.mwi_common) start();
else {
console.info("[MWISubscribe] waiting for mwi_common");
document.addEventListener("mwi_common_injected", start);
}
})();