MWIFavorites

收藏和数量跟踪

// ==UserScript==
// @name         MWIFavorites
// @namespace    http://tampermonkey.net/
// @version      1.1.5
// @description  收藏和数量跟踪
// @author       xiaoshui05
// @match        https://www.milkywayidle.com/*
// @match        https://test.milkywayidle.com/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// ==/UserScript==

(function() {
    'use strict';



    let itemFavoritesMaps = {};
    let itemFavoritesMapsId={};
    let itemFavoritesWorking=false;
    let needUpdate=false;
    let itemCount={};
    let currentUrl = window.location.href;
    let characterId = currentUrl.substring(currentUrl.indexOf('=')+1);

    function getCharacterId(){
        currentUrl = window.location.href;
        characterId = currentUrl.substring(currentUrl.indexOf('=')+1);
    }
    function numberFormatter(num, digits = 1) {
        if (num === null || num === undefined) {
            return null;
        }
        if (num < 0) {
            return "-" + numberFormatter(-num);
        }
        const lookup = [
            { value: 1, symbol: "" },
            { value: 1e3, symbol: "k" },
            { value: 1e6, symbol: "M" },
            { value: 1e9, symbol: "B" },
        ];
        const rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
        var item = lookup
            .slice()
            .reverse()
            .find(function (item) {
                return num >= item.value;
            });
        return item ? (num / item.value).toFixed(digits).replace(rx, "$1") + item.symbol : "0";
    }
    /* 支持修改版汉化插件 */
    function getOriTextFromElement(elem) {
        if (!elem) {
            console.error("getTextFromElement null elem");
            return "";
        }
        const translatedfrom = elem.getAttribute("script_translatedfrom");
        if (translatedfrom) {
            return translatedfrom;
        }
        return elem.textContent;
    }


    hookWS();
    function hookWS() {
        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 }); // Anti-loop

            return handleMessage(message);
        }
    }

    function handleMessage(message) {
        let obj = JSON.parse(message);
        if (obj && obj.type === "init_character_data") {
            itemCount=[];
            itemFavoritesMaps={};
            getCharacterId();
            itemFavoritesMapsLoad();
            for (const item of obj.characterItems) {
                if (item.itemLocationHrid === "/item_locations/inventory") {
                    updateItemCount(item.itemHrid.slice(7),item.count);
                }
            }
        } else if (obj && obj.type === "action_completed" && obj.endCharacterItems) {
            for (const item of obj.endCharacterItems) {
                if (item.itemLocationHrid === "/item_locations/inventory") {
                    updateItemCount(item.itemHrid.slice(7),item.count);
                }
            }
        }else if (obj && obj.type === "items_updated" && obj.endCharacterItems) {
            for (const item of obj.endCharacterItems) {
                if (item.itemLocationHrid === "/item_locations/inventory") {
                    updateItemCount(item.itemHrid.slice(7),item.count);
                }
            }
        };
        return message;
    }

    function updateItemCount(id,count){
        itemCount[id]=count;
        if(itemFavoritesMapsId[id]){
            needUpdate=true;
        }
    }

    /* 添加收藏 展示*/
    const itemFavoritesMapsSave = function(){
        localStorage.setItem('itemFavoritesMaps'+characterId,JSON.stringify(itemFavoritesMaps));
    };
    const itemFavoritesMapsLoad = function(){
        let temp = localStorage.getItem('itemFavoritesMaps'+characterId)
        if(temp){
            itemFavoritesMaps = JSON.parse(temp);
        }
    };
    const itemFavoritesShow = function(){
        let scriptFavoritesShowDiv = null;
        let targetNode = document.querySelector('div.Inventory_items__6SXv0');

        if(!targetNode){
            setTimeout(itemFavoritesShow,1000);
        }
        if(itemFavoritesWorking || (!targetNode)){
            return;
        }
        itemFavoritesWorking=true;

        scriptFavoritesShowDiv = targetNode.querySelector('div.Script_favorites_show');
        if(scriptFavoritesShowDiv){
            targetNode.removeChild(scriptFavoritesShowDiv);
        }
        if((!itemFavoritesMaps) || Object.keys(itemFavoritesMaps).length===0){
            itemFavoritesWorking=false;
            return;
        }
        //获取图标地址
        let firstItemItem2De2O = targetNode.querySelector('div.Item_item__2De2O');
        let iconBasePath = firstItemItem2De2O.querySelector('use').href.baseVal.split('#')[0]

        let itemFavoriteData =[];
        itemFavoritesMapsId={};
        for(const k in itemFavoritesMaps){
            let temp={};
            temp.name= k.trim();
            temp.id=temp.name.toLowerCase().replaceAll(' ','_').replaceAll("'",'');
            itemFavoritesMapsId[temp.id]=true;
            temp.target=Number(itemFavoritesMaps[k]);
            temp.count=itemCount[temp.id];
            if(!temp.count){
                temp.count=0;
            }
            temp.rate=temp.count/temp.target;
            itemFavoriteData.push(temp);
        }

        itemFavoriteData.sort((a,b)=>a.rate-b.rate);


        const insertElem = document.createElement("div");
        insertElem.className='Inventory_itemGrid__20YAH';
        insertElem.insertAdjacentHTML('beforeend','<div class="Inventory_label__XEOAx"><span class="Inventory_categoryButton__35s1x" script_translatedfrom="Currencies">收藏</span></div>');


        for(let item of itemFavoriteData){
            let shortage =item.count-item.target;
            //if(shortage<=0){shortage='';}
            insertElem.insertAdjacentHTML('beforeend',
                                          `
                                          <div class="Item_itemContainer__x7kH1">
                                              <div>
                                                  <div class="Item_item__2De2O Item_clickable__3viV6">
                                                      <div class="Item_count__1HVvv">${numberFormatter(shortage)}</div>
                                                      <div class="Item_iconContainer__5z7j4">
                                                          <svg role="img" aria-label="${item.name}" class="Icon_icon__2LtL_" width="100%" height="100%">
                                                              <use href="${iconBasePath}#${item.id}"></use>
                                                          </svg>
                                                       </div>
                                                       <div class="script_itemLevel" style="z-index: 1;position: absolute;top: 2px;text-align: right;color:${item.rate<1?'red':'green'}">${(item.rate * 100).toFixed(0)}%</div>
                                                   </div>
                                               </div>
                                           </div>
                                           `
                                         );
        }

        for(let item of insertElem.querySelectorAll('div.Item_itemContainer__x7kH1')){
            item.addEventListener('click', function(event) {
                let tipsElem = document.body.querySelector('#scripte_favorites_tips')
                if(tipsElem){
                    tipsElem.parentNode.removeChild(tipsElem);
                }

                let name = event.currentTarget.children[0].children[0].children[1].children[0].ariaLabel.trim();
                let id=name.toLowerCase().replaceAll(' ','_').replaceAll("'",'');

                tipsElem = document.createElement('div');
                tipsElem.id='scripte_favorites_tips';
                tipsElem.insertAdjacentHTML('beforeend',
                                            `
                                                <div class="MuiTooltip-tooltip MuiTooltip-tooltipPlacementBottom css-1spb1s5" style="opacity: 1; transition: opacity cubic-bezier(0.4, 0, 0.2, 1);">
                                                    <div class="Item_actionMenu__2yUcG">
                                                        <div class="Item_itemInfo__3zAGf">
                                                            <span class="Item_name__2C42x" script_translatedfrom=" ${name} "> ${name} </span>
                                                        </div>
                                                        <div class="Item_itemInfo__3zAGf">
                                                            <span class="Item_name__2C42x" script_translatedfrom=" Amount: ${numberFormatter(itemCount[id])}/${numberFormatter(itemFavoritesMaps[name])}"> Amount: ${numberFormatter(itemCount[id])}/${numberFormatter(itemFavoritesMaps[name])}</span>
                                                        </div>
                                                        <button class="Button_button__1Fe9z Button_sell__3FNpM Button_fullWidth__17pVU" script_translatedfrom="Sell For 1000 Coins">取消收藏</button>
                                                    </div>
                                                </div>
                                           `
                                           );

                tipsElem.children[0].children[0].children[2].addEventListener('click',function(){
                    delete itemFavoritesMaps[name];
                    itemFavoritesMapsSave();
                    itemFavoritesShow();
                    document.body.removeChild(tipsElem);
                })

                tipsElem.style='position: absolute; inset: 0px auto auto 0px; margin: 0px;z-index:999;';
                tipsElem.style.backgroundColor='#616133'
                tipsElem.style.position = 'absolute';
                let rect = event.currentTarget.getBoundingClientRect();
                tipsElem.style.left = (rect.left-100) + 'px';
                tipsElem.style.top = (rect.top+60) + 'px';
                document.body.appendChild(tipsElem);

                setTimeout(()=>{document.body.removeChild(tipsElem)},3000);

            })
        }

        scriptFavoritesShowDiv = document.createElement("div");
        scriptFavoritesShowDiv.className='Script_favorites_show';
        scriptFavoritesShowDiv.appendChild(insertElem);
        targetNode.insertAdjacentElement('afterbegin',scriptFavoritesShowDiv)

        itemFavoritesWorking=false;
    };
    itemFavoritesMapsLoad();
    itemFavoritesShow();
    setInterval(()=>{
        if(needUpdate){
            needUpdate=false;
            itemFavoritesShow();
        }
    },5000);


    /* 添加收藏 按钮*/
    const itemFavoritesObserver = new MutationObserver(async function (mutations) {
        for (const mutation of mutations) {
            for (const added of mutation.addedNodes) {
                if (added.classList.contains("MuiTooltip-popper")) {
                    let item_amountInputContainer = added.querySelector("div.Item_amountInputContainer__1RT17");
                    let item_input=added.querySelector('input.Input_input__2-t98');
                    if(item_amountInputContainer && item_input){

                        let name = getOriTextFromElement(added.querySelector('span.Item_name__2C42x')).trim();

                        let favoritesDiv = document.createElement("div");
                        favoritesDiv.className='Item_amountInputContainer__1RT17';

                        let favoritesInput= document.createElement("input");
                        favoritesInput.className='Input_input__2-t98';
                        favoritesInput.type='number';
                        favoritesInput.value=0;
                        if(itemFavoritesMaps[name]){
                            favoritesInput.value=Number(itemFavoritesMaps[name]);
                        };
                        favoritesInput.setAttribute('maxlength','14');
                        let btnAdd = document.createElement("button");
                        btnAdd.className='Button_button__1Fe9z Button_fullWidth__17pVU';
                        btnAdd.innerText = "添加收藏";
                        btnAdd.onclick = () => {
                            if(Number(favoritesInput.value)<=0){return;}
                            itemFavoritesMaps[name]=Number(favoritesInput.value);
                            favoritesInput.value=Number(itemFavoritesMaps[name]);
                            itemFavoritesMapsSave();
                            itemFavoritesShow();
                        };
                        let btnDel = document.createElement("button");
                        btnDel.className='Button_button__1Fe9z Button_fullWidth__17pVU Button_sell__3FNpM';
                        btnDel.innerText = "取消收藏";
                        btnDel.onclick = () => {
                            delete itemFavoritesMaps[name];
                            favoritesInput.value=0;
                            itemFavoritesMapsSave();
                            itemFavoritesShow();
                        };

                        let favoritesInputDiv= document.createElement("div");
                        favoritesInputDiv.className='Input_inputContainer__22GnD Input_small__1-Eva';
                        favoritesInputDiv.appendChild(favoritesInput);
                        favoritesDiv.appendChild(favoritesInputDiv);
                        favoritesDiv.appendChild(btnAdd);
                        favoritesDiv.appendChild(btnDel);


                        item_amountInputContainer.parentNode.insertAdjacentElement('beforeend',favoritesDiv);

                    }
                }
            }
        }
    });
    itemFavoritesObserver.observe(document.body, { attributes: false, childList: true, characterData: false });

})();