Click on thumbnails to view full-size images/videos with favorite, pool, and navigation
// ==UserScript==
// @name Gelbooru Enhanced Experience
// @namespace http://tampermonkey.net/
// @version 11.0
// @description Click on thumbnails to view full-size images/videos with favorite, pool, and navigation
// @author kanrau
// @match https://gelbooru.com/*
// @match http://gelbooru.com/*
// @grant none
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// ============ CONFIGURATION ============
const CONFIG = {
POOLS: [
{ id: '69275', name: 'colored likes' },
{ id: '69675', name: 'Mehbooba' },
{ id: '69875', name: 'Butter' },
{ id: '70030', name: 'hinata' },
{ id: '70031', name: 'tatsumaki' }
],// change with your fav pools' id
PRELOAD_COUNT: 3,
MESSAGE_DURATION: 3000,
TOAST_DURATION: 2000,
POSTS_PER_PAGE: { favorites: 50, pool: 45, regular: 42 }
};
const COLORS = {
bg: '#212121',
bgLight: '#2f2f2f',
bgDark: '#1a1a1a',
text: '#c1c1c1',
darktext: '#000000',
link: '#4c98e7',
linkHover: '#6db3f2',
artist: '#f2a0a0',
border: '#3a3a3a',
success: 'rgba(76,175,80,. 9)',
error: 'rgba(244,67,54,. 9)'
};
const STYLES = {
link: `color: ${COLORS.link};text-decoration: none;font: 12px Verdana,sans-serif;cursor:pointer`,
artist: `color:${COLORS.artist};text-decoration:none;font:12px Verdana,sans-serif;cursor: pointer`,
separator: `color:${COLORS.text};font:12px Verdana,sans-serif`,
overlay: `display:none;position: fixed;inset:0;background:rgba(0,0,0,. 85);z-index:999999;cursor:pointer;justify-content:center;align-items: center`,
media: 'max-width:85vw;max-height:85vh;display:none',
arrow: `position:fixed;top:50%;transform:translateY(-50%);font-size:60px;color:${COLORS.text};cursor:pointer;user-select:none;padding:20px;opacity:. 7;transition:opacity . 3s,transform .3s`,
dropdown: `display:none;position:absolute;top:100%;left:0;background:${COLORS.bgLight};border:1px solid ${COLORS. border};z-index:1000002;min-width:150px;text-align:left;margin-top:2px;border-radius:3px`,
dropdownItem: `display:block;padding: 8px 12px;color:${COLORS.link};font:12px Verdana,sans-serif;text-decoration:none;border-bottom:1px solid ${COLORS. border}`,
topBar: `position:fixed;top: 15px;left:50%;transform:translateX(-50%);display:flex;gap: 5px;align-items:center;z-index:1000000;background:rgba(26,26,26,0.85);padding:8px 15px;border-radius:3px;border:1px solid ${COLORS.border}`
};
// ============ STATE ============
const state = {
currentIndex: -1,
thumbnails: [],
cache: {},
overlayVisible: false,
suspendMain: false
};
const elements = {};
// ============ UTILITIES ============
const utils = {
isVideoFocused: () => document.activeElement === elements.video,
isInputFocused: () => ['INPUT', 'TEXTAREA'].includes(document.activeElement?. tagName),
getPostUrl: (id) => `https://gelbooru.com/index.php?page=post&s=view&id=${id}`,
createElement(tag, styles, props = {}) {
const el = document.createElement(tag);
if (styles) el.style.cssText = styles;
Object.assign(el, props);
return el;
},
addHoverUnderline(el) {
el.onmouseenter = () => { el.style.textDecoration = 'underline'; el.style.color = COLORS.linkHover; };
el.onmouseleave = () => { el.style.textDecoration = 'none'; el.style.color = COLORS.link; };
},
addHoverUnderlineArtist(el) {
el.onmouseenter = () => el.style.textDecoration = 'underline';
el.onmouseleave = () => el.style.textDecoration = 'none';
},
addHoverScale(el) {
el. onmouseenter = () => { el.style.opacity = '1'; el.style.transform = 'translateY(-50%) scale(1.2)'; };
el.onmouseleave = () => { el.style. opacity = '. 7'; el.style.transform = 'translateY(-50%)'; };
}
};
// ============ API ============
const api = {
async fetchPostData(postId) {
try {
const response = await fetch(utils.getPostUrl(postId), { credentials: 'include' });
if (!response.ok) return null;
const html = await response.text();
if (html.includes('not available in your country')) return null;
const doc = new DOMParser().parseFromString(html, 'text/html');
let mediaUrl = null, isVideo = false;
const img = doc.querySelector('#image');
if (img?. src) {
mediaUrl = img. getAttribute('src');
} else {
const video = doc. querySelector('video#gelcomVideoPlayer source');
if (video?. src) {
mediaUrl = video.getAttribute('src');
isVideo = true;
}
}
if (! mediaUrl) {
const match = html.match(/https?:\/\/[^"'\s]+\. gelbooru\.com\/images\/[^"'\s]+\.(jpg|jpeg|png|gif|webp|mp4|webm)/i);
if (match) {
mediaUrl = match[0];
isVideo = /\.(mp4|webm)$/i.test(mediaUrl);
}
}
const artistTag = doc.querySelector('li.tag-type-artist a[href*="tags="]');
let artistName = null, artistUrl = null;
if (artistTag) {
artistName = artistTag. textContent.trim();
artistUrl = artistTag.getAttribute('href');
if (artistUrl && ! artistUrl.startsWith('http')) {
artistUrl = 'https://gelbooru.com/' + artistUrl;
}
}
return { mediaUrl, isVideo, artistName, artistUrl };
} catch (e) {
console.error('Fetch error:', e);
return null;
}
},
async addToFavorite(postId) {
try {
await fetch(`https://gelbooru.com/index. php?page=post&s=vote&id=${postId}&type=up`, { credentials: 'include' });
const res = await fetch(`https://gelbooru.com/public/addfav.php?id=${postId}`, { credentials: 'include' });
return res.ok;
} catch { return false; }
},
async addToPool(postId, poolId) {
try {
const body = new URLSearchParams({ id: poolId, commit: 'import', [`posts[${postId}]`]: '1' });
const res = await fetch(`https://gelbooru.com/index.php?page=pool&s=import&id=${poolId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body,
credentials: 'include'
});
return res.ok;
} catch { return false; }
},
async removeFromFavorite(postId) {
try {
const res = await fetch(`https://gelbooru.com/index.php?page=favorites&s=delete&id=${postId}`, { credentials: 'include' });
return res.ok;
} catch { return false; }
},
async removeFromPool(postId, poolId) {
try {
const res = await fetch(`https://gelbooru.com/public/remove.php?removepool_post=1&pool_id=${poolId}&id=${postId}`, { credentials: 'include' });
return res.ok;
} catch { return false; }
}
};
// ============ UI ============
const ui = {
showMessage(text, success = true) {
const { message } = elements;
message.textContent = text;
message.style.background = success ? COLORS.success : COLORS.error;
message.style.display = 'block';
message.style.color = '#000000';
setTimeout(() => message.style.display = 'none', CONFIG.MESSAGE_DURATION);
},
showToast(text, success = true) {
document.getElementById('gel-toast')?.remove();
const toast = utils.createElement('div', `
position:fixed;top:15%;right:2%;padding:8px 15px;
background:${success ? COLORS.bgLight : COLORS.error};
color: ${COLORS.text};border: 1px solid ${COLORS.border};
border-radius: 3px;z-index:10000;font: bold 12px Verdana,sans-serif
`, { id: 'gel-toast', textContent: text });
document.body. appendChild(toast);
setTimeout(() => toast.remove(), CONFIG.TOAST_DURATION);
},
updateArtist(data) {
const { artist } = elements;
if (data?. artistName && data?. artistUrl) {
artist.textContent = data.artistName;
artist. href = data.artistUrl;
artist. style.pointerEvents = 'auto';
} else {
artist. textContent = '(no artist)';
artist.href = '#';
artist. style.pointerEvents = 'none';
}
},
createLink(text, onClick) {
const link = utils.createElement('a', STYLES. link, { href: 'javascript:;', textContent: text });
utils.addHoverUnderline(link);
if (onClick) link.onclick = (e) => { e.preventDefault(); e.stopPropagation(); onClick(); };
return link;
},
createPoolDropdown(postId, forOverlay = false) {
const wrapper = utils.createElement('span', 'position:relative;display:inline-block');
const trigger = utils.createElement('a', forOverlay ? STYLES.link : null, { href: 'javascript:;', innerHTML: 'Add to pool ▾' });
if (forOverlay) utils.addHoverUnderline(trigger);
const menu = utils.createElement('div', STYLES. dropdown);
CONFIG.POOLS.forEach((pool, i) => {
const item = utils. createElement('a', STYLES.dropdownItem, { href: 'javascript:;', textContent: pool. name });
if (i === CONFIG.POOLS.length - 1) item.style.borderBottom = 'none';
item.onmouseenter = () => { item.style.background = COLORS.link; item.style.color = '#fff'; };
item.onmouseleave = () => { item.style.background = 'transparent'; item.style. color = COLORS. link; };
item.onclick = async (e) => {
e.preventDefault();
e.stopPropagation();
menu.style.display = 'none';
const success = await api.addToPool(postId, pool.id);
forOverlay ? ui.showMessage(success ? `✓ Added to ${pool.name}` : '✗ Failed', success)
: ui.showToast(success ? `✓ Added to ${pool.name}` : '✗ Failed', success);
};
menu.appendChild(item);
});
trigger.onclick = (e) => { e.preventDefault(); e.stopPropagation(); menu.style.display = menu.style.display === 'none' ? 'block' : 'none'; };
document.addEventListener('click', (e) => { if (! wrapper.contains(e. target)) menu.style.display = 'none'; });
wrapper.append(trigger, menu);
return { wrapper, menu };
}
};
// ============ OVERLAY ============
const overlay = {
create() {
const el = elements;
el.overlay = utils.createElement('div', STYLES.overlay, { id: 'gel-enlarger-overlay' });
el.loading = utils.createElement('div', `color:${COLORS. text};font:24px Verdana,sans-serif;position:absolute`, { textContent: 'Loading...' });
el.image = utils.createElement('img', `${STYLES.media};object-fit:contain;cursor:pointer`);
el.image.onclick = (e) => { e.stopPropagation(); this.openPost(); };
el.videoContainer = utils.createElement('div', `${STYLES.media};position:relative;display:flex;align-items:center;justify-content:center`);
el.video = utils.createElement('video', 'max-width:85vw;max-height:85vh;width:auto;height:auto', {
id: 'gel-enlarger-video',
controls: true,
loop: true,
autoplay: true
});
el.video.ondblclick = (e) => { e.preventDefault(); e.stopPropagation(); };
let clickCount = 0, clickTimer = null;
el.video.onclick = (e) => {
e.stopPropagation();
clickCount++;
if (clickCount === 1) {
clickTimer = setTimeout(() => clickCount = 0, 300);
} else if (clickCount === 2) {
clearTimeout(clickTimer);
clickCount = 0;
this.openPost();
}
};
el.videoContainer.appendChild(el.video);
el.counter = utils.createElement('div', `position:fixed;bottom:20px;left:50%;transform:translateX(-50%);color:${COLORS. text};font:12px Verdana,sans-serif;padding:5px 10px;background:${COLORS. bgDark};border-radius:3px;border:1px solid ${COLORS.border}`);
el.message = utils.createElement('div', `position:fixed;top:60px;left:50%;transform: translateX(-50%);padding:10px 20px;border-radius:3px;font:12px Verdana,sans-serif;display:none;color:#fff;z-index:1000001`);
el.leftArrow = this.createArrow('left', '❮', () => {
if (state.currentIndex <= 0) {
const pid = parseInt(new URL(window.location.href).searchParams.get('pid') || '0');
if (pid > 0) {
ui.showMessage('Start of page. Loading previous page...', true);
this.goToPreviousPage()
} else {
ui.showMessage('This is the first page', false);
}
} else {
this.navigate(-1);
}
});
el.rightArrow = this.createArrow('right', '❯', () => {
if (state.currentIndex >= state.thumbnails.length - 1) {
ui.showMessage('End of page. Loading next page...', true);
setTimeout(() => this.goToNextPage(), 100);
} else {
this. navigate(1);
}
});
const topBar = utils.createElement('div', STYLES.topBar);
topBar.onclick = (e) => e.stopPropagation();
el.artist = utils.createElement('a', STYLES.artist, { textContent: 'loading... ', target: '_blank' });
utils.addHoverUnderlineArtist(el.artist);
el.artist.onclick = (e) => e.stopPropagation();
el.favLink = ui.createLink('Add to favorites', async () => {
const thumb = state.thumbnails[state.currentIndex];
if (! thumb) return;
const success = await api.addToFavorite(thumb. postId);
ui.showMessage(success ? '✓ Added to favorites!' : '✗ Failed', success);
});
el.unfavLink = ui.createLink('Unfavorite', async () => {
const thumb = state.thumbnails[state.currentIndex];
if (!thumb) return;
const success = await api.removeFromFavorite(thumb.postId);
ui.showMessage(success ? '✓ Removed from favorites!' : '✗ Failed', success);
});
el.unpoolLink = ui.createLink('Unpool', async () => {
const thumb = state.thumbnails[state.currentIndex];
if (!thumb) return;
const pool = CONFIG.POOLS[0]; // Using first pool (colored likes - 69275)
const success = await api.removeFromPool(thumb.postId, pool.id);
ui.showMessage(success ? `✓ Removed from ${pool.name}` : '✗ Failed', success);
});
const { wrapper: poolWrapper, menu: poolMenu } = ui.createPoolDropdown(null, true);
el.poolWrapper = poolWrapper;
el.poolMenu = poolMenu;
const sep = () => utils.createElement('span', STYLES.separator, { textContent: ' | ' });
topBar.append(el.artist, sep(), el.favLink, sep(), el.unfavLink, sep(), poolWrapper, sep(), el.unpoolLink);
el.overlay.append(el.loading, el.image, el.videoContainer, el.counter, el. message, el.leftArrow, el. rightArrow, topBar);
el.overlay.onclick = (e) => { if (e.target === el.overlay) this.hide(); };
el.overlay.addEventListener('click', () => el.poolMenu.style.display = 'none');
document.body.appendChild(el.overlay);
document.addEventListener('keydown', (e) => this.handleKeyboard(e), true);
},
createArrow(side, html, onClick) {
const arrow = utils.createElement('div', `${STYLES.arrow};${side}:20px`, { innerHTML: html });
utils.addHoverScale(arrow);
arrow.onclick = (e) => { e.stopPropagation(); onClick(); };
return arrow;
},
show() {
elements.overlay.style. display = 'flex';
state.overlayVisible = true;
},
hide() {
const el = elements;
el.overlay.style.display = 'none';
el.image.src = '';
el.image.style.display = 'none';
el.video.pause();
el.video.src = '';
el.videoContainer.style.display = 'none';
el.loading.style.display = 'block';
el. loading.textContent = 'Loading...';
el.poolMenu.style.display = 'none';
state.currentIndex = -1;
state.overlayVisible = false;
},
navigate(dir) {
const newIndex = state.currentIndex + dir;
if (newIndex >= 0 && newIndex < state.thumbnails. length) {
this.loadMedia(newIndex);
}
},
goToNextPage() {
const currentUrl = window.location.href;
const url = new URL(currentUrl);
if (currentUrl.includes('page=post') && currentUrl.includes('s=view')) {
ui.showMessage('No next page on single post view', false);
return;
}
const pid = parseInt(url.searchParams.get('pid') || '0');
const temp = url.searchParams.get('page');
const perPage = temp === 'favorites' ? CONFIG.POSTS_PER_PAGE.favorites : temp === 'pool' ? CONFIG.POSTS_PER_PAGE.pool :CONFIG.POSTS_PER_PAGE.regular;
if (state.thumbnails.length < perPage) {
ui.showMessage('This is the last page', false);
return;
}
url.searchParams.set('pid', pid + perPage);
sessionStorage.setItem('gelbooru_overlay_nav', 'next');
window.location.href = url.toString();
},
goToPreviousPage() {
const currentUrl = window.location.href;
const url = new URL(currentUrl);
if (currentUrl.includes('page=post') && currentUrl.includes('s=view')) {
ui.showMessage('No previous page on single post view', false);
return;
}
const pid = parseInt(url.searchParams.get('pid') || '0');
const temp = url.searchParams.get('page');
const perPage = temp === 'favorites'
? CONFIG.POSTS_PER_PAGE.favorites
: temp === 'pool'
? CONFIG.POSTS_PER_PAGE.pool
: CONFIG.POSTS_PER_PAGE.regular;
if (pid <= 0) {
ui.showMessage('This is the first page', false);
return;
}
url.searchParams.set('pid', Math.max(0, pid - perPage));
// Save state for restoring overlay
sessionStorage.setItem('gelbooru_overlay_nav', 'prev');
window.location.href = url.toString();
},
openPost() {
const thumb = state.thumbnails[state.currentIndex];
if (thumb) window.open(thumb.postUrl, '_blank');
},
handleKeyboard(e) {
if (! state.overlayVisible) return;
// if (state.suspendMain) return;
if (e.key === ' ' && utils.isVideoFocused()) return;
const actions = {
'Escape': () => this.hide(),
'ArrowLeft': () => {
if (state.currentIndex <= 0) {
const pid = parseInt(new URL(window.location.href).searchParams.get('pid') || '0');
if (pid > 0) {
ui.showMessage('Start of page. Loading previous page...', true);
setTimeout(() => this.goToPreviousPage(), 500);
} else {
ui.showMessage('This is the first page', false);
}
} else {
this.navigate(-1);
}
},
'ArrowRight': () => {
if (state.currentIndex >= state.thumbnails. length - 1) {
ui.showMessage('End of page. Loading next page...', true);
setTimeout(() => this.goToNextPage(), 1000);
} else {
this.navigate(1);
}
},
'f': () => elements.favLink.click(),
'F': () => elements.favLink.click(),
'j': () => this.addToFirstPool(),
'J': () => this.addToFirstPool(),
' ': () => {
if (elements.videoContainer.style.display !== 'none' && ! utils.isVideoFocused()) {
elements.video.paused ? elements.video.play() : elements.video.pause();
}
}
};
if (actions[e.key]) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
actions[e.key]();
}
},
async addToFirstPool() {
const thumb = state.thumbnails[state.currentIndex];
if (!thumb) return;
const pool = CONFIG.POOLS[0];
const success = await api. addToPool(thumb.postId, pool.id);
ui.showMessage(success ? `✓ Added to ${pool.name}` : '✗ Failed', success);
},
async loadMedia(index) {
if (index < 0 || index >= state.thumbnails.length) return;
state.currentIndex = index;
const thumb = state.thumbnails[index];
const el = elements;
el.counter.textContent = `${index + 1} / ${state.thumbnails.length}`;
const isFirst = index === 0;
const canGoPrevPage = new URL(window.location.href).searchParams.get('pid') > 0;
el.leftArrow.style.opacity = (index > 0 || canGoPrevPage) ? '.7' : '.3';
el.leftArrow.style.cursor = (index > 0 || canGoPrevPage) ? 'pointer' : 'default';
el. loading.textContent = 'Loading...';
el.loading.style. display = 'block';
el.image.style.display = 'none';
el. videoContainer.style.display = 'none';
el.video. pause();
el.poolMenu.style.display = 'none';
el.artist.textContent = 'loading...';
el.artist.href = '#';
el.artist.style.pointerEvents = 'none';
this.updatePoolDropdown(thumb. postId);
let data = state.cache[thumb. postId];
if (!data) {
data = await api.fetchPostData(thumb. postId);
if (data) state.cache[thumb.postId] = data;
}
if (data?. mediaUrl) {
this.displayMedia(data);
ui.updateArtist(data);
this.preloadNext(index);
} else {
el. loading.textContent = 'Failed to load media';
el.artist.textContent = '(error)';
}
},
updatePoolDropdown(postId) {
const { poolMenu } = elements;
poolMenu.querySelectorAll('a').forEach((item, i) => {
const pool = CONFIG.POOLS[i];
item.onclick = async (e) => {
e. preventDefault();
e.stopPropagation();
poolMenu.style.display = 'none';
const success = await api.addToPool(postId, pool.id);
ui. showMessage(success ? `✓ Added to ${pool.name}` : '✗ Failed', success);
};
});
},
displayMedia(data) {
const el = elements;
if (data.isVideo) {
el.video.src = data.mediaUrl;
el. videoContainer.style.display = 'block';
el.loading.style.display = 'none';
el.video. blur();
el. video.play().catch(() => {});
} else {
const img = new Image();
img.onload = () => {
el. image.src = data.mediaUrl;
el.image.style.display = 'block';
el.loading.style.display = 'none';
};
img.onerror = () => el.loading.textContent = 'Failed to load image';
img.src = data.mediaUrl;
}
},
preloadNext(fromIndex) {
for (let i = 1; i <= CONFIG. PRELOAD_COUNT; i++) {
const idx = fromIndex + i;
if (idx >= state.thumbnails. length) break;
const thumb = state.thumbnails[idx];
if (state.cache[thumb. postId]) continue;
api.fetchPostData(thumb.postId).then(data => {
if (data) {
state. cache[thumb.postId] = data;
if (! data.isVideo && data.mediaUrl) new Image().src = data.mediaUrl;
}
});
}
}
};
// ============ THUMBNAILS ============
const thumbnails = {
collect() {
state.thumbnails = [];
document.querySelectorAll('img[src*="/thumbnails/"]').forEach(img => {
const link = img.closest('a[href*="page=post"][href*="s=view"][href*="id="]');
if (! link) return;
const match = link.href. match(/[?&]id=(\d+)/);
if (! match) return;
state.thumbnails.push({
img,
link,
postId: match[1],
postUrl: link.href
});
});
return state.thumbnails. length;
},
setup() {
this.collect();
state.thumbnails.forEach((thumb) => {
if (thumb.link. dataset.enlargerSetup) return;
thumb.link.dataset.enlargerSetup = 'true';
thumb.img.style.cursor = 'zoom-in';
thumb.img.title = 'Click to enlarge (Ctrl+Click for original)';
thumb.link.addEventListener('click', (e) => {
if (e.ctrlKey || e.metaKey || e.button === 1) return;
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
this.collect();
const idx = state.thumbnails.findIndex(t => t.postId === thumb.postId);
if (idx === -1) return;
overlay.show();
overlay.loadMedia(idx);
}, true);
});
}
};
// ============ SINGLE POST PAGE ============
const singlePost = {
init() {
const postId = new URLSearchParams(window. location.search).get('id');
if (!postId) return;
this.injectPoolDropdown(postId);
this.setupKeyboardShortcuts(postId);
},
injectPoolDropdown(postId) {
// Find the "Leave a Comment" link
const commentLink = document.querySelector('a#showCommentBox');
if (commentLink) {
const { wrapper } = ui.createPoolDropdown(postId, false);
// Insert separator and pool dropdown after favorite link
const sep = document.createTextNode(' | ');
if (commentLink.nextSibling) {
commentLink.parentNode.insertBefore(sep, commentLink.nextSibling);
commentLink.parentNode.insertBefore(wrapper, sep. nextSibling);
} else {
commentLink.parentNode. appendChild(sep);
commentLink. parentNode.appendChild(wrapper);
}
return;
}
},
setupKeyboardShortcuts(postId) {
document.addEventListener('keydown', async (e) => {
if (state.overlayVisible) return;
if (utils.isInputFocused()) return;
// F - Trigger favorite
if (e.key === 'f' || e.key === 'F') {
const favLink = document. querySelector('a[onclick*="addFav"]');
if (favLink) {
e.preventDefault();
favLink.click();
ui.showToast('✓ Favorites toggled');
}
}
// J - Add to first pool
if (e.key === 'j' || e.key === 'J') {
e.preventDefault();
const pool = CONFIG.POOLS[0];
ui.showToast(`Adding to ${pool.name}...`);
const success = await api. addToPool(postId, pool.id);
ui.showToast(success ? `✓ Added to ${pool.name}` : '✗ Failed', success);
}
// 1-9 - Add to pool by number
const num = parseInt(e. key);
if (num >= 1 && num <= CONFIG.POOLS.length) {
e.preventDefault();
const pool = CONFIG.POOLS[num - 1];
ui. showToast(`Adding to ${pool.name}...`);
const success = await api.addToPool(postId, pool.id);
ui.showToast(success ? `✓ Added to ${pool.name}` : '✗ Failed', success);
}
});
}
};
const poolQuickDelete = {
POOL_ID: "69275",
init() {
const url = new URL(window.location.href);
if (
url.searchParams.get("page") !== "pool" ||
url.searchParams.get("s") !== "show" ||
url.searchParams.get("id") !== this.POOL_ID
) return;
const tryInject = () => {
const label = document.querySelector('label[for="del-mode"]');
if (!label) return false;
if (document.getElementById("gel-pool-quick-delete")) return true;
const box = document.createElement("div");
box.id = "gel-pool-quick-delete";
box.style.cssText = `
margin-top:8px;
display:flex;
gap:6px;
align-items:center;
font:12px Verdana,sans-serif;
`;
const input = document.createElement("input");
input.type = "text";
input.placeholder = "Post ID to remove";
input.style.cssText = `
padding:4px 6px;
background:#1a1a1a;
color:#c1c1c1;
border:1px solid #3a3a3a;
border-radius:3px;
width:140px;
`;
const btn = document.createElement("button");
btn.textContent = "Delete";
btn.style.cssText = `
padding:4px 10px;
background:#2f2f2f;
color:#c1c1c1;
border:1px solid #3a3a3a;
border-radius:3px;
cursor:pointer;
`;
btn.onmouseenter = () => btn.style.background = "#3a3a3a";
btn.onmouseleave = () => btn.style.background = "#2f2f2f";
btn.onclick = async () => {
const id = input.value.trim();
if (!/^\d+$/.test(id)) {
ui.showToast("✗ Invalid post id", false);
return;
}
ui.showToast("Removing...");
try {
const res = await fetch(
`https://gelbooru.com/public/remove.php?removepool_post=1&pool_id=${this.POOL_ID}&id=${id}`,
{ credentials: "include" }
);
ui.showToast(res.ok ? "✓ Removed from pool" : "✗ Failed", res.ok);
} catch {
ui.showToast("✗ Failed", false);
}
};
box.append(input, btn);
label.insertAdjacentElement("afterend", box);
return true;
};
if (tryInject()) return;
const obs = new MutationObserver(() => {
if (tryInject()) obs.disconnect();
});
obs.observe(document.body, { childList: true, subtree: true });
}
};
const homeLinks = {
inject() {
const tryInject = () => {
const linksDiv = document.querySelector('div#links.space');
if (!linksDiv) return false;
if (document.getElementById("gel-custom-links")) return true;
const wrapper = document.createElement("span");
wrapper.id = "gel-custom-links";
const sampleLink = document.querySelector('a');
const linkColor = sampleLink ? getComputedStyle(sampleLink).color : 'inherit';
const poolLink = document.createElement("a");
poolLink.style.color = linkColor;
poolLink.href = "https://gelbooru.com/index.php?page=pool&s=show&id=69275";
poolLink.textContent = "My Pool";
poolLink.title = "Open my pool";
const favLink = document.createElement("a");
favLink.href = "https://gelbooru.com/index.php?page=favorites&s=view&id=1420596";
favLink.textContent = "My Favorites";
favLink.style.color = linkColor;
favLink.title = "Open my favorites";
wrapper.append(" ");
wrapper.appendChild(poolLink);
wrapper.append(" ");
wrapper.appendChild(favLink);
linksDiv.appendChild(wrapper);
return true;
};
// Try immediately
if (tryInject()) return;
// Otherwise observe until it appears
const obs = new MutationObserver(() => {
if (tryInject()) obs.disconnect();
});
obs.observe(document.body, { childList: true, subtree: true });
}
};
// ============ INIT ============
function init() {
homeLinks.inject();
poolQuickDelete.init();
const url = window.location.href;
const isSinglePost = url. includes('page=post') && url.includes('s=view') && url.includes('id=');
if (isSinglePost) {
singlePost. init();
}
overlay.create();
thumbnails.setup();
// Restore overlay state after page navigation
const navState = sessionStorage.getItem('gelbooru_overlay_nav');
if (navState) {
sessionStorage.removeItem('gelbooru_overlay_nav');
setTimeout(() => {
if (state.thumbnails.length > 0) {
overlay.show();
if (navState === 'next') {
overlay.loadMedia(0); // first image
} else if (navState === 'prev') {
overlay.loadMedia(state.thumbnails.length - 1); // last image
}
}
}, 100);
}
new MutationObserver(() => thumbnails.setup()).observe(document.body, { childList: true, subtree: true });
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();