Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Booru Quick Options
- // @version 1.0.0
- // @description Adds a handful of quick utilities to post thumbnails.
- // @author https://danbooru.donmai.us/users/1247062
- // @match https://*.donmai.us/
- // @match https://*.donmai.us/posts
- // @match https://*.donmai.us/posts?*
- // @match https://donmai.moe/
- // @match https://donmai.moe/posts
- // @match https://donmai.moe/posts?*
- // @grant GM_addStyle
- // ==/UserScript==
- GM_addStyle(`
- body:not([data-mode-menu-active="true"]) picture:hover .quickoptions-buttons { display: block; }
- .quickoptions-buttons {
- display: none;
- position: absolute;
- top: 6px;
- left: 4px;
- }
- .quickoptions-buttons[data-loading="true"] {
- opacity: 40%;
- pointer-events: none;
- }
- .quickoptions-buttons a {
- background-color: #1e1d1da8;
- padding: 2px;
- font-size: smaller;
- color: #fff;
- display: inline-block;
- }
- `)
- const optionsTemplate = `
- <a href="#" id="qo-action-download">save</a>
- <a href="#" id="qo-action-copy">copy</a>
- <a href="#" id="qo-action-copy-id">copy id</a>
- <a href="#" id="qo-action-artist">artist »</a>
- <a href="#" id="qo-action-char">char »</a>
- <a href="#" id="qo-action-goto-source">source »</a>
- `
- var posts = {}
- function normalizePixiv(link) {
- try {
- let regex = /https:\/\/i\.pximg\.net\/img-original\/img\/[0-9]+\/[0-9]+\/[0-9]+\/[0-9]+\/[0-9]+\/[0-9]+\/([0-9]+)_p.+\.(png|jpg|webp)/
- let matched = link.match(regex)
- if(matched == null) throw new Error()
- return `https://www.pixiv.net/artworks/${matched[1]}`
- } catch(e) {
- console.log(e)
- return Danbooru.notice(`extracting pixiv id failed: ${link}`)
- }
- }
- async function requirePost(button) {
- let buttonHolder = button.parentNode
- let pid = buttonHolder.getAttribute("post-id")
- let postRecord = posts[pid]
- if(postRecord.lock == true) return
- if(postRecord.data != null) return postRecord
- postRecord.lock = true
- buttonHolder.setAttribute("data-loading", "true")
- try {
- let resp = await fetch(`/posts/${pid}.json`)
- let postData = await resp.json()
- postRecord.data = postData
- } catch(e) {
- console.log(e)
- Danbooru.notice(e)
- }
- postRecord.lock = false
- buttonHolder.setAttribute("data-loading", "false")
- if(postRecord.data != null) return postRecord
- }
- async function doDownload(event) {
- event.preventDefault()
- let post = await requirePost(event.target)
- let url = new URL(post.data.file_url)
- url.searchParams.set("download", "1")
- window.open(url, "_self")
- }
- async function doCopyImg(event) {
- event.preventDefault()
- let post = await requirePost(event.target)
- let url = new URL(post.data.file_url)
- if(!["png", "jpg", "jpeg", "webp"].includes(post.data.file_ext)) return Danbooru.notice("Sorry, only static images are supported.")
- Danbooru.notice("Fetching image...")
- let imgResp
- let imgData
- try {
- imgResp = await fetch(url)
- imgData = await imgResp.blob()
- } catch(e) {
- console.log(e)
- Danbooru.notice(e)
- return
- }
- /*
- here's where it gets stupid. you see, the clipboard write api
- *does not* support any image file type other than PNG.
- so to copy a file, we have to do this dumb dance of converting
- our file to a bitmap, rendering that bitmap to a canvas, then
- exporting that canvas to a PNG to copy.
- this means i cannot support things that aren't static images
- and the copied image has its file size inflated. this sucks.
- https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem/supports_static
- */
- let imgBitmap = await window.createImageBitmap(imgData)
- let canvas = document.createElement("canvas")
- canvas.width = imgBitmap.width
- canvas.height = imgBitmap.height
- let ctx = canvas.getContext("2d")
- ctx.drawImage(imgBitmap, 0, 0)
- canvas.toBlob(async (pngData) => {
- let clipItem = new ClipboardItem({
- [pngData.type]: pngData
- })
- await navigator.clipboard.write([clipItem])
- Danbooru.notice(`Post #${post.data.id} image copied to clipboard`)
- }, "image/png")
- }
- async function doCopyId(event) {
- event.preventDefault()
- let pid = event.target.parentNode.getAttribute("post-id")
- await navigator.clipboard.write([
- new ClipboardItem({ ["text/plain"]: `post #${pid}` })
- ])
- Danbooru.notice(`post #${pid} copied to clipboard`)
- }
- async function doBrowseArtist(event) {
- if(event.button == 2) return
- event.preventDefault()
- let post = await requirePost(event.target)
- if(post.data.tag_count_artist == 0) return Danbooru.notice("Post has no artist tag.")
- let artistTag = post.data.tag_string_artist.split(" ")[0]
- let url = new URL(window.location)
- url.path = "/posts"
- url.searchParams.set("tags", artistTag + " status:any")
- url.searchParams.set("page", "1")
- window.open(url, "_blank")
- }
- async function doBrowseCharacter(event) {
- if(event.button == 2) return
- event.preventDefault()
- let post = await requirePost(event.target)
- if(post.data.tag_count_character == 0) return Danbooru.notice("Post has no character tag.")
- let charTag = post.data.tag_string_character.split(" ")[0]
- let url = new URL(window.location)
- url.path = "/posts"
- url.searchParams.set("tags", charTag)
- url.searchParams.set("page", "1")
- window.open(url, "_blank")
- }
- async function doBrowseSource(event) {
- event.preventDefault()
- let post = await requirePost(event.target)
- let sourceLink = post.data.source
- if(sourceLink.length == 0) return Danbooru.notice(`Post has no source`)
- // it appears danbooru api doesn't return the normalized source and
- // pixiv rejects requests to the source link as given by the api. let's normalize
- if(sourceLink.includes("i.pximg.net")) sourceLink = normalizePixiv(sourceLink)
- if(post.data.tag_string.includes("non-web_source")) return Danbooru.notice(`Post has a non-web source (${post.data.source})`)
- if(event.button != 2) { window.open(sourceLink, "_blank") } else {
- await navigator.clipboard.write([
- new ClipboardItem({ ["text/plain"]: sourceLink })
- ])
- Danbooru.notice(`Copied post #${post.data.id} source link to clipboard`)
- }
- }
- $(document).on("danbooru:post-preview-updated", onGalleryMutation)
- function onGalleryMutation(_, mut) {
- attachPost(document.querySelector(`#post_${mut.id}`))
- }
- function attachPost(element) {
- let pid = element.getAttribute("data-id")
- let picture = element.querySelector("picture")
- let holder = document.createElement("div")
- holder.classList.add("quickoptions-buttons")
- holder.setAttribute("post-id", pid)
- picture.appendChild(holder)
- holder.innerHTML = optionsTemplate
- posts[pid] = {
- lock: false,
- options: holder,
- data: null
- }
- holder.querySelector("#qo-action-download").addEventListener("click", doDownload)
- holder.querySelector("#qo-action-copy").addEventListener("click", doCopyImg)
- holder.querySelector("#qo-action-copy-id").addEventListener("click", doCopyId)
- holder.querySelector("#qo-action-artist").addEventListener("click", doBrowseArtist)
- holder.querySelector("#qo-action-artist").addEventListener("auxclick", doBrowseArtist)
- holder.querySelector("#qo-action-char").addEventListener("click", doBrowseCharacter)
- holder.querySelector("#qo-action-char").addEventListener("auxclick", doBrowseCharacter)
- holder.querySelector("#qo-action-goto-source").addEventListener("click", doBrowseSource)
- holder.querySelector("#qo-action-goto-source").addEventListener("auxclick", doBrowseSource)
- holder.querySelector("#qo-action-goto-source").addEventListener("contextmenu", (e) => {e.preventDefault()})
- }
- function init() {
- document.querySelectorAll(".post-preview").forEach(post => attachPost(post))
- }
- init()
Add Comment
Please, Sign In to add comment