NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Arrow Keys: Next/Prev Chapter // @namespace https://github.com/Astropilot // @version 0.6.0 // @description Arrow Key Keyboard shortcuts for multiple manga reader websites (next/prev chapter) // @author Astropilot // @license MIT // @homepage https://github.com/Astropilot/webtoon_userscripts // @homepageURL https://github.com/Astropilot/webtoon_userscripts // @supportURL https://github.com/Astropilot/webtoon_userscripts/issues // @downloadURL https://raw.githubusercontent.com/Astropilot/webtoon_userscripts/main/src/arrow_keys.user.js // @run-at document-end // @grant none // @noframes // @match *://*.manga-scans.com/chapter/* // @match *://*.reaperscans.com/comics/*/chapters/* // @match *://*.webtoons.com/*/viewer*episode_no=* // @match *://*.xcalibrscans.com/*-chapter-* // @match *://*.mangakakalot.com/chapter/* // @match *://*.chapmanganato.to/manga-*/chapter-* // @match *://*.scyllacomics.xyz/manga/*/* // @match *://*.mangagalaxy.org/series/*/chapter-* // @require https://cdn.jsdelivr.net/npm/psl@1.9.0/dist/psl.min.js#sha256-pGXYc481WIYNZUsKubKxCxQUydhNrlM5S8g5eMU8fdw= // ==/UserScript== (function () { "use strict"; // Selectors should point to link (<a href/>) or <button> that redirect to prev/next chapter. const navigationSelectorsPerDomains = [ { hosts: ["xcalibrscans.com"], selectors: { prev: "a.ch-prev-btn", next: "a.ch-next-btn" } }, { hosts: ["manga-scans.com"], selectors: { prev: "div.prev-post > a", next: "div.next-post > a" } }, { hosts: ["reaperscans.com"], selectors: { prev: "main nav:nth-of-type(1) div.flex:nth-of-type(1) > a", next: "main nav:nth-of-type(1) div.flex:nth-of-type(3) > a:nth-of-type(2)" }, }, { hosts: ["webtoons.com"], selectors: { prev: "a._prevEpisode", next: "a._nextEpisode" } }, { hosts: ["mangakakalot.com"], selectors: { prev: ".btn-navigation-chap > a.next", // Not an error, next/back classes are really reversed... next: ".btn-navigation-chap > a.back" } }, { hosts: ["chapmanganato.to"], selectors: { prev: ".navi-change-chapter-btn > a.navi-change-chapter-btn-prev", next: ".navi-change-chapter-btn > a.navi-change-chapter-btn-next" } }, { hosts: ["scyllacomics.xyz"], selectors: { prev: "main > section > div.relative > div.flex > div.grid > a:nth-of-type(1)", next: "main > section > div.relative > div.flex > div.grid > a:nth-of-type(2)" } }, { hosts: ["mangagalaxy.org"], selectors: { prev: "a[aria-label='Prev']", next: "a[aria-label='Next']" } }, ]; // We extract top domain from hostname without subdomains const currentDomain = psl.get(window.location.hostname); if (currentDomain === null || currentDomain.length === 0) { console.warn("[Arrow Keys UserScript] Failed to parse current domain!"); return; } let rule = null; for (const domainsRule of navigationSelectorsPerDomains) { if (domainsRule.hosts.includes(currentDomain)) { rule = domainsRule; break; } } if (rule === null) { console.warn("[Arrow Keys UserScript] Failed to find selectors rule!"); return; } document.addEventListener("keyup", (event) => { if (event.target && (event.target.matches("input") || event.target.matches("textarea") || event.target.isContentEditable)) { // Do nothing when inside a text/editable field. return; } if (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey) { // Do nothing when modifiers are held return; } if (event.code === "ArrowLeft") { document.querySelector(rule.selectors.prev)?.click(); } else if (event.code === "ArrowRight") { document.querySelector(rule.selectors.next)?.click(); } }); })();