Greasy Fork

Greasy Fork is available in English.

Better MWI Chat

Make Chat Great Again!

目前为 2025-05-13 提交的版本。查看 最新版本

// ==UserScript==
// @name         Better MWI Chat
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Make Chat Great Again!
// @author       VoltaX
// @match        https://www.milkywayidle.com/*
// @icon         http://milkywayidle.com/favicon.ico
// @grant        none
// ==/UserScript==
const css = 
`
.mwibetterchat-disable{
    display: none;
}
.rotate-left{
    transform: rotate(90deg);
}
.rotate-right{
    transform: rotate(-90deg);
}
div.ChatMessage_chatMessage__2wev4[processed]:not([not-modified]){
    display: flex;
    flex-direction: column;
}
div.chat-message-header{
    display: flex;
    flex-direction: row;
}
div.chat-message-header span.timespan{
    display: none;
}
div.chat-message-header:hover span.timespan{
    display: auto;
}
div.chat-message-body{
    border-radius: 10px;
    margin: 3px;
    background: #4357af;
    padding: 5px 8px;
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    flex-shrink: 1;
    width: fit-content;
}
div.chat-message-body-wrapper{
    display: flex;
    flex-direction: row;
    flex-wrap: no-wrap;
    align-items: end;
}
button.repeat-msg-button:hover, div.chat-message-body-wrapper:hover button.repeat-msg-button, div.ChatMessage_chatMessage__2wev4:hover button.repeat-msg-button{
    display: inline-block;
}
button.repeat-msg-button:hover{
    cursor: pointer;
}
button.repeat-msg-button{
    display: none;
    margin: 3px 3px 6px;
    padding: 0px -2px;
    width: 24px;
    height: 24px;
    line-height: 16px;
    font-size: 10px;
    text-wrap: nowrap;
    border-radius: 12px;
    border: 2px solid #0099ff;
    color: #0099ff;
    background: rgba(0, 0, 0, 0);
}
`;
const InsertStyleSheet = (style) => {
    const s = new CSSStyleSheet();
    s.replaceSync(style);
    document.adoptedStyleSheets = [...document.adoptedStyleSheets, s];
};
InsertStyleSheet(css);
const HTML = (tagname, attrs, ...children) => {
    if(attrs === undefined) return document.createTextNode(tagname);
    const ele = document.createElement(tagname);
    if(attrs) for(const [key, value] of Object.entries(attrs)){
        if(value === null || value === undefined) continue;
        if(key.charAt(0) === "_"){
            const type = key.slice(1);
            ele.addEventListener(type, value);
        }
        else if(key === "eventListener"){
            for(const listener of value){
                ele.addEventListener(listener.type, listener.listener, listener.options);
            }
        }
        else ele.setAttribute(key, value);
    }
    for(const child of children) if(child) ele.append(child);
    return ele;
};
const ProcessChatMessage = () => {
    document.querySelectorAll("div.ChatHistory_chatHistory__1EiG3 > div.ChatMessage_chatMessage__2wev4:not([processed])").forEach(div => {
        div.setAttribute("processed", "");
        const timeSpan = div.children[0];
        const parent = div.parentElement;
        const isLastChild = parent.querySelector(":scope > div:nth-last-child(1)") === div;
        timeSpan.classList.add("timespan");
        const nameSpan = div.querySelector(":scope span.ChatMessage_name__1W9tB.ChatMessage_clickable__58ej2")?.parentElement?.parentElement?.parentElement;
        if(!nameSpan) {
            div.setAttribute("not-modified", "");
            return;
        }
        const nameWrapper = HTML("div", {class: "chat-message-header"});
        nameWrapper.replaceChildren(nameSpan, timeSpan);
        const bubble = HTML("div", {class: "chat-message-body-wrapper"});
        const contentWrapper = HTML("div", {class: "chat-message-body"});
        contentWrapper.replaceChildren(...[...div.children]);
        const repeat = HTML("button", {class: "repeat-msg-button", _click: () => {
            const content = contentWrapper.innerText;
            const input = document.querySelector("input.Chat_chatInput__16dhX");
            const prevVal = input.value;
            input.value = content;
            const ev = new Event("input", {bubbles: true});
            ev.simulated = true;
            const tracker = input._valueTracker;
            if(tracker) tracker.setValue(prevVal);
            input.dispatchEvent(ev);
        }}, "+1");
        bubble.replaceChildren(contentWrapper, repeat);
        div.replaceChildren(nameWrapper, bubble); 
        const contentHeight = div.getBoundingClientRect().height;
        if(isLastChild && (parent.offsetHeight + parent.scrollTop + contentHeight > parent.scrollHeight)) parent.scrollTop = parent.scrollTop + contentHeight;
        /*
        const texts = [...div.querySelectorAll(`:scope > span:not([style="display: inline-block;"]):not(.ChatMessage_timestamp__1iRZO)`)];
        texts.forEach(text => text.textContent = text.textContent.replaceAll("喵", ""));
        const userName = div.querySelector(":scope div.CharacterName_name__1amXp")?.dataset?.name;
        if(!userName) return;
        const content = texts.map(span => span.textContent).join("");
        if(content.length > 0){
            const DoRepeat = () => {
                const input = document.querySelector("input.Chat_chatInput__16dhX");
                const prevVal = input.value;
                input.value = content;
                const ev = new Event("input", {bubbles: true});
                ev.simulated = true;
                const tracker = input._valueTracker;
                if(tracker) tracker.setValue(prevVal);
                input.dispatchEvent(ev);
            }
            div.insertAdjacentElement("beforeend",
                HTML("button", {class: "comment-improvement-button repeat-comment-button", _click: DoRepeat}, " + 1 ")
            );
            texts.forEach(text => text.addEventListener("click", DoRepeat));
        }
        const DoMentionOrWhisper = (isMention) => () => {
            const mentionStr = `@${userName}`;
            const input = document.querySelector("input.Chat_chatInput__16dhX");
            const prevVal = input.value;
            input.value = isMention ? `${mentionStr} ${prevVal.replaceAll(/@[a-zA-Z0-9]+/g, "")}` : `/w ${userName} ${prevVal.replaceAll(/\/w [a-zA-Z0-9]+/g, "")}`;
            const ev = new Event("input", {bubbles: true});
            ev.simulated = true;
            const tracker = input._valueTracker;
            if(tracker) tracker.setValue(prevVal);
            input.dispatchEvent(ev);
        };
        div.insertAdjacentElement("beforeend",
            HTML("button", {class: "comment-improvement-button mention-sender-button", _click: DoMentionOrWhisper(true)}, "@此人")
        );
        div.insertAdjacentElement("beforeend",
            HTML("button", {class: "comment-improvement-button whisper-button", _click: DoMentionOrWhisper(false)}, "私聊")
        );
        */
    })
};
const AddSwitchButton = (chatDiv) => {
    const collapse = chatDiv.querySelector(":scope div.TabsComponent_expandCollapseButton__6nOWk");
    const collapsedupe = collapse.cloneNode(true);
    const arrowSVG = collapsedupe.children[0];
    arrowSVG.classList.add("rotate-left");
    collapsedupe.addEventListener("click", () => {
        collapse.classList.toggle("mwibetterchat-disable");
        arrowSVG.classList.toggle("rotate-left");
        arrowSVG.classList.toggle("rotate-right");
        MoveChatPannel(false);
    })
    collapse.classList.add("mwibetterchat-disable");
    collapse.insertAdjacentElement("afterend", collapsedupe);
};
const MoveChatPannel = (firstInvoked = true) => {
    const chatDiv = document.querySelector(`div.Chat_chat__3DQkj${firstInvoked?":not([moved])":""}`);
    const characterDiv = document.querySelector(`div.CharacterManagement_characterManagement__2PhvW${firstInvoked?":not([moved])":""}`);
    if(!chatDiv || !characterDiv) return;
    if(firstInvoked) AddSwitchButton(chatDiv);
    chatDiv.setAttribute("moved", "");
    characterDiv.setAttribute("moved", "");
    const chatWrapper = chatDiv.parentElement;
    characterDiv.replaceWith(chatDiv);
    chatWrapper.replaceChildren(characterDiv);
};
const ManageScrolling = () => {
    document.querySelectorAll("div.ChatHistory_chatHistory__1EiG3:not([listening])").forEach(div => {
        div.setAttribute("listening", "");
        div.addEventListener("scroll", (ev) => {
            const t = ev.target;
            const shouldScrollToBottom = t.offsetHeight + t.scrollTop + 25 > t.scrollHeight;
            console.log("called", shouldScrollToBottom, t.offsetHeight, t.scrollTop, t.scrollHeight);
            if(shouldScrollToBottom) t.scrollTop = 99999;
        })
    })
}
const OnMutate = (mutlist, observer) => {
    observer.disconnect();
    MoveChatPannel();
    //ManageScrolling();
    ProcessChatMessage();
    observer.observe(document, {subtree: true, childList: true});
};
new MutationObserver(OnMutate).observe(document, {subtree: true, childList: true});