// ==UserScript==
// @name ColaManga 瀏覽增強
// @name:zh-TW ColaManga 瀏覽增強
// @name:zh-CN ColaManga 浏览增强
// @name:en ColaManga Browsing Enhancement
// @version 0.0.11-Beta4
// @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/1456525/ClassSyntax_min.js
// ==/UserScript==
(async () => {
/* 臨時的自定義 (當 Enable = false 時, 其餘的設置將無效) */
const Config = {
BGColor: {
Enable: true,
Color: "#595959",
},
AutoTurnPage: { // 自動翻頁
Enable: true,
Mode: 5, // 1 = 快速 | 2 = 普通 | 3 = 緩慢 | 4 = 一般無盡 | 5 = 優化無盡
},
RegisterHotkey: { // 快捷功能
Enable: true,
Function: { // 移動端不適用以下配置
TurnPage: true, // 翻頁
AutoScroll: true, // 自動滾動
KeepScroll: true, // 換頁繼續滾動
ManualScroll: false, // 手動滾動啟用時, 將會變成點擊一次, 根據視點翻一頁 且 自動滾動會無效
}
}
};
new class Manga extends Syntax {
constructor() {
super();
this.ScrollPixels = 2;
this.WaitPicture = 1e3;
this.IsFinalPage = 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.Id = {
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"
};
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.Get_Nodes = Root => {
const nodes = [];
const IdWhiteList = new Set(Object.values(this.Id));
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;
};
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.AutoScroll = move => {
requestAnimationFrame(() => {
if (this.Up_scroll && move < 0) {
window.scrollBy(0, move);
this.TopDetected();
this.AutoScroll(move);
} else if (this.Down_scroll && move > 0) {
window.scrollBy(0, move);
this.BottomDetected();
this.AutoScroll(move);
}
});
};
this.ManualScroll = move => {
window.scrollBy({
left: 0,
top: move,
behavior: "smooth"
});
};
this.FinalPage = link => {
this.IsFinalPage = link.startsWith("javascript");
return this.IsFinalPage;
};
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), ${this.Id.Iframe} {pointer-events: auto;}
`, this.Id.Block);
this.AdCleanup = setInterval(() => {
this.$$(`iframe:not(#${this.Id.Iframe})`)?.remove();
removeBlockedListeners();
}, 500);
}
async BackgroundStyle(Color) {
this.Body.style.cssText = `
background: ${Color} !important;
`;
document.documentElement.style.cssText = `
overflow: visible !important;
`;
}
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):not(.read_page_link)", {
all: true,
root: this.MangaList
}).forEach(reloadBtn => observer.observe(reloadBtn));
}
async PictureStyle() {
if (this.Device.Type() == "Desktop") {
this.AddStyle(`
.mh_comicpic img {
margin: auto;
display: block;
cursor: pointer;
vertical-align: top;
width: ${this.ImgStyle.Img_Bw};
max-width: ${this.ImgStyle.Img_Mw};
}
`, this.Id.Image);
}
setTimeout(() => {
this.AutoReload();
}, this.WaitPicture);
}
async HotkeySwitch(Use) {
let JumpState = false;
if (this.Device.Type() == "Desktop") {
if (this.IsMainPage && Use.KeepScroll && Use.AutoScroll && !Use.ManualScroll) {
this.Down_scroll = this.storage("scroll");
this.Down_scroll && this.AutoScroll(this.ScrollPixels);
}
const UP_ScrollSpeed = -this.ScrollPixels;
const CanScroll = Use.AutoScroll || Use.ManualScroll;
this.AddListener(window, "keydown", event => {
const key = event.key;
if (key == "ArrowLeft" && Use.TurnPage && !JumpState) {
event.stopImmediatePropagation();
JumpState = !this.FinalPage(this.PreviousPage);
location.assign(this.PreviousPage);
} else if (key == "ArrowRight" && Use.TurnPage && !JumpState) {
event.stopImmediatePropagation();
JumpState = !this.FinalPage(this.NextPage);
location.assign(this.NextPage);
} else if (key == "ArrowUp" && CanScroll) {
event.stopImmediatePropagation();
event.preventDefault();
if (Use.ManualScroll) {
this.ManualScroll(-this.Device.iH());
} else {
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.AutoScroll(UP_ScrollSpeed);
}
}
} else if (key == "ArrowDown" && CanScroll) {
event.stopImmediatePropagation();
event.preventDefault();
if (Use.ManualScroll) {
this.ManualScroll(this.Device.iH());
} else {
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.AutoScroll(this.ScrollPixels);
}
}
}
}, {
capture: true
});
} else if (this.Device.Type() == "Mobile") {
let startX, startY, moveX, moveY;
const sidelineX = this.Device.iW() * .3;
const sidelineY = this.Device.iH() / 4 * .3;
this.AddListener(window, "touchstart", event => {
startX = event.touches[0].clientX;
startY = event.touches[0].clientY;
}, {
passive: true
});
this.AddListener(window, "touchmove", this.Debounce(event => {
moveY = event.touches[0].clientY - startY;
if (Math.abs(moveY) < sidelineY) {
moveX = event.touches[0].clientX - startX;
if (moveX > sidelineX && !JumpState) {
JumpState = !this.FinalPage(this.PreviousPage);
location.assign(this.PreviousPage);
} else if (moveX < -sidelineX && !JumpState) {
JumpState = !this.FinalPage(this.NextPage);
location.assign(this.NextPage);
}
}
}, 60), {
passive: true
});
}
}
async AutoPageTurn(Mode) {
let self = this, hold, point, 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;
point = self.$$("li:nth-child(3) a.read_page_link");
break;
case 3:
hold = 1;
point = self.$$("div.endtip2.clear");
break;
case 4:
case 5:
this.UnlimitedPageTurn(Mode == 5);
break;
default:
hold = .1;
point = self.BottomStrip;
}
if (point) {
setTimeout(() => {
img = self.$$("img", {
all: true,
root: self.MangaList
});
self.Observer_Next.observe(point);
}, self.WaitPicture);
}
}
async UnlimitedPageTurn(Optimized) {
const self = this;
const iframe = document.createElement("iframe");
iframe.id = this.Id.Iframe;
iframe.src = self.NextPage;
this.AddStyle(`
.mh_wrap, .mh_readend, .mh_footpager,
.fed-foot-info, #imgvalidation2022 {display: none;}
body {
margin: 0;
padding: 0;
}
#${this.Id.Iframe} {
margin: 0;
padding: 0;
height: 110vh;
width: 100%;
border: none;
}
`, this.Id.Scroll);
if (this.IsMainPage) {
this.Listen(window, "message", event => {
const data = event.data;
if (data && data.length > 0) {
document.title = data[0];
this.NextPage = data[3];
this.PreviousPage = data[1];
history.pushState(null, null, data[2]);
}
});
} else {
this.AddStyle(`
html {
overflow: hidden !important;
overflow-x: hidden !important;
scrollbar-width: none !important;
-ms-overflow-style: none !important;
}
html::-webkit-scrollbar {
display: none !important;
}
`, this.Id.ChildS);
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 <= 5) {
TurnPage();
return;
}
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: 150
}, observer => {
Observer = observer.ob;
});
}, self.WaitPicture);
}
async function TurnPage() {
let Content, CurrentUrl, StylelRules = self.$$(`#${self.Id.Scroll}`).sheet.cssRules;
if (self.FinalPage(self.NextPage)) {
StylelRules[0].style.display = "block";
return;
}
document.body.appendChild(iframe);
Waitload();
function Waitload() {
iframe.onload = () => {
CurrentUrl = iframe.contentWindow.location.href;
CurrentUrl != self.NextPage && (iframe.src = self.NextPage,
Waitload());
Content = iframe.contentWindow.document;
Content.body.style.overflow = "hidden";
const TopImg = self.$$("#mangalist img", {
root: Content
});
setInterval(() => {
StylelRules[2].style.height = `${Content.body.scrollHeight}px`;
}, 1500);
const UrlUpdate = new IntersectionObserver(observed => {
observed.forEach(entry => {
if (entry.isIntersecting) {
UrlUpdate.disconnect();
self.Log("無盡翻頁", CurrentUrl);
const PageLink = self.$$("div.mh_readend ul a", {
all: true,
root: Content.body
});
const PreviousUrl = PageLink[0]?.href;
const NextUrl = PageLink[2]?.href;
window.parent.postMessage([Content.title, PreviousUrl, CurrentUrl, NextUrl], self.Origin);
}
});
}, {
threshold: .1
});
UrlUpdate.observe(TopImg);
if (Optimized) {
self.$$("title").id = self.Id.Title;
const adapt = self.Device.Type() == "Desktop" ? .5 : .7;
const hold = Math.min(adapt, self.Device.iH() * adapt / TopImg.clientHeight);
const ReleaseMemory = new IntersectionObserver(observed => {
observed.forEach(entry => {
if (entry.isIntersecting) {
ReleaseMemory.disconnect();
self.Get_Nodes(document).forEach(node => {
node.remove();
});
TopImg.scrollIntoView();
}
});
}, {
threshold: hold
});
ReleaseMemory.observe(TopImg);
}
};
iframe.onerror = () => {
iframe.src = self.NextPage;
Waitload();
};
}
}
}
async Injection() {
this.BlockAds();
try {
this.Get_Data(state => {
if (state) {
this.PictureStyle();
Config.BGColor.Enable && this.BackgroundStyle(Config.BGColor.Color);
Config.AutoTurnPage.Enable && this.AutoPageTurn(Config.AutoTurnPage.Mode);
Config.RegisterHotkey.Enable && this.HotkeySwitch(Config.RegisterHotkey.Function);
} else this.Log(null, "Error");
});
} catch (error) {
this.Log(null, error);
}
}
}().Injection();
})();