// ==UserScript== // @name ColaManga 瀏覽增強 // @name:zh-TW ColaManga 瀏覽增強 // @name:zh-CN ColaManga 浏览增强 // @name:en ColaManga Browsing Enhancement // @version 0.0.10 // @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 MIT // @namespace https://greasyfork.org/users/989635 // @run-at document-start // @grant GM_setValue // @grant GM_getValue // @require https://update.greasyfork.org/scripts/487608/1382007/ClassSyntax_min.js // @downloadURL none // ==/UserScript== (function () { /* 設置是否使用該功能, 沒有設定參數, 這只是臨時的寫法, 之後會刪除掉 (0 = 不使用 | 1 = 使用 | mode = 有些有不同模式 2..3..n) */ const Config = { BGColor: 1, // 背景換色 [目前還沒有自訂], 改代碼可搜索 #595959, 並修改該字串 RegisterHotkey: 3, // 快捷功能 mode: 1 = 翻頁, 2 = 翻頁+滾動, 3 翻頁+滾動+換頁繼續滾動 AutoTurnPage: 4 // 自動換頁 mode: 1 = 快速, 2 = 普通, 3 = 緩慢, 4 = 無盡 (實驗中的酷東西) }; new class Manga extends Syntax { constructor() { super(); this.ScrollPixels = 2; this.WaitPicture = 1e3; this.JumpTrigger = false; this.AdCleanup = this.Body = null; this.ContentsPage = this.HomePage = null; this.PreviousPage = this.NextPage = null; this.MangaList = this.BottomStrip = null; this.Up_scroll = this.Down_scroll = false; this.Observer_Next = null; this.IsMainPage = window.self === window.parent; this.BlockListener = new Set(["pointerup", "pointerdown", "dState", "touchstart", "unhandledrejection"]); this.Get_Data = async callback => { this.WaitMap(["body", "div.mh_readtitle", "div.mh_headpager", "div.mh_readend", "#mangalist"], element => { const [Body, Title, HeadPager, Readend, Manga] = element; this.Body = Body; const HomeLink = this.$$("a", { all: true, root: Title }); this.ContentsPage = HomeLink[0].href; this.HomePage = HomeLink[1].href; try { const PageLink = this.$$("ul a", { all: true, root: Readend }); this.PreviousPage = PageLink[0].href; this.NextPage = PageLink[2].href; } catch { const PageLink = this.$$("a.mh_btn:not(.mh_bgcolor)", { all: true, root: HeadPager }); this.PreviousPage = PageLink[0].href; this.NextPage = PageLink[1].href; } this.MangaList = Manga; this.BottomStrip = this.$$("a", { root: Readend }); if ([this.Body, this.ContentsPage, this.HomePage, this.PreviousPage, this.NextPage, this.MangaList, this.BottomStrip].every(Check => Check)) callback(true); else callback(false); }, { timeout: 10, timeoutResult: true, object: document }); }; this.storage = (key, value = null) => { return value != null ? this.Storage(key, { value: value }) : this.Storage(key); }; this.TopDetected = this.Throttle(() => { this.Up_scroll = this.Device.sY() == 0 ? (this.storage("scroll", false), false) : true; }, 1e3); this.BottomDetected = this.Throttle(() => { this.Down_scroll = this.Device.sY() + this.Device.iH() >= document.documentElement.scrollHeight ? (this.storage("scroll", false), false) : true; }, 1e3); this.scroll = move => { requestAnimationFrame(() => { if (this.Up_scroll && move < 0) { window.scrollBy(0, move); this.TopDetected(); this.scroll(move); } else if (this.Down_scroll && move > 0) { window.scrollBy(0, move); this.BottomDetected(); this.scroll(move); } }); }; this.FinalPage = link => link.startsWith("javascript"); this.VisibleObjects = object => object.filter(img => img.height > 0 || img.src); this.ObserveObject = object => object[Math.max(object.length - 2, 0)]; this.DetectionValue = object => this.VisibleObjects(object).length >= Math.floor(object.length * .5); this.Get_Style = () => { const Style = this.Store("g", "Style") || [{ BG_Color: "#595959", Img_Bw: "auto", Img_Mw: "100%" }]; return Style[0]; }; this.ImgStyle = this.Get_Style(); } async BlockAds() { const OriginListener = EventTarget.prototype.addEventListener, Block = this.BlockListener; const EventListeners = new Map(); EventTarget.prototype.addEventListener = function (type, listener, options) { if (Block.has(type)) return; if (!EventListeners.has(this)) EventListeners.set(this, []); EventListeners.get(this).push({ type: type, listener: listener, options: options }); OriginListener.call(this, type, listener, options); }; function removeBlockedListeners() { EventListeners.forEach((listeners, element) => { listeners.forEach(({ type, listener }) => { if (Block.has(type)) { element.removeEventListener(type, listener); } }); }); } this.AddStyle(` html {pointer-events: none !important;} .mh_wrap, span.mh_btn:not(.contact), #Iframe-Comics {pointer-events: auto;} `, "Inject-Blocking-Ads"); this.AdCleanup = setInterval(() => { this.$$("iframe:not(#Iframe-Comics)")?.remove(); removeBlockedListeners(); }, 500); } async BackgroundStyle() { this.Body.style.backgroundColor = this.ImgStyle.BG_Color; } async AutoReload() { let 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 }); this.$$("span.mh_btn:not(.contact)", { all: true, root: this.MangaList }).forEach(b => { observer.observe(b); }); } async PictureStyle() { if (this.Device.Type() == "Desktop") { this.AddStyle(` .mh_comicpic img { vertical-align: top;cursor: pointer;display: block;margin: auto; width: ${this.ImgStyle.Img_Bw}; max-width: ${this.ImgStyle.Img_Mw}; } `, "Inject-Image-Style"); } setTimeout(() => { this.AutoReload(); }, this.WaitPicture); } async HotkeySwitch(mode) { if (this.Device.Type() == "Desktop") { if (mode == 3 && this.IsMainPage) { this.Down_scroll = this.storage("scroll"); this.Down_scroll && this.scroll(this.ScrollPixels); } const UP_ScrollSpeed = this.ScrollPixels * -1; this.Listen(window, "keydown", event => { const key = event.key; if (key == "ArrowLeft" && !this.JumpTrigger) { event.stopImmediatePropagation(); this.JumpTrigger = !this.FinalPage(this.PreviousPage) ? true : false; location.assign(this.PreviousPage); } else if (key == "ArrowRight" && !this.JumpTrigger) { event.stopImmediatePropagation(); this.JumpTrigger = !this.FinalPage(this.NextPage) ? true : false; location.assign(this.NextPage); } else if (key == "ArrowUp" && mode >= 2) { event.stopImmediatePropagation(); event.preventDefault(); if (this.Up_scroll) { this.Up_scroll = false; } else if (!this.Up_scroll || this.Down_scroll) { this.Down_scroll = false; this.Up_scroll = true; this.scroll(UP_ScrollSpeed); } } else if (key == "ArrowDown" && mode >= 2) { event.stopImmediatePropagation(); event.preventDefault(); if (this.Down_scroll) { this.Down_scroll = false; this.storage("scroll", false); } else if (this.Up_scroll || !this.Down_scroll) { this.Up_scroll = false; this.Down_scroll = true; this.storage("scroll", true); this.scroll(this.ScrollPixels); } } }, { capture: true }); } else if (this.Device.Type() == "Mobile") { const sidelineX = .35 * this.Device.iW(), sidelineY = this.Device.iH() / 4 * .2; let startX, startY, moveX, moveY; this.Listen(window, "touchstart", event => { startX = event.touches[0].clientX; startY = event.touches[0].clientY; }, { passive: true }); this.Listen(window, "touchmove", this.Throttle(event => { requestAnimationFrame(() => { moveX = event.touches[0].clientX - startX; moveY = event.touches[0].clientY - startY; if (Math.abs(moveY) < sidelineY) { if (moveX > sidelineX && !this.JumpTrigger) { this.JumpTrigger = !this.FinalPage(this.PreviousPage) ? true : false; location.assign(this.PreviousPage); } else if (moveX < -sidelineX && !this.JumpTrigger) { this.JumpTrigger = !this.FinalPage(this.NextPage) ? true : false; location.assign(this.NextPage); } } }); }, 200), { passive: true }); } } async AutoPageTurn(mode) { let self = this, hold, object, img; self.Observer_Next = new IntersectionObserver(observed => { observed.forEach(entry => { if (entry.isIntersecting && self.DetectionValue(img)) { self.Observer_Next.disconnect(); !self.FinalPage(self.NextPage) && location.assign(self.NextPage); } }); }, { threshold: hold }); switch (mode) { case 2: hold = .5; object = self.$$("li:nth-child(3) a.read_page_link"); break; case 3: hold = 1; object = self.$$("div.endtip2.clear"); break; case 4: this.UnlimitedPageTurn(); break; default: hold = .1; object = self.BottomStrip; } if (mode != 4) { setTimeout(() => { img = self.$$("img", { all: true, root: self.MangaList }); self.Observer_Next.observe(object); }, self.WaitPicture); } } async UnlimitedPageTurn() { const self = this; const iframe = document.createElement("iframe"); iframe.id = "Iframe-Comics"; iframe.src = self.NextPage; this.AddStyle(` .mh_wrap, .mh_readend, .mh_footpager, .fed-foot-info, #imgvalidation2022 {display: none;} #Iframe-Comics {height:0px; border: none; width: 100%;} `, "scroll-hidden"); if (this.IsMainPage) { this.Listen(window, "message", event => { const data = event.data; document.title = data[0]; history.pushState(null, null, data[1]); }); } else { let MainWindow = window; this.Listen(window, "message", event => { while (MainWindow.parent !== MainWindow) { MainWindow = MainWindow.parent; } MainWindow.postMessage(event.data, self.Origin); }); } TriggerNextPage(); async function TriggerNextPage() { let Img, Observer, Quantity = 0; self.Observer_Next = new IntersectionObserver(observed => { observed.forEach(entry => { if (entry.isIntersecting && self.DetectionValue(Img)) { self.Observer_Next.disconnect(); Observer.disconnect(); TurnPage(); } }); }, { threshold: .1 }); setTimeout(() => { Img = self.$$("img", { all: true, root: self.MangaList }); if (Img.length <= 3) { TurnPage(); return; } self.Observer_Next.observe(self.ObserveObject(self.VisibleObjects(Img))); self.Observer(self.MangaList, () => { const Visible = self.VisibleObjects(Img), VL = Visible.length; if (VL > Quantity) { Quantity = VL; self.Observer_Next.disconnect(); self.Observer_Next.observe(self.ObserveObject(Visible)); } }, { throttle: 100 }, observer => { Observer = observer.ob; }); }, self.WaitPicture); } async function TurnPage() { let URL, Content, StylelRules = self.$$("#scroll-hidden").sheet.cssRules; if (self.FinalPage(self.NextPage)) { StylelRules[0].style.display = "block"; return; } document.body.appendChild(iframe); self.Log("無盡翻頁", self.NextPage); Waitload(); function Waitload() { iframe.onload = () => { URL = iframe.contentWindow.location.href; URL != self.NextPage && (iframe.src = self.NextPage, Waitload()); Content = iframe.contentWindow.document; Content.body.style.overflow = "hidden"; setInterval(() => { StylelRules[1].style.height = `${Content.body.scrollHeight}px`; }, 1500); const UrlUpdate = new IntersectionObserver(observed => { observed.forEach(entry => { if (entry.isIntersecting) { UrlUpdate.disconnect(); window.parent.postMessage([Content.title, URL], self.Origin); } }); }, { threshold: .1 }); UrlUpdate.observe(self.$$("#mangalist img", { root: Content })); }; iframe.onerror = () => { iframe.src = self.NextPage; Waitload(); }; } } } async Injection() { this.BlockAds(); try { this.Get_Data(state => { if (state) { Config.BGColor > 0 && this.BackgroundStyle(); this.PictureStyle(); Config.RegisterHotkey > 0 && this.HotkeySwitch(Config.RegisterHotkey); Config.AutoTurnPage > 0 && this.AutoPageTurn(Config.AutoTurnPage); } else this.Log(null, "Error"); }); } catch (error) { this.Log(null, error); } } }().Injection(); })();