// ==UserScript==
// @name [银河奶牛]仓库物品收藏和快速切换角色
// @name:en MWI Item Favorites Manager && Quickly Switch Characters
// @namespace http://tampermonkey.net/
// @version test0.23
// @description 仓库物品收藏管理和快速切换角色||Added a favorite button to the item menu and 4 characters buttons to the main page.
// @description:en Added a favorite button to the item menu and 4 characters buttons to the main page.
// @icon https://www.milkywayidle.com/favicon.svg
// @author Meoling
// @license MIT
// @match https://www.milkywayidle.com/*
// @match https://test.milkywayidle.com/*
// @grant GM_getValue
// @grant GM_setValue
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
// 获取当前角色名
function getCharacterName() {
const headerInfo = document.querySelector('.Header_info__26fkk');
if (!headerInfo) return null;
const nameElement = headerInfo.querySelector('.CharacterName_name__1amXp');
return nameElement ? nameElement.textContent.trim() : null;
}
// 保存收藏物品到本地存储
function saveFavoritesToLocalStorage(itemName, categoryName) {
const characterName = getCharacterName();
if (!characterName) return;
const storageKey = `mw_favorites_${characterName}`;
const favorites = loadFavoritesFromLocalStorage();
// 检查是否已存在相同物品
const existingIndex = favorites.findIndex(item => item.name === itemName);
if (existingIndex === -1) {
favorites.push({name: itemName, category: categoryName});
localStorage.setItem(storageKey, JSON.stringify(favorites));
}
}
// 从本地存储加载收藏物品
function loadFavoritesFromLocalStorage() {
const characterName = getCharacterName();
if (!characterName) return [];
const storageKey = `mw_favorites_${characterName}`;
return JSON.parse(localStorage.getItem(storageKey)) || [];
}
// 创建仓库收藏分类
function addFavoritesCategory() {
// 查找仓库的所有分类容器
const firstContainer = document.querySelector('.Inventory_items__6SXv0');
const inventoryContainers = firstContainer.querySelectorAll(':scope > div');
if (inventoryContainers && inventoryContainers.length > 0) {
const existingFavorites = firstContainer.querySelector('#favorites-category');
if (existingFavorites) {
return;
}
// 创建新的收藏分类
const favoritesContainer = document.createElement('div');
// 复制现有分类的结构
const itemGridHTML = `
`;
favoritesContainer.innerHTML = itemGridHTML;
favoritesContainer.id = 'favorites-category';
// 将收藏分类添加到仓库的最前面
if (firstContainer) {
firstContainer.insertBefore(favoritesContainer, firstContainer.firstChild);
//console.log('收藏分类已添加');
}
}
}
// 添加仓库收藏按钮
function addFavoriteButton(menuContainer) {
// 检查是否已存在收藏按钮
const existingButton = menuContainer.querySelector('.favorite-button');
if (existingButton) {
return;
}
const favoriteButton = document.createElement('button');
favoriteButton.className = 'Button_button__1Fe9z Button_fullWidth__17pVU favorite-button';
favoriteButton.textContent = '收藏/取消收藏';
// 添加点击事件
favoriteButton.addEventListener('click', function() {
// 获取当前物品名称
const itemName = menuContainer.querySelector('.Item_name__2C42x').textContent.trim();
const characterName = getCharacterName();
if (!characterName) return;
const favorites = loadFavoritesFromLocalStorage();
const itemIndex = favorites.findIndex(item => item.name === itemName);
const isFavorite = itemIndex !== -1;
if (isFavorite) {
const itemCategory = favorites[itemIndex].category;
favorites.splice(itemIndex, 1);
localStorage.setItem(`mw_favorites_${characterName}`, JSON.stringify(favorites));
const favoritesGrid = document.querySelector('#favorites-category .Inventory_itemGrid__20YAH');
const existingItem = favoritesGrid.querySelector(`svg[aria-label="${itemName}"]`);
if (existingItem) {
const inventoryItem = document.querySelector(`.Inventory_items__6SXv0 .Item_itemContainer__x7kH1 svg[aria-label="${itemName}"]`);
if (!inventoryItem) {
console.log('未在仓库中找到该物品');
return;
}
const itemContainer = inventoryItem.closest('.Item_itemContainer__x7kH1');
if (!itemContainer) {
console.log('无法获取物品容器');
return;
}
const categorySpan = [...document.querySelectorAll('.Inventory_categoryButton__35s1x')]
.find(span => span.textContent.trim() === itemCategory);
if (categorySpan) {
const categoryGrid = categorySpan.closest('.Inventory_itemGrid__20YAH');
if (categoryGrid) {
categoryGrid.appendChild(itemContainer);
}
}
refresh();
//existingItem.closest('.Item_itemContainer__x7kH1').remove();
}
} else {
const inventoryItem = document.querySelector(`.Inventory_items__6SXv0 .Item_itemContainer__x7kH1 svg[aria-label="${itemName}"]`);
if (!inventoryItem) {
console.log('未在仓库中找到该物品');
return;
}
const itemContainer = inventoryItem.closest('.Item_itemContainer__x7kH1');
if (!itemContainer) {
console.log('无法获取物品容器');
return;
}
const categoryGrid = itemContainer.closest('.Inventory_itemGrid__20YAH');
const categoryName = categoryGrid ?
categoryGrid.querySelector('.Inventory_categoryButton__35s1x')?.textContent.trim() :
'未知分类';
saveFavoritesToLocalStorage(itemName, categoryName);
const favoritesGrid = document.querySelector('#favorites-category .Inventory_itemGrid__20YAH');
if (!favoritesGrid) {
console.log('未找到收藏分类');
return;
}
const existingItem = favoritesGrid.querySelector(`svg[aria-label="${itemName}"]`);
if (!existingItem) {
favoritesGrid.appendChild(itemContainer);
}
}
});
menuContainer.appendChild(favoriteButton);
}
// 添加市场的分类容器(未完成)
function addMarketFavoritesCategory() {
}
// 刷新函数,当DOM变化时调用
function refresh() {
try {
// 仓库收藏功能
const inventoryContainer = document.querySelector('.Inventory_items__6SXv0');
if (inventoryContainer) {
addFavoritesCategory();
const favorites = loadFavoritesFromLocalStorage();
const favoritesGrid = document.querySelector('#favorites-category .Inventory_itemGrid__20YAH');
if (favoritesGrid) {
favorites.forEach(item => {
const inventoryItem = document.querySelector(`.Inventory_items__6SXv0 .Item_itemContainer__x7kH1 svg[aria-label="${item.name}"]`);
if (inventoryItem) {
const itemContainer = inventoryItem.closest('.Item_itemContainer__x7kH1');
const existingItem = favoritesGrid.querySelector(`svg[aria-label="${item.name}"]`);
if (!existingItem && itemContainer) {
favoritesGrid.appendChild(itemContainer);
}
}
});
}
}
// 检查是否出现物品菜单
const itemMenu = document.querySelector('.Item_actionMenu__2yUcG');
if (itemMenu) {
addFavoriteButton(itemMenu);
}
//市场物品的收藏(未完成)
// 角色选择页面检测 - 添加防抖
if (isCharacterSelectPage()) {
if (!window.mwCharacterDetected) {
window.mwCharacterDetected = true;
detectAndSaveCharacters();
}
} else {
window.mwCharacterDetected = false;
// 非角色选择页面,创建快速切换按钮 - 添加防抖
if (!document.querySelector('#character-switch-container')) {
createCharacterSwitchButtons();
}
}
} catch (error) {
console.log('刷新函数出错:', error);
}
}
// 设置MutationObserver监听DOM变化
const config = { attributes: true, childList: true, subtree: true };
const observer = new MutationObserver(function (mutationsList, observer) {
refresh();
});
observer.observe(document, config);
// 页面加载完成后执行一次
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', refresh);
} else {
refresh();
}
// ===== 快速切换角色功能 =====
// 获取服务器类型
function getServerType() {
return window.location.hostname.includes('test') ? 'test' : 'main';
}
// 检查是否在角色选择页面
function isCharacterSelectPage() {
return window.location.pathname.includes('/characterSelect');
}
// 检测并保存角色信息
function detectAndSaveCharacters() {
const serverType = getServerType();
const storageKey = `mw_characters_${serverType}`;
setTimeout(() => {
try {
const characterElements = document.querySelectorAll('.MuiTypography-root');
const gameModeElements = document.querySelectorAll('.CharacterSelectPage_gameMode__R2el7');
const characters = [];
let characterIndex = 0;
characterElements.forEach((element) => {
try {
if (element.tagName !== 'A') return;
const link = element.href;
if (!link || !link.includes('characterId=')) return;
const characterNameElement = element.querySelector('.CharacterName_characterName__2FqyZ');
if (!characterNameElement) return;
const characterName = characterNameElement.textContent.trim();
// 获取角色模式
let mode = '';
if (gameModeElements[characterIndex]) {
const modeText = gameModeElements[characterIndex].textContent.trim();
if (modeText === '标准' || modeText === 'Standard') {
mode = '标准';
} else if (modeText === '铁牛' || modeText === 'Ironcow') {
mode = '铁牛';
}
}
const displayText = mode ? `${mode}(${characterName})` : characterName;
characters.push({
name: characterName,
mode: mode,
link: link,
displayText: displayText
});
characterIndex++;
} catch (error) {
console.log('处理单个角色信息时出错:', error);
}
});
if (characters.length > 0) {
localStorage.setItem(storageKey, JSON.stringify(characters));
console.log(`已保存${characters.length}个角色信息到${serverType}服`);
}
} catch (error) {
console.log('检测角色信息时出错:', error);
}
}, 2000);
}
// 从本地存储加载角色信息
function loadCharactersFromStorage() {
const serverType = getServerType();
const storageKey = `mw_characters_${serverType}`;
const storedData = localStorage.getItem(storageKey);
return storedData ? JSON.parse(storedData) : [];
}
// 创建快速切换角色按钮
function createCharacterSwitchButtons() {
const existingContainer = document.querySelector('#character-switch-container');
if (existingContainer) existingContainer.remove();
const characters = loadCharactersFromStorage();
let isCollapsed = false;
// 主容器
const mainContainer = document.createElement('div');
mainContainer.id = 'character-switch-container';
Object.assign(mainContainer.style, {
position: 'fixed',
top: '10px',
left: '73%',
transform: 'translateX(-50%)',
zIndex: '9999',
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
});
// 折叠按钮
const toggleButton = document.createElement('button');
toggleButton.textContent = '▼';
toggleButton.title = characters.length > 0 ? '收起角色切换' : '收起角色选择';
Object.assign(toggleButton.style, {
padding: '2px 8px',
backgroundColor: 'rgba(48, 63, 159, 0.3)',
color: 'rgba(255, 255, 255, 0.9)',
border: '1px solid rgba(255, 255, 255, 0.2)',
borderRadius: '3px',
fontSize: '12px',
cursor: 'pointer',
backdropFilter: 'blur(2px)',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
minWidth: '24px',
height: '20px',
marginBottom: '5px'
});
// 按钮容器
const buttonContainer = document.createElement('div');
Object.assign(buttonContainer.style, {
display: 'flex',
gap: '10px',
flexWrap: 'wrap',
maxWidth: '500px',
justifyContent: 'center'
});
// 通用按钮样式
const buttonStyle = {
padding: '4px 8px',
backgroundColor: 'rgba(48, 63, 159, 0.3)',
color: 'rgba(255, 255, 255, 0.9)',
border: '1px solid rgba(255, 255, 255, 0.2)',
borderRadius: '3px',
fontSize: '12px',
backdropFilter: 'blur(2px)',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
textDecoration: 'none',
cursor: 'pointer',
whiteSpace: 'nowrap',
maxWidth: '120px',
overflow: 'hidden',
textOverflow: 'ellipsis'
};
const hoverStyle = {
backgroundColor: 'rgba(26, 35, 126, 0.5)',
boxShadow: '0 2px 5px rgba(0,0,0,0.2)'
};
// 如果没有角色数据,创建一个跳转到角色选择界面的按钮
if (characters.length === 0) {
const characterSelectButton = document.createElement('a');
characterSelectButton.textContent = '获取角色数据';
// 构建角色选择页面的URL
const currentOrigin = window.location.origin;
characterSelectButton.href = `${currentOrigin}/characterSelect`;
characterSelectButton.title = '前往角色选择页面获取角色信息';
Object.assign(characterSelectButton.style, buttonStyle);
characterSelectButton.addEventListener('mouseover', () => Object.assign(characterSelectButton.style, hoverStyle));
characterSelectButton.addEventListener('mouseout', () => Object.assign(characterSelectButton.style, buttonStyle));
buttonContainer.appendChild(characterSelectButton);
} else {
// 有角色数据时,创建角色按钮
characters.forEach((character) => {
const button = document.createElement('a');
button.textContent = character.displayText;
button.href = character.link;
button.title = `切换到: ${character.displayText}`;
Object.assign(button.style, buttonStyle);
button.addEventListener('mouseover', () => Object.assign(button.style, hoverStyle));
button.addEventListener('mouseout', () => Object.assign(button.style, buttonStyle));
buttonContainer.appendChild(button);
});
}
// 折叠按钮事件
toggleButton.addEventListener('click', () => {
isCollapsed = !isCollapsed;
buttonContainer.style.display = isCollapsed ? 'none' : 'flex';
toggleButton.textContent = isCollapsed ? '▲' : '▼';
if (characters.length > 0) {
toggleButton.title = isCollapsed ? '展开角色切换' : '收起角色切换';
} else {
toggleButton.title = isCollapsed ? '展开角色选择' : '收起角色选择';
}
});
toggleButton.addEventListener('mouseover', () => Object.assign(toggleButton.style, hoverStyle));
toggleButton.addEventListener('mouseout', () => Object.assign(toggleButton.style, buttonStyle));
mainContainer.appendChild(toggleButton);
mainContainer.appendChild(buttonContainer);
document.body.appendChild(mainContainer);
}
})();