ColaManga 瀏覽增強

隱藏廣告內容,提昇瀏覽體驗。自訂背景顏色,圖片大小調整。當圖片載入失敗時,自動重新載入圖片。提供熱鍵功能:[← 上一頁]、[下一頁 →]、[↑ 自動上滾動]、[↓ 自動下滾動]。當用戶滾動到頁面底部時,自動跳轉到下一頁。

// ==UserScript==
// @name         ColaManga 瀏覽增強
// @name:zh-TW   ColaManga 瀏覽增強
// @name:zh-CN   ColaManga 浏览增强
// @name:en      ColaManga Browsing Enhance
// @version      0.0.12-Beta
// @author       Canaan HS
// @description       隱藏廣告內容,提昇瀏覽體驗。自訂背景顏色,圖片大小調整。當圖片載入失敗時,自動重新載入圖片。提供熱鍵功能:[← 上一頁]、[下一頁 →]、[↑ 自動上滾動]、[↓ 自動下滾動]。當用戶滾動到頁面底部時,自動跳轉到下一頁。
// @description:zh-TW 隱藏廣告內容,提昇瀏覽體驗。自訂背景顏色,圖片大小調整。當圖片載入失敗時,自動重新載入圖片。提供熱鍵功能:[← 上一頁]、[下一頁 →]、[↑ 自動上滾動]、[↓ 自動下滾動]。當用戶滾動到頁面底部時,自動跳轉到下一頁。
// @description:zh-CN 隐藏广告内容,提昇浏览体验。自定义背景颜色,调整图片大小。当图片载入失败时,自动重新载入图片。提供快捷键功能:[← 上一页]、[下一页 →]、[↑ 自动上滚动]、[↓ 自动下滚动]。当用户滚动到页面底部时,自动跳转到下一页。
// @description:en    Hide advertisement content, enhance browsing experience. Customize background color, adjust image size. Automatically reload images when they fail to load. Provide shortcut key functionalities: [← Previous Page], [Next Page →], [↑ Auto Scroll Up], [↓ Auto Scroll Down]. Automatically jump to the next page when users scroll to the bottom of the page.

// @match        *://www.colamanga.com/manga-*/*/*.html
// @icon         https://www.colamanga.com/favicon.png

// @license      MPL-2.0
// @namespace    https://greasyforks.org/users/989635

// @require      https://update.greasyforks.org/scripts/487608/1597491/SyntaxLite_min.js

// @grant        GM_setValue
// @grant        GM_getValue

// @run-at       document-start
// ==/UserScript==

(function () {
    /* 臨時的自定義 (當 Enable = false 時, 其餘的設置將無效) */
    const Config = {
        BGColor: {
            Enable: true,
            Color: "#595959",
        },
        AutoTurnPage: { // 自動翻頁
            Enable: true,
            Mode: 3, // 1 = 快速 | 2 = 一般無盡 | 3 = 優化無盡
        },
        RegisterHotkey: { // 快捷功能
            Enable: true,
            Function: { // 移動端不適用以下配置
                TurnPage: true, // 翻頁
                AutoScroll: true, // 自動滾動
                KeepScroll: true, // 換頁繼續滾動
                ManualScroll: false, // 手動滾動啟用時, 將會變成點擊一次, 根據視點翻一頁 且 自動滾動會無效
            }
        }
    };
    const Control = {
        ScrollPixels: 2,
        WaitPicture: 1e3,
        BlockListener: new Set(["auxclick", "mousedown", "pointerup", "pointerdown", "dState", "touchstart", "unhandledrejection"]),
        IdList: {
            Title: "CME_Title",
            Iframe: "CME_Iframe",
            Block: "CME_Block-Ads",
            Menu: "CME_Menu-Style",
            Image: "CME_Image-Style",
            Scroll: "CME_Scroll-Hidden",
            ChildS: "CME_Child-Scroll-Hidden"
        }
    };
    const Param = {
        Body: null,
        ContentsPage: null,
        HomePage: null,
        PreviousLink: null,
        NextLink: null,
        MangaList: null,
        BottomStrip: null,
        Up_scroll: false,
        Down_scroll: false,
        IsFinalPage: false,
        IsMainPage: window.self === window.parent
    };
    function Tools(Syn2, Config2, Control2, Param2) {
        const IdWhiteList = new Set(Object.values(Control2.IdList));
        const Storage = (key, value = null) => {
            return value != null ? Syn2.Session(key, {
                value: value
            }) : Syn2.Session(key);
        };
        const TopDetected = Syn2.Throttle(() => {
            Param2.Up_scroll = Syn2.sY == 0 ? (Storage("scroll", false), false) : true;
        }, 1e3);
        const Skip = Config2.AutoTurnPage.Mode === 1 && Config2.RegisterHotkey.Function.KeepScroll;
        const IsTheBottom = () => Syn2.sY + Syn2.iH >= document.documentElement.scrollHeight;
        const BottomDetected = Syn2.Throttle(() => {
            if (Skip) return;
            Param2.Down_scroll = IsTheBottom() ? (Storage("scroll", false), false) : true;
        }, 1e3);
        return {
            Storage: Storage,
            GetSet: () => {
                return Syn2.gV("Style", {
                    BG_Color: "#595959",
                    Img_Bw: "auto",
                    Img_Mw: "100%"
                });
            },
            Get_Nodes(Root) {
                const nodes = [];
                function Task(root) {
                    const tree = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, {
                        acceptNode: node => {
                            if (IdWhiteList.has(node.id)) {
                                return NodeFilter.FILTER_REJECT;
                            }
                            return NodeFilter.FILTER_ACCEPT;
                        }
                    });
                    while (tree.nextNode()) {
                        nodes.push(tree.currentNode);
                    }
                }
                Task(Root.head);
                Task(Root.body);
                return nodes;
            },
            AutoScroll(move) {
                requestAnimationFrame(() => {
                    if (Param2.Up_scroll && move < 0) {
                        window.scrollBy(0, move);
                        TopDetected();
                        this.AutoScroll(move);
                    } else if (Param2.Down_scroll && move > 0) {
                        window.scrollBy(0, move);
                        BottomDetected();
                        this.AutoScroll(move);
                    }
                });
            },
            ManualScroll(move) {
                window.scrollBy({
                    left: 0,
                    top: move,
                    behavior: "smooth"
                });
            },
            FinalPage(link) {
                Param2.IsFinalPage = link.startsWith("javascript");
                return Param2.IsFinalPage;
            },
            VisibleObjects: object => object.filter(img => img.height > 0 || img.src),
            ObserveObject: object => object[Math.max(object.length - 2, 0)],
            DetectionValue(object) {
                return this.VisibleObjects(object).length >= Math.floor(object.length * .5);
            }
        };
    }
    function Style(Syn2, Control2, Param2, Set2) {
        return {
            async BackgroundStyle(Color) {
                Param2.Body.style.cssText = `
                background: ${Color} !important;
            `;
                document.documentElement.style.cssText = `
                overflow: visible !important;
            `;
            },
            async PictureStyle() {
                if (Syn2.Platform === "Desktop") {
                    Syn2.AddStyle(`
                    .mh_comicpic img {
                        margin: auto;
                        display: block;
                        cursor: pointer;
                        vertical-align: top;
                        width: ${Set2.Img_Bw};
                        max-width: ${Set2.Img_Mw};
                    }
                `, Control2.IdList.Image);
                }
                setTimeout(() => {
                    const click = new MouseEvent("click", {
                        bubbles: true,
                        cancelable: true
                    });
                    const observer = new IntersectionObserver(observed => {
                        observed.forEach(entry => {
                            if (entry.isIntersecting) {
                                entry.target.dispatchEvent(click);
                            }
                        });
                    }, {
                        threshold: .3
                    });
                    Param2.MangaList.$qa("span.mh_btn:not(.contact):not(.read_page_link)").forEach(reloadBtn => observer.observe(reloadBtn));
                }, Control2.WaitPicture);
            },
            async MenuStyle() { }
        };
    }
    function PageTurn(Syn2, Control2, Param2, tools) {
        async function Unlimited(Optimized) {
            Syn2.AddStyle(`
            .mh_wrap, .mh_readend, .mh_footpager,
            .fed-foot-info, #imgvalidation2022 {display: none;}
            body {
                margin: 0;
                padding: 0;
            }
            #${Control2.IdList.Iframe} {
                margin: 0;
                padding: 0;
                width: 100%;
                height: 110vh;
                border: none;
            }
        `, Control2.IdList.Scroll);
            const StylelRules = Syn2.$q(`#${Control2.IdList.Scroll}`).sheet.cssRules;
            if (Param2.IsMainPage) {
                let Size = 0;
                window.addEventListener("message", event => {
                    const data = event.data;
                    if (data && data.length > 0) {
                        const {
                            Title,
                            PreviousUrl,
                            CurrentUrl,
                            NextUrl,
                            Resize,
                            SizeSet,
                            SizeRecord
                        } = data[0];
                        if (Resize) {
                            if (Size > SizeRecord) Size -= SizeRecord;
                            Size += Resize;
                            StylelRules[2].style.height = `${Size}px`;
                        } else if (SizeSet) StylelRules[2].style.height = `${SizeSet}px`; else if (Title && NextUrl && PreviousUrl && CurrentUrl) {
                            document.title = Title;
                            Param2.NextLink = NextUrl;
                            Param2.PreviousLink = PreviousUrl;
                            history.pushState(null, null, CurrentUrl);
                        }
                    }
                });
            } else {
                Syn2.AddStyle(`
                html {
                    overflow: hidden !important;
                    overflow-x: hidden !important;
                    scrollbar-width: none !important;
                    -ms-overflow-style: none !important;
                }
                html::-webkit-scrollbar {
                    display: none !important;
                }
            `, Control2.IdList.ChildS);
                let MainWindow = window;
                window.addEventListener("message", event => {
                    while (MainWindow.parent !== MainWindow) {
                        MainWindow = MainWindow.parent;
                    }
                    MainWindow.postMessage(event.data, Syn2.$origin);
                });
            }
            const iframe = Syn2.createElement("iframe", {
                id: Control2.IdList.Iframe,
                src: Param2.NextLink
            });
            (() => {
                let Img, Observer, Quantity = 0;
                const Observer_Next = new IntersectionObserver(observed => {
                    observed.forEach(entry => {
                        if (entry.isIntersecting && tools.DetectionValue(Img)) {
                            Observer_Next.disconnect();
                            Observer.disconnect();
                            TurnPage();
                        }
                    });
                }, {
                    threshold: .1
                });
                setTimeout(() => {
                    Img = Param2.MangaList.$qa("img");
                    if (Img.length <= 5) {
                        TurnPage();
                        return;
                    }
                    Observer_Next.observe(tools.ObserveObject(tools.VisibleObjects(Img)));
                    Syn2.Observer(Param2.MangaList, () => {
                        const Visible = tools.VisibleObjects(Img);
                        const VL = Visible.length;
                        if (VL > Quantity) {
                            Quantity = VL;
                            Observer_Next.disconnect();
                            Observer_Next.observe(tools.ObserveObject(Visible));
                        }
                    }, {
                        debounce: 300
                    }, observer => {
                        Observer = observer.ob;
                    });
                }, Control2.WaitPicture);
            })();
            function TurnPage() {
                let CurrentHeight = 0;
                const Resize = new ResizeObserver(() => {
                    const NewHeight = Param2.MangaList.offsetHeight;
                    if (NewHeight > CurrentHeight) {
                        window.parent.postMessage([{
                            Resize: Param2.MangaList.offsetHeight,
                            SizeRecord: CurrentHeight
                        }], Syn2.$origin);
                        CurrentHeight = NewHeight;
                    }
                });
                if (tools.FinalPage(Param2.NextLink)) {
                    if (Optimized) {
                        window.parent.postMessage([{
                            SizeSet: Param2.MangaList.offsetHeight + 245
                        }], Syn2.$origin);
                    }
                    StylelRules[0].style.display = "block";
                    return;
                }
                Waitload();
                Param2.Body.appendChild(iframe);
                Resize.observe(Param2.MangaList);
                function Waitload() {
                    let IframeWindow, CurrentUrl, Content, AllImg;
                    const Failed = () => {
                        iframe.offAll();
                        iframe.src = Param2.NextLink;
                        Waitload();
                    };
                    const Success = () => {
                        iframe.offAll();
                        IframeWindow = iframe.contentWindow;
                        CurrentUrl = IframeWindow.location.href;
                        if (CurrentUrl !== Param2.NextLink) {
                            Failed();
                            return;
                        }
                        Content = IframeWindow.document;
                        Content.body.style.overflow = "hidden";
                        Syn2.Log("無盡翻頁", CurrentUrl);
                        AllImg = Content.$qa("#mangalist img");
                        const UrlUpdate = new IntersectionObserver(observed => {
                            observed.forEach(entry => {
                                var _a, _b;
                                if (entry.isIntersecting) {
                                    UrlUpdate.disconnect();
                                    Resize.disconnect();
                                    const PageLink = Content.body.$qa("div.mh_readend ul a");
                                    window.parent.postMessage([{
                                        Title: Content.title,
                                        CurrentUrl: CurrentUrl,
                                        PreviousUrl: (_a = PageLink[0]) == null ? void 0 : _a.href,
                                        NextUrl: (_b = PageLink[2]) == null ? void 0 : _b.href
                                    }], Syn2.$origin);
                                }
                            });
                        }, {
                            threshold: .1
                        });
                        AllImg.forEach(async img => UrlUpdate.observe(img));
                        if (Optimized) {
                            Syn2.$q("title").id = Control2.IdList.Title;
                            const adapt = Syn2.Platform === "Desktop" ? .5 : .7;
                            const ReleaseMemory = new IntersectionObserver(observed => {
                                observed.forEach(entry => {
                                    if (entry.isIntersecting) {
                                        const targetImg = entry.target;
                                        const ratio = Math.min(adapt, Syn2.iH * adapt / targetImg.clientHeight);
                                        if (entry.intersectionRatio >= ratio) {
                                            ReleaseMemory.disconnect();
                                            tools.Get_Nodes(document).forEach(node => {
                                                node.remove();
                                            });
                                            targetImg.scrollIntoView();
                                        }
                                    }
                                });
                            }, {
                                threshold: [0, .1, .2, .3, .4, .5, .6, .7, .8, .9, 1]
                            });
                            AllImg.forEach(async img => ReleaseMemory.observe(img));
                        }
                    };
                    iframe.on("load", Success);
                    iframe.on("error", Failed);
                }
            }
        }
        return {
            async Auto(Mode) {
                switch (Mode) {
                    case 2:
                    case 3:
                        Unlimited(Mode === 3);
                        break;

                    default:
                        setTimeout(() => {
                            const img = Param2.MangaList.$qa("img");
                            if (!tools.FinalPage(Param2.NextLink)) {
                                const Observer_Next = new IntersectionObserver(observed => {
                                    observed.forEach(entry => {
                                        if (entry.isIntersecting && tools.DetectionValue(img)) {
                                            location.assign(Param2.NextLink);
                                        }
                                    });
                                }, {
                                    threshold: .5
                                });
                                Observer_Next.observe(Param2.BottomStrip);
                            }
                        }, Control2.WaitPicture);
                }
            }
        };
    }
    (async () => {
        const tools = Tools(Syn, Config, Control, Param);
        const Set2 = tools.GetSet();
        const style = Style(Syn, Control, Param, Set2);
        const turn = PageTurn(Syn, Control, Param, tools);
        async function BlockAds() {
            Syn.AddStyle(`
            html {pointer-events: none !important;}
            div[style*='position'] {display: none !important;}
            .mh_wrap a,
            .mh_readend a,
            span.mh_btn:not(.contact),
            #${Control.IdList.Iframe} {
                pointer-events: auto !important;
            }
        `, Control.IdList.Block);
            const OriginListener = EventTarget.prototype.addEventListener;
            const Block = Control.BlockListener;
            EventTarget.prototype.addEventListener = new Proxy(OriginListener, {
                apply(target, thisArg, args) {
                    const [type, listener, options] = args;
                    if (Block.has(type)) return;
                    return target.apply(thisArg, args);
                }
            });
            const iframe = `iframe:not(#${Control.IdList.Iframe})`;
            const AdCleanup = () => {
                var _a;
                (_a = Syn.$q(iframe)) == null ? void 0 : _a.remove();
                requestIdleCallback(AdCleanup, {
                    timeout: 300
                });
            };
            AdCleanup();
        }
        async function HotkeySwitch(Use) {
            let JumpState = false;
            if (Syn.Platform === "Desktop") {
                if (Param.IsMainPage && Use.KeepScroll && Use.AutoScroll && !Use.ManualScroll) {
                    Param.Down_scroll = tools.Storage("scroll");
                    Param.Down_scroll && tools.AutoScroll(Control.ScrollPixels);
                }
                const UP_ScrollSpeed = -2;
                const CanScroll = Use.AutoScroll || Use.ManualScroll;
                Syn.onEvent(window, "keydown", event => {
                    const key = event.key;
                    if (key === "ArrowLeft" && Use.TurnPage && !JumpState) {
                        event.stopImmediatePropagation();
                        JumpState = !tools.FinalPage(Param.PreviousLink);
                        location.assign(Param.PreviousLink);
                    } else if (key === "ArrowRight" && Use.TurnPage && !JumpState) {
                        event.stopImmediatePropagation();
                        JumpState = !tools.FinalPage(Param.NextLink);
                        location.assign(Param.NextLink);
                    } else if (key === "ArrowUp" && CanScroll) {
                        event.stopImmediatePropagation();
                        event.preventDefault();
                        if (Use.ManualScroll) {
                            tools.ManualScroll(-Syn.iH);
                        } else {
                            if (Param.Up_scroll) {
                                Param.Up_scroll = false;
                            } else if (!Param.Up_scroll || Param.Down_scroll) {
                                Param.Down_scroll = false;
                                Param.Up_scroll = true;
                                tools.AutoScroll(UP_ScrollSpeed);
                            }
                        }
                    } else if (key === "ArrowDown" && CanScroll) {
                        event.stopImmediatePropagation();
                        event.preventDefault();
                        if (Use.ManualScroll) {
                            tools.ManualScroll(Syn.iH);
                        } else {
                            if (Param.Down_scroll) {
                                Param.Down_scroll = false;
                                tools.Storage("scroll", false);
                            } else if (Param.Up_scroll || !Param.Down_scroll) {
                                Param.Up_scroll = false;
                                Param.Down_scroll = true;
                                tools.Storage("scroll", true);
                                tools.AutoScroll(Control.ScrollPixels);
                            }
                        }
                    }
                }, {
                    capture: true
                });
            } else if (Syn.Platform === "Mobile") {
                let startX, startY, moveX, moveY;
                const sidelineX = Syn.iW * .3;
                const sidelineY = Syn.iH / 4 * .3;
                Syn.onEvent(window, "touchstart", event => {
                    startX = event.touches[0].clientX;
                    startY = event.touches[0].clientY;
                }, {
                    passive: true
                });
                Syn.onEvent(window, "touchmove", Syn.Debounce(event => {
                    moveY = event.touches[0].clientY - startY;
                    if (Math.abs(moveY) < sidelineY) {
                        moveX = event.touches[0].clientX - startX;
                        if (moveX > sidelineX && !JumpState) {
                            JumpState = !tools.FinalPage(Param.PreviousLink);
                            location.assign(Param.PreviousLink);
                        } else if (moveX < -sidelineX && !JumpState) {
                            JumpState = !tools.FinalPage(Param.NextLink);
                            location.assign(Param.NextLink);
                        }
                    }
                }, 60), {
                    passive: true
                });
            }
        }
        (() => {
            async function Init(callback) {
                Syn.WaitElem(["body", "div.mh_readtitle", "div.mh_headpager", "div.mh_readend", "#mangalist"], null, {
                    timeout: 10,
                    throttle: 30,
                    visibility: Param.IsMainPage,
                    timeoutResult: true
                }).then(([Body, Title, HeadPager, Readend, Manga]) => {
                    Param.Body = Body;
                    const HomeLink = Title.$qa("a");
                    Param.ContentsPage = HomeLink[0].href;
                    Param.HomePage = HomeLink[1].href;
                    try {
                        const PageLink = Readend.$qa("ul a");
                        Param.PreviousLink = PageLink[0].href;
                        Param.NextLink = PageLink[2].href;
                    } catch {
                        const PageLink = HeadPager.$qa("a.mh_btn:not(.mh_bgcolor)");
                        Param.PreviousLink = PageLink[0].href;
                        Param.NextLink = PageLink[1].href;
                    }
                    Param.MangaList = Manga;
                    Param.BottomStrip = Readend.$q("a");
                    if ([Param.Body, Param.ContentsPage, Param.HomePage, Param.PreviousLink, Param.NextLink, Param.MangaList, Param.BottomStrip].every(Check => Check)) callback(true); else callback(false);
                });
            }
            BlockAds();
            try {
                Init(state => {
                    if (state) {
                        style.PictureStyle();
                        Config.BGColor.Enable && style.BackgroundStyle(Config.BGColor.Color);
                        Config.AutoTurnPage.Enable && turn.Auto(Config.AutoTurnPage.Mode);
                        Config.RegisterHotkey.Enable && HotkeySwitch(Config.RegisterHotkey.Function);
                    } else Syn.Log(null, "Error");
                });
            } catch (error) {
                Syn.Log(null, error);
            }
        })();
    })();
})();
长期地址
遇到问题?请前往 GitHub 提 Issues。