I like to browse one-handed.
Tested on latest Firefox with Greasemonkey.
Index Gallery:
- Next page: [Space] at bottom
- Previous page: [Shift-Space] at top
- Convert to gallery: [Ctrl-Space] or [View all] button
- (Oldest first: [Ctrl-shift-space])
- Next in gallery: [Space]
- Previous in gallery: [Shift-space]
- Play gif or webm: [W]
- Seek: [A]/[D]
- Volume: [Shift-W]/[Shift-S]
Post Tweaks:
- Play webm: [W] (Also focuses image)
- Seek: [A]/[D]
- Volume: [Shift-W]/[Shift-S]
I can make some improvements if there's interest, but I think I saw dedicated extensions for this sort of thing. Figured I'd post what I have anyway.
Index Gallery
Reload gallery page to get out of it, it just replaces the current document
Lazily loads items in sequence, adjust timerInterval if your internet is awesome.
Does not handle flash submissions
Firefox doesn't like pages of videos (Video thumbnails, please)
No blacklist support (I don't know where it is in the document scope)
Rudimentary include/exclude filter commented out/disabled inside of extractItem around line 130
Unviewed/Unloaded shown in tab title
// ==UserScript== // @name E621_IndexGallery // @namespace eig // @include https://e621.net/post/index/* // @version 1 // @grant none // ==/UserScript== let timerInterval = 1000 let baseTitle = document.title let mainLoop = null let paused = false let loading = 0 let onItemLoad = () => { loading-- } let onItemError = () => { loading-- } let imgOnLoad = function() { if (this.width + this.height == 0) { this.onerror() } else if (this.complete) { // console.log('Active:', loading, 'Finished:', this.src) onItemLoad() } } let imgOnLoadComplete = function() { if (this.width + this.height == 0) { this.onerror() } else { // console.log('Active:', loading, 'Finished:', this.src) onItemLoad() } } let extensions = [ 'jpg', 'png', 'jpeg', 'gif' ] let clickGif = function() { if (this.hasClassName('gif-loader')) { this.playing = true this.src = this.item.base+'.gif' this.removeClassName('gif-loader') } // else { // this.playing = false // this.src = this.item.thumb // this.addClassName('gif-loader') // } } let imgOnError = function() { this._ext++ let nextExt = extensions[this._ext] if (nextExt) { if (nextExt == 'gif') { this.src = this.item.thumb this.addClassName('gif-loader') this.addEventListener('click', clickGif) // console.log('Loading gif placeholder: '+this.item.base) } else { this.src = this._src+'.'+nextExt // console.log('Falling back to: '+this.src) } } else { console.log('Failed to load: '+this.item.page) onItemError() } } let urls = [] /* We have to load each video page to discover the url for the video itself, because there's no direct connection to the video */ let videoPageLoad = function() { if (this.status != 200) { console.log(this.statusText) // TODO: Error return } else { this.item.src = this.responseXML.querySelector('#webm-container > source').src this.item.node.src = this.item.src } // console.dir(this.responseXML) // console.log('Video loaded : '+this.responseURL+', '+this.item.src) // console.log('videoPageLoad', this) } let videoPageError = function() { // console.log('videoPageError', this) onItemError() loading-- } let videoOnCanPlay = function() { // console.log('videoOnCanPlay', this) onItemLoad() loading-- } let videoOnError = function() { // console.log('videoOnError', this) onItemLoad() loading-- } let re_src = /(.+)preview\/(.+)\.jpg$/ let extractItem = e => { if (e.src.includes('download-preview')) { return null } // if (!(e.alt.includes('female') || e.alt.includes('intersex')) || e.alt.includes('my_little_pony') || e.alt.includes('sonic_(series)')) { // return null // } let item = { page: e.parentElement.href, thumb: e.src } if (e.src.includes('webm-preview')) { item.video = true } else if (e.src.includes('preview/')) { let match = re_src.exec(e.src) if (match) { item.base = match[1]+match[2] } } return item } let extractPreviews = doc => { return Array.from(doc.querySelectorAll('img.preview'), extractItem) .filter(x => x) } let widgets = {} let timeoutID let currentImg let unviewed = -1 let mainItem = (item) => { // Wrapper let div = document.createElement('div') // Video if (item.video) { // console.log('Adding video: '+item.page) let video = document.createElement('video') item.node = video video.loop = true video.autoplay = false video.muted = true video.controls = true video.preload = 'metadata' video.type = 'video/webm' video.addEventListener('canplay', videoOnCanPlay) video.addEventListener('error', videoOnError) div.appendChild(video) // Weight videos heigher loading++ let xhr = new XMLHttpRequest() xhr.item = item xhr.addEventListener('load', videoPageLoad) xhr.addEventListener('error', videoPageError) xhr.open('GET', item.page) xhr.responseType = 'document'; xhr.send() } // Image else { // console.log('Adding image: '+item.base) let img = document.createElement('img') img.onerror = imgOnError img.onload = imgOnLoad img.onloadcomplete = imgOnLoadComplete img.item = item img.src = item.base+'.jpg' img._src = item.base img._ext = 0 div.appendChild(img) } // Link let a = document.createElement('a') a.href = item.page a.innerHTML = item.page div.appendChild(a) widgets.output.appendChild(div) loading++ if (!currentImg) { currentImg = div currentImg.scrollIntoView({behavior: 'smooth'}) } else { div.viewed = false unviewed++ } } let updateUI = () => { if (urls.length === 0) { document.title = baseTitle widgets.status.innerHTML = 'Done' } else { if (unviewed < 10) { widgets.status.innerHTML = urls.length+' Remaining' } else { widgets.status.innerHTML = urls.length+' (Waiting)' } document.title = `(${unviewed}/${urls.length}) ${baseTitle}` } } mainLoop = function() { if (urls.length > 0 && !paused && loading < 2) { if (unviewed < 5) { mainItem(urls.shift()) } } updateUI() } let onPauseClick = function() { paused = !paused if (paused) { widgets.pause.innerHTML = 'Resume' } else { widgets.pause.innerHTML = 'Pause' } } let onDownloadClick = function(alt) { // Begin urls = extractPreviews(document) if (alt) { urls.reverse() } // $(window).off() document.querySelector('body').innerHTML = `<style id='__style'> body { text-align: center; } #__status { font-size: 1.4em; margin: 10px; } #__control { position: fixed; background: grey; padding-right: 5px; border-radius: 0px 0px 10px 0px; z-index: 100; } #__control > * { display: inline-block } #__output img, #__output video { display: block; position: relative; height: calc(100vh - 25px); width: 98vw; object-fit: contain; } img.gif-loader::after { position: absolute; top: 50%; left: 50%; content: 'Click to play GIF'; } #__output a { margin-bottom: 5vh; } body * { margin-left: auto; margin-right: auto; } </style> <div id='__container'> <div id='__control'> <button id='__pause'>Pause</button> <div id='__status'></div> </div> <div id='__output'><div></div></div> </div>` widgets.control = document.getElementById('__control') widgets.status = document.getElementById('__status') widgets.pause = document.getElementById('__pause') widgets.output = document.getElementById('__output') widgets.pause.addEventListener('click', onPauseClick) let videoFunction = (func) => { return (...args) => { if (currentImg && currentImg.firstChild && currentImg.firstChild.nodeName == 'VIDEO') { func(currentImg.firstChild, ...args) } } } // Video helpers let seek = videoFunction((video, mod) => { video.currentTime = Math.min(Math.max(video.currentTime + mod, 0), video.duration) }) let attenuate = videoFunction((video, mod) => { video.volume = Math.min(Math.max(video.volume + mod, 0), 1) if (video.volume > 0 && video.muted) { video.muted = false } }) let playOrPause = () => { if (!currentImg) { return null } let node = currentImg.firstChild if (node.nodeName == 'VIDEO') { if (!node.playing) { node.play() node.playing = true } else { node.pause() node.playing = false } } else { node.click() } } document.onkeydown = function(event) { if (event.key == ' ') { if (currentImg) { // Pause focus if (currentImg.firstChild && currentImg.firstChild.playing) { playOrPause() currentImg.playOnFocus = true } // Get next focus let sibling = event.shiftKey ? currentImg.previousSibling : currentImg.nextSibling if (sibling) { sibling.scrollIntoView() currentImg = sibling // Resume previously paused focus if (currentImg.playOnFocus == true) { playOrPause() playOnFocus = false } // Set as viewed for buffered loading if (!sibling.viewed) { sibling.viewed = true unviewed-- } } } event.preventDefault() } // Volume up else if (event.key == 'W') { attenuate(0.2) } // Volume down else if (event.key == 'S') { attenuate(-0.2) } // Play or pause else if (event.key == 'w') { playOrPause() } // Seek forward else if (event.key == 'd') { seek(5) } // Seek backward else if (event.key == 'a') { seek(-5) } } setInterval(mainLoop, timerInterval) } let divHeader = document.querySelector('.sidebar') widgets.download = document.createElement('div') widgets.download.innerHTML = '<button>Load all</button>' widgets.download.lastChild.addEventListener('click', onDownloadClick, false) divHeader.parentNode.insertBefore(widgets.download, divHeader.nextSibling) document.onkeydown = function(event) { if (event.key == ' ' && event.ctrlKey) { onDownloadClick(event.altKey) } else if (event.key == ' ') { let link = null // Previous page at top if (event.shiftKey && window.scrollY == 0) { link = document.querySelector('a.prev_page') } // Next page at bottom else if (!event.shiftKey && (window.innerHeight + window.scrollY) >= document.body.offsetHeight-100) { link = document.querySelector('a.next_page') } if (link) { link.click() } } }
PostTweaks
Also works on r34
// ==UserScript== // @name e6+ tweaks // @namespace e6pt // @include https://e621.net/post* // @include https://rule34.xxx/index.php?page=post* // @grant none // ==/UserScript== let img = document.getElementById('image') let webm = document.getElementById('webm-container') // e621 || document.getElementById('gelcomVideoPlayer') // rule34 let e = webm || img // Video helpers let seek = (video, mod) => { video.currentTime = Math.min(Math.max(video.currentTime + mod, 0), video.duration) } let attenuate = (video, mod) => { video.volume = Math.min(Math.max(video.volume + mod, 0), 1) } // Pause/play/seek keyboard controls let playing = false let onKeyDown = event => { if (event.key == 'w') { e.scrollIntoView() } if (webm) { webm = document.getElementById('webm-container') // e621 || document.getElementById('gelcomVideoPlayer') // rule34 reloads theirs? // Volume up if (event.key == 'W') { if (webm.muted) { webm.muted = false webm.volume = 0 } attenuate(webm, 0.2) } // Volume down else if (event.key == 'S') { attenuate(webm, -0.2) } else if (event.key == 'w') { if (event.shiftKey) { } // Play or pause else { if (!playing) { webm.play() playing = true } else { webm.pause() playing = false } } } // Seek forward else if (event.key == 'd') { seek(webm, 5) } // Seek backwards else if (event.key == 'a') { seek(webm, -5) } // Mute toggle else if (event.key == 'e') { webm.muted = !webm.muted } } } document.onkeydown = onKeyDown // Focus content if (e) { e.scrollIntoView() setTimeout(() => { e.scrollIntoView() document.onkeydown = onKeyDown }, 3000) } // Mute video if (webm) { webm.muted = true }