Created
October 21, 2025 08:26
-
-
Save srndpty/d7c7277cb3915fc87dfb18ee0754da25 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Misskey Danbooru Checker | |
| // @namespace https://github.com/ | |
| // @version 2.0 | |
| // @description A unified, cached, and more informative Danbooru checker for Misskey notes. | |
| // @author srndpty | |
| // @match https://misskey.io/* | |
| // @connect danbooru.donmai.us | |
| // @grant GM.xmlHttpRequest | |
| // @grant GM_addStyle | |
| // @run-at document-idle | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| // --- START: CONFIGURATION --- | |
| // A single selector that matches a note on ANY page (timeline or single view) | |
| const NOTE_SELECTOR = 'article.x5yeR, article.xexC6'; | |
| // Selectors for finding elements WITHIN a note | |
| const PERMALINK_SELECTOR = 'a[href^="/notes/"]'; | |
| const INJECTION_POINT_SELECTOR = 'footer.xhAPG, article.xexC6 > footer'; | |
| const MEDIA_CONTAINER_SELECTOR = 'div.xbIzI'; | |
| // --- END: CONFIGURATION --- | |
| // In-memory cache to store results for the session | |
| const checkedNotesCache = new Map(); | |
| console.log('Danbooru Checker: Script is now running (v2.0).'); | |
| GM_addStyle(` | |
| .danbooru-checker-link { | |
| margin: 0 8px; font-weight: bold; text-decoration: none !important; | |
| padding: 2px 6px; border-radius: 4px; font-size: 0.9em; | |
| display: inline-flex; align-items: center; height: 32px; | |
| } | |
| .danbooru-checker-found { color: #fff !important; background-color: #008000; } | |
| .danbooru-checker-not-found { color: #fff !important; background-color: #d9534f; } | |
| .danbooru-checker-checking { color: #555 !important; background-color: #eee; } | |
| .danbooru-no-media-indicator { | |
| margin-left: 8px; opacity: 0.3; cursor: default; | |
| display: inline-flex; align-items: center; user-select: none; | |
| } | |
| `); | |
| /** | |
| * Builds and injects the final link based on API or cache result. | |
| * @param {object} result - The result object, e.g., {status: 'found', id: 123}. | |
| * @param {string} noteUrl - The URL of the note. | |
| * @param {HTMLElement} injectionElement - The DOM element to append the link to. | |
| */ | |
| function buildLink(result, noteUrl, injectionElement) { | |
| // Clear any previous links (like "Checking...") | |
| injectionElement.querySelectorAll('.danbooru-checker-link').forEach(el => el.remove()); | |
| const link = document.createElement('a'); | |
| link.className = 'danbooru-checker-link'; | |
| link.target = '_blank'; | |
| link.rel = 'noopener noreferrer'; | |
| if (result.status === 'found') { | |
| link.href = `https://danbooru.donmai.us/posts/${result.id}`; | |
| link.textContent = `Found (#${result.id})`; | |
| link.classList.add('danbooru-checker-found'); | |
| } else { // 'not_found' | |
| link.href = `https://danbooru.donmai.us/uploads/new?url=${encodeURIComponent(noteUrl)}`; | |
| link.textContent = 'Upload'; | |
| link.classList.add('danbooru-checker-not-found'); | |
| } | |
| injectionElement.appendChild(link); | |
| } | |
| /** | |
| * A single, unified function to process any note element. | |
| * @param {HTMLElement} noteElement - The note article element. | |
| */ | |
| function checkNote(noteElement) { | |
| if (noteElement.dataset.danbooruChecked) return; | |
| const injectionPoint = noteElement.querySelector(INJECTION_POINT_SELECTOR); | |
| if (!injectionPoint) { // Can't do anything without a place to inject | |
| noteElement.dataset.danbooruChecked = 'failed-no-injection-point'; | |
| return; | |
| } | |
| // Feature: Skip notes without media and provide visual feedback | |
| if (!noteElement.querySelector(MEDIA_CONTAINER_SELECTOR)) { | |
| noteElement.dataset.danbooruChecked = 'no-media'; | |
| if (!injectionPoint.querySelector('.danbooru-no-media-indicator')) { | |
| const noMediaIndicator = document.createElement('span'); | |
| noMediaIndicator.textContent = 'no media'; | |
| noMediaIndicator.title = 'Danbooru Checker: Skipped (no media)'; | |
| noMediaIndicator.className = 'danbooru-no-media-indicator'; | |
| injectionPoint.appendChild(noMediaIndicator); | |
| } | |
| return; | |
| } | |
| // Determine the note's URL | |
| const permalinkElement = noteElement.querySelector(PERMALINK_SELECTOR); | |
| const noteUrl = permalinkElement ? permalinkElement.href : window.location.href; | |
| noteElement.dataset.danbooruChecked = 'checking'; | |
| // Feature: Use cache if available | |
| if (checkedNotesCache.has(noteUrl)) { | |
| buildLink(checkedNotesCache.get(noteUrl), noteUrl, injectionPoint); | |
| return; | |
| } | |
| const tempLink = document.createElement('a'); | |
| tempLink.className = 'danbooru-checker-link danbooru-checker-checking'; | |
| tempLink.textContent = 'Danbooru...'; | |
| injectionPoint.appendChild(tempLink); | |
| GM.xmlHttpRequest({ | |
| method: 'GET', | |
| url: `https://danbooru.donmai.us/posts.json?tags=source:${encodeURIComponent(noteUrl)}&limit=1`, | |
| onload: function(response) { | |
| try { | |
| const posts = JSON.parse(response.responseText); | |
| let result; | |
| if (posts && posts.length > 0) { | |
| result = { status: 'found', id: posts[0].id }; | |
| } else { | |
| result = { status: 'not_found' }; | |
| } | |
| checkedNotesCache.set(noteUrl, result); // Save to cache | |
| buildLink(result, noteUrl, injectionPoint); | |
| } catch (e) { | |
| console.error('Danbooru Checker: Failed to parse response.', e); | |
| } | |
| }, | |
| onerror: function(error) { | |
| console.error('Danbooru Checker: Network request failed.', error); | |
| tempLink.remove(); | |
| } | |
| }); | |
| } | |
| /** | |
| * Initializes the script. | |
| */ | |
| function init() { | |
| // This function will be called by the observer whenever the DOM changes. | |
| const processAllVisibleNotes = () => { | |
| document.querySelectorAll(NOTE_SELECTOR).forEach(checkNote); | |
| }; | |
| const observer = new MutationObserver(processAllVisibleNotes); | |
| observer.observe(document.body, { | |
| childList: true, | |
| subtree: true | |
| }); | |
| // Run once on initial load | |
| processAllVisibleNotes(); | |
| console.log('Danbooru Checker: Unified observer initialized.'); | |
| } | |
| if (document.readyState === 'complete') { | |
| init(); | |
| } else { | |
| window.addEventListener('load', init); | |
| } | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment