Donmai

Danbooru (etc...) userscripts

Posted under General

BrokenEagle98 said:

The following will make maintaining bookmarks on Danbooru easier, as it removes the trailing search parameters thereby leaving a clean URL to bookmark. Use the Bookmarklet if it's only needed occasionally, or the Userscript if it's to be used all the time. (ref forum #136183)

Bookmarklet

javascript:$(".post-preview a").each((i,entry)=>{entry.href=entry.pathname;});

Note: Unfortunately, DText doesn't allow one to create a link out of the above, so the bookmarklet will have to be set manually.

Userscript

// ==UserScript==
// @name           Danbooru Post URLs
// @version        2.0
// @match          *://*.donmai.us/*
// @exclude        /^https?://\w+\.donmai\.us/.*\.(xml|json|atom)(\?|$)/
// @grant          none
// @run-at         document-end
// @description    Remove trailing search parameters from image links on the post search page.
// ==/UserScript==

$(".post-preview a").each((i,entry)=>{
    entry.href = entry.pathname;
});
Edit:
  • (2019-10-04)
    • Added exclude line for XML/JSON
    • Added version number
  • (2021-01-25)
    • Made the script active on the entire site
    • Changed the run time
    • Used the instance variation of each

Is this still current? This is the last piece of functionality I was using BetterBetterBooru for, but today's update seems to have broken BBB for good and this script doesn't seem to do anything for me.

Mrfle said:

Is this still current? This is the last piece of functionality I was using BetterBetterBooru for, but today's update seems to have broken BBB for good and this script doesn't seem to do anything for me.

It still works for me, on both Chrome and Firefox.

I've been voting a lot more since the voting system changed, and I wanted a more efficient way to do it. So based off of suggestions in topic #19990 I made this userscript to add hotkeys for voting.

I have upvote set to shift-W and downvote set to shift-S, the idea being that you can scroll through your search using A and D, voting as you go with your hand on WASD. But you can easily set the hotkeys to whatever you want by changing "shift+w" to the relevant key.

// ==UserScript==
// @name         VotingHotkeys
// @namespace    http://tampermonkey.net/
// @version      0.2
// @author       CormacM
// @match        https://*.donmai.us/posts/*
// @icon         https://www.google.com/s2/favicons?domain=donmai.us
// @grant        none
// ==/UserScript==


$(document).keydown("shift+w", () => document.getElementsByClassName('post-upvote-link')[0].click());
$(document).keydown("shift+s", () => document.getElementsByClassName('post-downvote-link')[0].click());

EDIT: improved version based on suggestion from an anonymous gooseman

Updated

Add copy tag button on the wiki pages to the right of the tag name.

UserScript
// ==UserScript==
// @name         Copy wiki tag
// @description  Add copy tag button on the wiki pages to the right of the tag name.
// @namespace    https://danbooru.donmai.us/
// @version      0.1
// @author       ZipFile
// @match        https://*.donmai.us/wiki_pages/*
// @exclude      https://*.donmai.us/wiki_pages/*/edit
// @grant        none
// ==/UserScript==

class CopyWikiTag {
    static init(document, navigator) {
        const pageTitle = document.getElementById("wiki-page-title");
        const a = pageTitle.querySelector("a");
        const url = new URL(a.href);
        const tags = url.searchParams.get("tags");
        const copyButton = document.createElement("i");

        copyButton.id = "copy-wiki-tag";
        copyButton.title = "Copy tag name";
        copyButton.style = "cursor: pointer; color: var(--muted-text-color);";
        copyButton.classList.value = "icon fas fa-copy";

        copyButton.addEventListener("click", () => navigator.clipboard.writeText(tags));
        pageTitle.append(copyButton);
    }
}

if (GM || GM_info) {
    CopyWikiTag.init(document, navigator);
}

I developed a userscript a while back that I shared on Discord, which adds an is_deleted checkbox input when editing an artist. This can help save time if editing and deleting an artist at the same time, otherwise the actions of editing and deleting must be done separately.

Userscript
// ==UserScript==
// @name         ArtistInputs
// @version      1.0
// @description  Add additional inputs to the artist edit form.
// @match        *://*.donmai.us/artists/*/edit
// @exclude      /^https?://\w+\.donmai\.us/.*\.(xml|json|atom)(\?|$)/
// @run-at       document-end
// @grant        none
// ==/UserScript==

const IS_DELETED_INPUT = `
<div class="input boolean optional artist_is_deleted field_with_hint">
    <input name="artist[is_deleted]" type="hidden" value="0">
    <input class="boolean optional" type="checkbox" value="1" name="artist[is_deleted]" id="artist_is_deleted">
    <label class="boolean optional" for="artist_is_deleted">Deleted</label>
    <span class="hint">Check to mark this artist as deleted.</span>
</div>`;

if (document.body.dataset.action === 'edit') {
    $('.artist_url_string').after(IS_DELETED_INPUT);
    $('#artist_is_deleted').prop('checked', JSON.parse(document.body.dataset.artistIsDeleted));
}

Laudividni said:

A request, if possible.

I use Search Artist Commentary for finding and manually translating identical commentaries, it's a great tool for me, but I'd like to be able to set a text (the translation) somewhere to add in the post's translated commentary field while tag scripting with commentary -commentary request -check commentary in the same page.

Simple stuff I wrote - copying other's code as I don't know javascript nor any programming language - to help with my shenanigans. It works when viewing a post, I don't know how to make something that works on the page I wanted months ago, but it doesn't matter much. As simple as it is, I'm sharing it because it helps mass translating titles like character names and the untitled ones from Pixiv, and other people might take interest and do better things with artist's commentaries. 6 was just a random easy key I chose.

// ==UserScript==
// @name         EditTranslatedtitle
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Adds text to the artist's commentary "translated title" field, adds commentary and removes commentary_request after pressing a key.
// @author       Individual
// @match        https://*.donmai.us/posts/*
// @grant        none
// ==/UserScript==

function doc_keyUp(e) {
    if (e.key === "6") {
        document.getElementById("artist_commentary_translated_title").value = "Untitled";
        if (document.getElementById("artist_commentary_add_commentary_tag")) {
            document.getElementById("artist_commentary_add_commentary_tag").click();};
        if (document.getElementById("artist_commentary_remove_commentary_request_tag")) {
            document.getElementById("artist_commentary_remove_commentary_request_tag").click();};
        document.getElementsByClassName("ui-button ui-corner-all ui-widget")[8].click();
    }
}
document.addEventListener("keyup", doc_keyUp, false);

Updated

Laudividni said:

It works when viewing a post, I don't know how to make something that works on the page I wanted months ago, but it doesn't matter much.

Which page is that? Artists commentary index? Or posts index?

Sort artist names: prioritize good artist names, gray-out garbage names.

User crawlers/scrappers are nice, but they add too much noise. Better implement userscript than fixing names myself.

UserScript
// ==UserScript==
// @name         Sort artist names
// @description  Prioritize good artist names, gray-out garbage names.
// @namespace    https://danbooru.donmai.us/
// @version      0.1
// @author       ZipFile
// @match        https://*.donmai.us/artists
// @match        https://*.donmai.us/artists?*
// @match        https://*.donmai.us/artists/*
// @exclude      https://*.donmai.us/artists/*/edit
// @grant        none
// @run-at       document-end
// ==/UserScript==

class SortArtistNames {
    static separator = /[@\p{Extended_Pictographic}\u2700-\u27BF]/u
    static badNames = new RegExp([
        "お?絵?(?:仕事|依頼)(?:募集|受付)(?:停止)?中?", // seeking job
        "(?:skeb|スケブ|リクエスト|コミッション|通販)(?:募集|受付|作業|停止)中?", // doing comms
        "始めました", // started something (skeb, fanbox, etc...)
        "^user_[a-z]+\\d+$", // autogenerated pixiv stacc names
        "お?絵描き$", // drawing account
        // company accounts
        "【公式】",
        "公式(?:ページ|アカウント|$)",
        // stupid suffixes
        "(?:ついった|ツイッタ)ー?",
        "(?:twitter|pixiv|skeb|fanbox)$",
        // events
        "例大祭",
        "[冬夏]コミ",
        "コミケ",
        "コミティア",
        "コミ1",
        "\\d?日目[東西南ews]?\\d?_?.?_?-?\\d\\d[a-z]",
        "砲雷撃戦?",
    ].join("|"), "i")

    static estimate(names) {
        const scores = {};
        const suffixes = {};
        const badComponents = {};
        let maxScore = 0, minScore = 0;

        for (const rawName of names) {
            const name = rawName.normalize("NFKC");
            const components = name.split(this.separator);
            let score = 0;

            for (const rawOtherName of names) {
                const otherName = rawOtherName.normalize("NFKC");

                if (name !== otherName && otherName.startsWith(name)) {
                    score++;
                    suffixes[rawOtherName] = true;
                }
            }

            for (const component of components) {
                if (!component || this.badNames.test(component)) {
                    score--;
                    badComponents[rawName] = true;
                }
            }

            if (score > maxScore) {
                maxScore = score;
            }

            if (score < minScore) {
                minScore = score;
            }

            scores[rawName] = score;
        }

        const scale = maxScore - minScore;
        const out = [];

        if (scale === 0) {
            return out;
        }

        function cmp(a, b) {
            const scoreA = scores[a], scoreB = scores[b];

            if (scoreA === scoreB) {
                return a.localeCompare(b);
            }

            return scoreB - scoreA;
        }

        for (const name of names.sort(cmp)) {
            const score = (scores[name] - minScore) / scale;
            const garbage = !!((suffixes[name] && scores[name] < 0) || badComponents[name]);

            out.push({name, score, garbage});
        }

        return out;
    }

    static sort(document, artistOtherNames, wrapper) {
        const nameToElementMap = {}, names = [];

        for (const el of artistOtherNames) {
            const name = el.text.trim();

            names.push(name);
            nameToElementMap[name] = el;
        }

        const fragment = document.createDocumentFragment();
        const scores = this.estimate(names);

        for (const score of scores) {
            const el = nameToElementMap[score.name];

            el.dataset.score = score.score;
            el.dataset.garbage = score.garbage;

            fragment.appendChild(el);
            fragment.appendChild(document.createTextNode(" "));

            if (score.garbage) {
                el.classList.add("text-muted");
            }
        }

        wrapper.appendChild(fragment);
    }

    static init(document) {
        const otherNamesWrapper = document.querySelector("#c-artists #a-show > .items-center + div");

        if (otherNamesWrapper) {
            const otherNames = otherNamesWrapper.querySelectorAll(".artist-other-name");

            this.sort(document, otherNames, otherNamesWrapper);
        }

        for (const col of document.querySelectorAll(".other-names-column")) {
            const otherNames = col.querySelectorAll(".artist-other-name");

            this.sort(document, otherNames, col);
        }
    }
}

if (GM || GM_info) {
    SortArtistNames.init(document);
}
Some URLs to experiment with
Heuristic

Scoring:

  • Increase score of the name if it is found as prefix in other names.
  • Reduce score of the garbage names.

Garbage detector rules:

  • Names with common suffixes (events, seeking jobs/comms, sns junk).
  • Names staring or ending with emoji.

After garbage detection and scoring is done, rebuild name list based on the score from highest to lowest (if score for two names is the same - use alphabetic order).

A button (top right corner) to hide already voted (faved) posts from the current page. "Show score" must be enabled for this feature to work.
https://raw.githubusercontent.com/ImoutoChan/iqdb-autoselect/master/danbooru-hide-favorited-posts.js

A slightly personal way to discover and explore tags on danbooru. Buttons (top right corner) that append 'order:favcount age:..6month -animated' or 'order:score age:..6month -animated' to current tags. It wouldn't work unless you have a high-tier account because of the tags limit. But you can always modify the script and personalize it (remove -animated for example).
https://raw.githubusercontent.com/ImoutoChan/iqdb-autoselect/master/danbooru-recent-top-posts.js

Oniii-chan said:

A button (top right corner) to hide already voted (faved) posts from the current page. "Show score" must be enabled for this feature to work.
https://raw.githubusercontent.com/ImoutoChan/iqdb-autoselect/master/danbooru-hide-favorited-posts.js

As an alternative to this, if you're just interested in highlighting which posts you have already favorited, you can instead use a custom CSS. For example, the CSS on forum #132077 will dim out favorited posts unless you hover over them.

Brightlight said:

The HTML attribute used by that custom CSS has been removed. →forum #178506

Oh, right. It still works for me, so I had forgotten that. I added that back in with my DisplayPostInfo userscript (topic #15926). To enable it, the post_favorites_enabled has to be checked underneath the userscript settings (User Settings >> Userscript Menus >> DisplayPostInfo >> Information Settings). Once set, it does nothing more than add the data-is-favorited attribute back to the post, so that the user can customize things however they want with their custom CSS.

Add sequential paginator buttons in addition to numbered.

UserScript
// ==UserScript==
// @name         Add sequential paginator
// @description  Add extra set of prev/next buttons but with sequential pagination
// @namespace    https://danbooru.donmai.us/
// @version      0.2
// @match        https://*.donmai.us/*
// @run-at       document-end
// @grant        none
// ==/UserScript==

class AddSequentialPagination {
    static getMinMaxID(document) {
        const articles = [
            ...document.querySelectorAll("article[data-id]"),
            ...document.querySelectorAll("tr[data-id]"),
        ];
        const ids = articles.map((a) => parseInt(a.dataset.id));

        if (ids.length === 0) {
            return [null, null];
        }

        return [
            ids.reduce((p, v) => ((p < v) ? p : v)),
            ids.reduce((p, v) => ((p > v) ? p : v)),
        ];
    }

    static init(document) {
        const numberedPaginator = document.querySelector(".numbered-paginator");

        if (!numberedPaginator || numberedPaginator.dataset.sequential) {
            return;
        }

        const [minId, maxId] = this.getMinMaxID(document);

        if (!(minId && maxId)) {
            return;
        }

        const prev = document.createElement("a");
        const next = document.createElement("a");
        const prevUrl = new URL(window.location);
        const nextUrl = new URL(window.location);

        prevUrl.searchParams.set("page", `a${maxId}`);
        nextUrl.searchParams.set("page", `b${minId}`);

        prev.class = "paginator-prev";
        prev.dataset.shortcut = "shift+a shift+left";
        prev.textContent = "a"
        prev.href = prevUrl;

        next.class = "paginator-next";
        next.dataset.shortcut = "shift+d shift+right";
        next.textContent = "b"
        next.href = nextUrl;

        numberedPaginator.dataset.sequential = true;
        numberedPaginator.prepend(prev);
        numberedPaginator.append(next);
    }
}

if (GM || GM_info) {
    AddSequentialPagination.init(document);
}

Updated

I have some questions about scripting (Js) on the website, is anyone knowledgeable about that? Specifically Image Board Enhancer greasyfork userscript. I've made some modifications that seems to work almost perfectly in chrome, but when I switch to firefox, the addon doesnt work at all. My changes are here, i would love if someone who knows Js better could take a look at why it might work in Chrome but not Firefox. <3 thanks

Also, even though it works in chrome, i do have this tiny issue with one of the icons being way too small, which i have no idea why. https://github.com/vscum/Image-Board-Enhancer-Plus/blob/main/IBE%20Problem.PNG?raw=true

1 2 3 4 5 6 7 8 9 10