[Userscript] Bulk Tag Editor

🐴
Magnificent Metadata Maniac - #1 Assistant
Solar Guardian - Refused to surrender in the face of the Lunar rebellion and showed utmost loyalty to the Solar Empire (April Fools 2023).
Non-Fungible Trixie -
Magical Inkwell - Wrote MLP fanfiction consisting of at least around 1.5k words, and has a verified link to the platform of their choice

IRL 🎠 stallion
I can’t seem to get this to work with the FF Violentmonkey edition. The “tag edit” button is there, but it does nothing when I click it. Tried under both Mac and Debian.
Background Pony #8E64
@Marker
Could you put this derpi thread here in the description or so in the script itself?
To ease finding it again in the future
Background Pony #BFC6
I have some proposed revisions:
  const booruDefault = {
...
    forbiddenRedirPath: '/', //new
    hasTagError: (str) => { //is this the best way to do this?  I dunno, I'm barely a hobbyist
      const dom = new DOMParser().parseFromString(str, 'text/html');
      return {
        errored: [...dom.getElementsByClassName('alert-danger')]
                 .some((ele) => ele.textContent.includes('Oops, something went wrong!')),
        messages: [...dom.getElementsByClassName('help-block')]
                  .map((ele) => ele.textContent),
      }; },
  async function throttle(fn, ...args) {
...
    try { //because we're throwing errors around
      const result = await new Promise(resolve => {
        window.setTimeout(() => {
          resolve(fn(...args));
        }, timeRemain);
      });
      GM_setValue(key, Date.now());
      return result;
    } finally {
      GM_setValue(key, Date.now()); //still need to do this while throwing errors around
    }
  async function bulkApplyTags(tagsToAdd, tagsToRemove) {
...
        await throttle(submitEdit, id, oldTags, newTags);
      } catch (err) {
        errors += 1;
// Also do stuff like styling thumbnails based on what the specific error is
      } finally {
  async function submitEdit(id, oldTags, newTags) {
...
    const abort = new AbortController(); //new
    const resp = await fetch(path, {
      method: 'POST',
      body: form,
      signal: abort.signal, //new
    });
    if (resp.status !== 200) {
      throw new Error('status code: ' + resp.status);
    } else if (resp.redirected) { //all new
      abort.abort();
      if ((window.location.origin + getBooruParam('forbiddenRedirPath')) == (resp.url))
        throw new Error('forbidden');
      else
        throw new Error('redirected');
    }
    const body = await resp.text();
    const errors = getBooruParam('hasTagError')(body);
    if (errors.errored) throw new Error('prevented', { cause: errors.messages });
The abort is because there’s no reason to load Derpibooru’s index page if tagging fails, saving 30-some KB, which is nothing in this day and age but it’s the principle of it…

1. If the tag change is rejected, count it as an error:
  • Although it could be thought of as “successfully failing” ie. working as intended vs. an error, not applying a change is an event a user* might want to follow up on.
    *me
    • Future features: highlight the images that the errors occurred on, with styles uniquely communicating which kind of failure occurred for them.
  • If the tag change is prevented due to permissions (such as tags being locked), philomena currently responds with a 302 redirect to ‘/’. fetch will resolve it as a status 200 with .redirect = true and .url as location.origin + '/'
    • I don’t see a way to know that a post’s tags are locked via the API or the data on the thumbnails page:
      • It can be found out in the aforementioned way, or
      • It could be found out by making yet another fetch, for the image’s page itself (not its API) and looking for <div class=\"js-imageform hidden\"><p>You can't edit the tags on this image. </p></div>.
  • If the tag change is prevented due to ratelimit violation, philomena responds with a 302 redirect to the referer
    • I couldn’t figure out a way to get the string that will be sent as the referrer without making a lot of janky assumptions; window.location.origin + window.location.pathname + window.location.search seems to be the default…but perhaps some other user extension would alter that, and I couldn’t find a way to get the actual referer string that will be sent by fetch’s Request - it just returns about:client verbatim.
    • So, instead, test for resp.redirected and hope that no other philomena implementation uses redirects for successful API responses.
  • Throw unique errors in these cases. As mentioned above, it might be nice to present which images encountered tagging errors, and what sort of tagging error (as you might just try again if it’s a network or ratelimit violation, vs. pursuing mod assistance with tag locked images.)
2. If the tag change is invalid, eg. trying to assign both safe and suggestive, philomena rejects the entire attempt and that should also be detected as an error.
  • When trying to apply invalid tags combos from the normal web form, the response contains a bunch of HTML (to replace certain elements on the page) and includes text like Oops, something went wrong! Please check the errors below. which could be detected. However, for reasons I can’t figure out, when making the requests through the userscript, even if I set all the headers and payload to be exactly the same, I find response body is empty/absent. Looks like a quirk(?) of the Chrome network tab - if I await and access resp.text() in the script, suddenly the response shows up.
    • Any idea why that is? Currently, I don’t have an idea how to detect that type of failure.
    • Instead, the tag validation could be re-created to proactively perform on the client-side in lieu of looking for it in responses.
Interested in advertising on Derpibooru? Click here for information!
Techy Cutie Pony Collection!

Help fund the $15 daily operational cost of Derpibooru - support us financially!

Syntax quick reference: **bold** *italic* ||hide text|| `code` __underline__ ~~strike~~ ^sup^ %sub%

Detailed syntax guide