Add keybinds for navigating galllery pages of boorus
// ==UserScript==
// @name booru keybinds
// @namespace 861ddd094884eac5bea7a3b12e074f34
// @version 3.0
// @description Add keybinds for navigating galllery pages of boorus
// @author Anonymous
// @match https://rule34.xxx/index.php?page=post&s=list*
// @include /^https:\/\/yande\.re\/post(\?page=[1-9]+)?([&?]tags=[^&]+)?/
// @include /^https:\/\/rule34\.us\/index\.php\?r=posts(%2F|\/)index.*/
// @match https://gelbooru.com/index.php?page=post&s=list*
// @include /^https:\/\/(www\.)?zerochan\.net\/([^?]+(\?q=[^&]+)?)?([?&]p=[1-9]+)?/
// @match https://anime-pictures.net/posts?page=*
// @include /^https:\/\/konachan\.net\/post(\?page=[1-9]+)?([&?]tags=[^&]+)?/
// @include /^https:\/\/kusowanka\.com\/(search\/\?[0-9a-zA-Z_=&%]+)?([?&]page=[1-9]+)?/
// @match https://tbib.org/index.php?page=post&s=list*
// @include /^https:\/\/[^.]+\.booru\.org\/index\.php\?page=post&s=list(&tags=[^&]+)?(&pid=[0-9]+)?
// @match https://safebooru.org/index.php?page=post&s=list*
// @match https://xbooru.com/index.php?page=post&s=list*
// @match https://realbooru.com/index.php?page=post&s=list*
// @include /^https:\/\/derpibooru.org\/(tags|search|images)?(\?page=[1-9]+)?(\?q=[^&]+)?/
// @include /^https:\/\/twibooru.org\/(tags|search|posts)?(\?page=[1-9]+)?(\?q=[^&]+)?/
// @include /^https:\/\/manebooru.art\/(tags|search|images)?(\?page=[1-9]+)?(\?q=[^&]+)?/
// @include /^https:\/\/browse\.minitokyo\.net\/gallery\?tid=[0-9]+&index=[0-9]+(&page=[1-9]+)?/
// @match https://inkbunny.net/submissionsviewall.php*
// @match https://hypnohub.net/index.php?page=post&s=list*
// @match https://booru.eu/post/list*
// @grant none
// @license MIT-0
// ==/UserScript==
(function() {
'use strict';
// CONFIGURATION
// for domains that use page numbers in an url parameter
// { domain: [ parameter, first page] }
const PAGE_DOMAINS = {
'yande.re': ['page', 1],
'rule34.us': ['page', 1],
'zerochan.net': ['p', 1],
'anime-pictures.net': ['page', 0],
'konachan.net': ['page', 1],
'kusowanka.com': ['page', 1],
'derpibooru.org': ['page', 1],
'twibooru.org': ['page', 1],
'manebooru.art': ['page', 1],
'minitokyo.net': ['page', 1],
'inkbunny.net': ['page', 1]
};
// for domains that put item count in an url parameter
// { domain: items per page }
const PID_DOMAINS = {
'rule34.xxx': 42,
'gelbooru.com': 42,
'tbib.org': 42,
'booru.org': 20,
'safebooru.org': 42,
'xbooru.com': 42,
'realbooru.com': 42,
'hypnohub.net': 42
};
const DEBUG = false;
// END CONFIGURATION
let PATH = window.location.pathname;
let PARAMS = new URLSearchParams(window.location.search);
const FQDN = window.location.hostname;
let DOMAIN = FQDN;
const domain_parts = FQDN.split('.');
if (domain_parts.length >= 3) {
DOMAIN = (
domain_parts[domain_parts.length - 2]
+ '.' + domain_parts[domain_parts.length - 1]
);
}
function detectPaginationType() {
if (PARAMS.has('s') || DOMAIN in PID_DOMAINS) {
return 'pid';
} else if (DOMAIN in PAGE_DOMAINS) {
return 'page';
} else if (PATH.startsWith('/post/list')) {
return 'path';
}
return null;
}
function getCurrentPage(type) {
let page;
if (!type) type = detectPaginationType();
if (type === 'pid') {
// assume parameter is named 'pid' and first item is 0
page = parseInt(
PARAMS.get(
type
)) || 0;
} else if (type === 'page') {
// special case for zerochan frontpage
if (
DOMAIN == 'www.zerochan.net'
&& PATH === '/'
&& !PARAMS.has(PAGE_DOMAINS[DOMAIN][0])
) {
page = 0;
} else {
// retrieve parameter name from configuration
// if no such param was passed, return known first page for domain
page = parseInt(
PARAMS.get(
PAGE_DOMAINS[DOMAIN][0]
)) || PAGE_DOMAINS[DOMAIN][1];
}
} else if (type === 'path') {
let path_parts = PATH.split('/');
if (path_parts.length >= 4) {
page = parseInt(
path_parts[path_parts.length - 1]
) || 1;
} else {
page = 1;
}
}
return page;
}
function calculatePreviousPage(type, current, pageLength) {
if (type === 'pid') {
if (current > 0) {
return Math.max(0, current - pageLength);
} else {
if (DEBUG) console.log(`(current=${current}) <= 0`);
return false;
}
} else if (type === 'page') {
if (current > PAGE_DOMAINS[DOMAIN][1]) {
return (current - 1);
} else {
if (DEBUG) console.log(`(current=${current}) <= (PAGE_DOMAINS[DOMAIN][1]=${PAGE_DOMAINS[DOMAIN][1]})`);
return false;
}
} else if (type === 'path') {
if (current > 1) {
return (current - 1);
} else {
if (DEBUG) console.log(`(current=${current}) <= 1`);
return false;
}
}
}
function calculateNextPage(type, current, pageLength) {
if (type === 'pid') {
return (current + pageLength);
} else if (type === 'page' || type === 'path') {
return (current + 1);
}
}
function reformatUri() {
const borSites = ['derpibooru.org', 'twibooru.org', 'manebooru.art'];
if (borSites.indexOf(DOMAIN) > -1 && PATH === '/') {
if (DOMAIN == 'twibooru.org') {
PATH = '/posts';
} else {
PATH = '/images';
}
}
if (PARAMS.length) {
return `${PATH}?${PARAMS.toString()}`;
} else {
return PATH;
}
}
function navigate(direction) {
PARAMS = new URLSearchParams(window.location.search);
const type = detectPaginationType();
let current = null;
current = getCurrentPage(type);
let pageLength = null;
if (type === 'pid') pageLength = PID_DOMAINS[DOMAIN];
let dest = null;
if (direction === 'previous') {
dest = calculatePreviousPage(type, current, pageLength);
} else if (direction === 'next') {
dest = calculateNextPage(type, current, pageLength);
}
if (dest === false || dest === null) return false;
if (type === 'pid') {
// assume parameter is named 'pid'
PARAMS.set(type, dest);
} else if (type === 'page') {
PARAMS.set(PAGE_DOMAINS[DOMAIN][0], dest);
} else if (type === 'path') {
let path_parts = PATH.split('/');
if (path_parts.length >= 4) path_parts.pop();
path_parts.push(dest.toString());
PATH = path_parts.join('/');
}
window.location.href = reformatUri()
if (DEBUG) console.debug(`Keybind caught: direction=${direction}, type=${type}, current=${current}, pageLength=${pageLength}`);
}
// Only activate if pagination is detected
const type = detectPaginationType();
if (!type) return false;
document.addEventListener('keydown', function(e) {
// Ignore if user is typing in an input field
if (
e.target.tagName === 'INPUT'
|| e.target.tagName === 'TEXTAREA'
|| e.target.getAttribute('role') == 'search'
) return;
switch(e.key) {
case 'ArrowLeft':
e.preventDefault();
navigate('previous');
break;
case 'ArrowRight':
e.preventDefault();
navigate('next');
break;
}
});
console.log(`Keybinds loaded (${type} mode). Use ← → arrow keys to navigate gallery pages.`);
})();