Navigational Keyboard Shortcuts

Navigate through websites using keyboard buttons N/B for next/previous pages.

As of 2019-08-06. See the latest version.

// ==UserScript==
// @name         Navigational Keyboard Shortcuts
// @namespace    https://github.com/kittenparry/
// @version      1.11.1
// @description  Navigate through websites using keyboard buttons N/B for next/previous pages.
// @author       kittenparry
// @match        *://*/*
// @grant        none
// @license      GPL-3.0-or-later
// ==/UserScript==

/* LIST:
 * metal-tracker.com
 * nexusmods.com
 * nyaa.si
 * rarbg.to || rarbgproxy.org
 * reddit.com
 * stargate.fandom.com
 * steamcommunity.com/workshop/
 * steamgifts.com
 * trakt.tv
 * tumblr.com
 * 
 * NSFW:
 * 8muses.com
 * camvault.xyz
 * camwhores.tv
 * chaturbate.com
 * coedcherry.com
 * erome.com
 * f95zone.com
 * hanime.tv
 * hentai-foundry.com
 * hongfire.com
 * javbus.com
 * meitulu.com
 * meituri.com
 * nhentai.net
 * nobodyhome.tv
 * planetsuzy.org
 * pornbay.org
 * recurbate.com
 * rec-tube.com
 * shadbase.com
 * sinnercomics.com
 * thothub.tv
 * yiff.party/activity
 */

/* CHANGELOG:
 * 1.11.1: fix trakt.tv back keybind not working
 * 1.11:   +stargate.fandom.com | navigates the episodes (preceded by & followed by)
 * 1.10:   +trakt.tv +camvault.xyz | trakt.tv only works in episode/season views
 * 1.9.2:  additional functions to ease repetition & meituri/meitulu isn't special anymore
 * 1.9.1:  ability to navigate to first/last pages on pornbay.org
 * 1.9:    +rec-tube.com
 * 1.8:    +erome.com +recurbate.com +hanime.tv
 * 1.7.1:  +nobodyhome.tv instead of nobodyhome.ga (domain change)
 * 1.7:    +planetsuzy.org
 * 1.6.1:  +rarbgproxy.org as an alternative to rarbg.to
 * 1.6:    +chaturbate.com
 * 1.5:    +meituri.com +meitulu.com | they work the same way so a simple or will do
 * 1.4:    +javbus.com | switch to semantic versioning so incrementing minor instead of patch part (https://semver.org/)
 * 1.3.7:  +thothub.tv
 * 1.3.6:  +nexusmods.com | a special case similar to camwhores.tv
 * 1.3.5:  +yiff.party/activity | with some clunky mechanics
 * 1.3.4:  +steamcommunity.com/workshop/
 * 1.3.3:  +f95zone.to/latest/ | change the original link to .to as well
 * 1.3.2:  +metal-tracker.com | btn case i wanted to use in camwhores.tv
 * 1.3.1:  +shadbase.com
 * 1.3:    +camwhores.tv | element needs to be reassigned each time so it's in the function
 * 1.2.4:  +8muses.com
 * 1.2.3:  +nobodyhome.ga
 * 1.2.2:  +hongfire.com
 * 1.2.1:  prevent execution of code when not on these sites
 * 1.2:    +f95zone.com
 * 1.1:    +nyaa.si +coedcherry.com
 * 1.0:    initial
 */

check_nav_key_press = (e, prev, next, special = '') => {
	var type = e.target.getAttribute('type');
	var tag = e.target.tagName.toLowerCase();
	if (type != 'text' && tag != 'textarea' && type != 'search') {
		if (e.keyCode == 66) {
			if (special == 'camwhores') {
				document.querySelector('li[class="page-current"]').previousElementSibling.firstElementChild.click();
			} else if (special == 'nexusmods') {
				document.querySelector('li[class="prev"]').firstElementChild.click();
			} else if (special == 'hanime') {
				sel_and_click('.pagination__navigation', 0);
			} else if (special == 'btn' && prev != undefined) {
				document.querySelector(prev).click();
			} else if (special == 'url' && prev != undefined) {
				window.location = prev;
			} else if (special == '') {
				window.location = get_query_href(prev);
			}
		} else if (e.keyCode == 78) {
			if (special == 'camwhores') {
				document.querySelector('li[class="page-current"]').nextElementSibling.firstElementChild.click();
			} else if (special == 'nexusmods') {
				document.querySelector('li[class="next"]').firstElementChild.click();
			} else if (special == 'hanime') {
				sel_and_click('.pagination__navigation', 3);
			} else if (special == 'btn' && next != undefined) {
				document.querySelector(next).click();
			} else if (special == 'url' && next != undefined) {
				window.location = next;
			} else if (special == '') {
				window.location = get_query_href(next);
			}
		}
	}
};

// return the first result of a given tag with the text
// eg. ('a', 'prev') returns the anchor with 'prev' innerHTML
find_els_with_text = (tag, text) => {
	var els = document.getElementsByTagName(tag);
	var found = [];
	for (var i = 0; i < els.length; i++) {
		if (els[i].innerHTML == text) {
			found.push(els[i]);
		}
	}

	return found[0].href;
};

// return the href of given selector
get_query_href = (sel) => {
	return document.querySelector(sel).href;
};

// click the idxth element of given selector
sel_and_click = (sel, idx) => {
	document.querySelectorAll(sel)[idx].click();
}

// return the href of given selector at idxth
get_sel_href = (sel, idx) => {
	return document.querySelectorAll(sel)[idx].href;
}

/* probably need a better way than simply .includes()
 * pqsel: previous query selector
 * nqsel: next query selector
 * nav_spcl: used when url is given instead of selector
 *	 check steamgifts example
 *	 also these include try/catch if they don't exist yet (first/last page)
 *	 !this section really looks ugly and repetitive
 */

var cur_loc = window.location.href;

if (cur_loc.includes('metal-tracker.com')) {
	var nav_spcl = 'btn';
	var pqsel = 'li[class="previous"]';
	var nqsel = 'li[class="next"]';
} else if (cur_loc.includes('nexusmods.com')) {
	var nav_spcl = 'nexusmods';
	var pqsel = '';
	var nqsel = '';
} else if (cur_loc.includes('nyaa.si')) {
	var pqsel = 'a[rel="prev"]';
	var nqsel = 'a[rel="next"]';
} else if (cur_loc.includes('rarbg.to') || cur_loc.includes('rarbgproxy.org')) {
	var pqsel = 'a[title="previous page"]';
	var nqsel = 'a[title="next page"]'
} else if (cur_loc.includes('reddit.com')) {
	var pqsel = 'a[rel="nofollow prev"]';
	var nqsel = 'a[rel="nofollow next"]';
} else if (cur_loc.includes('stargate.fandom.com')) {
	var nav_spcl = 'url';
	try {
		var pqsel = document.querySelector('div[data-source="preceded_by"]').lastElementChild.firstElementChild.href;
	} catch (e) {}
	try {
		var nqsel = document.querySelector('div[data-source="followed_by"]').lastElementChild.firstElementChild.href;
	} catch (e) {}
} else if (cur_loc.includes('steamcommunity.com/workshop/')) {
	var nav_spcl = 'url';
	try {
		var pqsel = get_sel_href('.pagebtn', 0);
	} catch (e) {}
	try {
		var nqsel = get_sel_href('.pagebtn', 1);
	} catch (e) {}
} else if (cur_loc.includes('steamgifts.com')) {
	var nav_spcl = 'url';
	try {
		var pqsel = document.querySelector('i[class="fa fa-angle-left"]').parentNode.href;
	} catch (e) {}
	try {
		var nqsel = document.querySelector('i[class="fa fa-angle-right"]').parentNode.href;
	} catch (e) {}
} else if (cur_loc.includes('trakt.tv')) {
	var pqsel = 'a[rel="prev"]';
	var nqsel = 'a[rel="next"]';
} else if (cur_loc.includes('tumblr.com')) {
	var pqsel = 'a[id="previous_page_link"]';
	var nqsel = 'a[id="next_page_link"]';
	/* * * * * * * *
	 * * * * * * * *
	 * NSFW BELOW  *
	 * * * * * * * *
	 * * * * * * * */
} else if (cur_loc.includes('8muses.com')) {
	var pqsel = 'a[class="pageNav-jump pageNav-jump--prev"]';
	var nqsel = 'a[class="pageNav-jump pageNav-jump--next"]';
} else if (cur_loc.includes('camvault.xyz')) {
	var pqsel = 'a[rel="prev"]';
	var nqsel = 'a[rel="next"]';
} else if (cur_loc.includes('camwhores.tv')) {
	var nav_spcl = 'camwhores';
	var pqsel = '';
	var nqsel = '';
} else if (cur_loc.includes('chaturbate.com')) {
	var pqsel = 'a[class="prev endless_page_link"]';
	var nqsel = 'a[class="next endless_page_link"]';
} else if (cur_loc.includes('coedcherry.com')) {
	var pqsel = 'a[rel="prev"]';
	var nqsel = 'a[rel="next"]';
} else if (cur_loc.includes('erome.com')) {
	var pqsel = 'a[rel="prev"]';
	var nqsel = 'a[rel="next"]';
} else if (cur_loc.includes('f95zone.to/latest/')) {
	// something else for "/pages/latest/"
	var pqsel = 'a[class="nav_prev"]';
	var nqsel = 'a[class="nav_next"]';
} else if (cur_loc.includes('f95zone.to')) {
	// for only threads/forums
	// something else is required for /pages/latest/
	var pqsel = 'a[class="pageNav-jump pageNav-jump--prev"]';
	var nqsel = 'a[class="pageNav-jump pageNav-jump--next"]';
} else if (cur_loc.includes('hanime.tv')) {
	var nav_spcl = 'hanime'
	var pqsel = '';
	var nqsel = '';
} else if (cur_loc.includes('hentai-foundry.com')) {
	var nav_spcl = 'url';
	try {
		var pqsel = document.querySelector('li[class="previous"]').firstChild.href;
	} catch (e) {}
	try {
		var nqsel = document.querySelector('li[class="next"]').firstChild.href;
	} catch (e) {}
} else if (cur_loc.includes('hongfire.com')) {
	var pqsel = 'a[class="js-pagenav-button js-pagenav-prev-button b-button b-button--secondary js-shrink-event-child"]';
	var nqsel = 'a[class="js-pagenav-button js-pagenav-next-button b-button b-button--secondary js-shrink-event-child"]';
} else if (cur_loc.includes('javbus.com')) {
	var pqsel = 'a[id="pre"]';
	var nqsel = 'a[id="next"]';
} else if (cur_loc.includes('meituri.com') || cur_loc.includes('meitulu.com')) {
	var nav_spcl = 'url';
	try {
		var pqsel = get_sel_href('.a1', 0);
	} catch (e) {}
	try {
		var nqsel = get_sel_href('.a1', 1);
	} catch (e) {}
} else if (cur_loc.includes('nhentai.net')) {
	var pqsel = 'a[class="previous"]';
	var nqsel = 'a[class="next"]';
} else if (cur_loc.includes('nobodyhome.tv')) {
	var pqsel = 'a[class="pagination_previous"]';
	var nqsel = 'a[class="pagination_next"]';
} else if (cur_loc.includes('planetsuzy.org')) {
	var pqsel = 'a[rel="prev"]';
	var nqsel = 'a[rel="next"]';
} else if (cur_loc.includes('pornbay.org')) {
	var nav_spcl = 'url';
	try {
		try {
			var pqsel = get_query_href('a[class="pager pager_prev"]');
		} catch (e) {
			var pqsel = get_query_href('a[class="pager pager_first"]');
		}
	} catch (e) {}
	try {
		try {
			var nqsel = get_query_href('a[class="pager pager_next"]');
		} catch (e) {
			var nqsel = get_query_href('a[class="pager pager_last"]');
		}
	} catch (e) {}
} else if (cur_loc.includes('recurbate.com')) {
	var pqsel = 'a[aria-label="Previous"]';
	var nqsel = 'a[aria-label="Next"]';
} else if (cur_loc.includes('rec-tube.com')) {
	var nav_spcl = 'url';
	try {
		try {
			var pqsel = find_els_with_text('a', 'Prev Page');
		} catch (e) {
			var pqsel = find_els_with_text('a', 'First Page')
		}
	} catch (e) {}
	try {
		try {
			var nqsel = find_els_with_text('a', 'Next Page');
		} catch (e) {
			var nqsel = find_els_with_text('a', 'Last Page')
		}
	} catch (e) {}
} else if (cur_loc.includes('shadbase.com')) {
	var pqsel = 'a[class="navi navi-prev"]';
	var nqsel = 'a[class="navi navi-next"]';
} else if (cur_loc.includes('sinnercomics.com')) {
	var pqsel = 'a[class="comic-nav-base comic-nav-previous"]';
	var nqsel = 'a[class="comic-nav-base comic-nav-next"]';
} else if (cur_loc.includes('thothub.tv')) {
	var pqsel = 'a[class="pageNav-jump pageNav-jump--prev"]';
	var nqsel = 'a[class="pageNav-jump pageNav-jump--next"]';
} else if (cur_loc.includes('yiff.party/activity')) {
	var nav_spcl = 'url';
	try {
		var pqsel = find_els_with_text('a', 'prev');
	} catch (e) {}
	try {
		var nqsel = find_els_with_text('a', 'next');
	} catch (e) {}
}

if (pqsel != undefined || nqsel != undefined) {
	try {
		if (nav_spcl) {
			window.addEventListener('keydown', (e) => check_nav_key_press(e, pqsel, nqsel, nav_spcl), false);
		} else {
			window.addEventListener('keydown', (e) => check_nav_key_press(e, pqsel, nqsel), false);
		}
	} catch (e) {}
}