AO3: [Wrangling] Highlight Bins with Overdue Tags

Highlight a bin on the Wrangling Home if the oldest tag in it is overdue

目前为 2022-05-01 提交的版本。查看 最新版本

// ==UserScript==
// @name         AO3: [Wrangling] Highlight Bins with Overdue Tags
// @namespace    https://greasyfork.org/en/users/906106-escctrl
// @description  Highlight a bin on the Wrangling Home if the oldest tag in it is overdue
// @author       escctrl
// @version      0.1
// @match        *://*.archiveofourown.org/tag_wranglers/*
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js
// @license      MIT
// ==/UserScript==


// ******* CONFIGURATION OPTIONS *******

// set which fandoms should be checked
//   options: "" = all, "solo" = solo-wrangled, or "shared" = co-wrangled
//   this works only if you already use one of the filtering scripts:
//      - "Wrangling Filter Redux" at https://greasyfork.org/scripts/381543
//      - "n-in-1 Filters" at https://greasyfork.org/en/scripts/430805
//   if you're using other filtering scripts, or none at all, it will always age-check all unwrangled bins
var filter_fandoms = "solo";

// add here how you'd like the link and/or the cell to appear, e.g. bold text on the link, yellow cell background color
const css_link = "font-weight: bold;";
const css_cell = "";

// set age at which tags are considered overdue, e.g. 14 days or 1 month
const max_age_days   = 0;
const max_age_months = 1;

// wait duration between checking individual bins - set this number higher if you often run into Retry Later
//   defined in milliseconds, e.g. 3000 = 3 seconds
const interval = 3000;

// ******* ********************* *******

(function($) {

    // for later comparison, the current date - 1 month
    const agedOut = createDate(max_age_days*-1, max_age_months*-1, 0);

    // some CSS to make aged-out links bold
    $('<style type="text/css"> td.has_agedout a { '+ css_link +' } td.has_agedout { ' + css_cell + ' } #agecheck-container { font-size: smaller; } </style>').appendTo($('head'));

    // we need to wait for the document to finish loading, so the added paragraph from the filtering scripts has loaded
    $(document).ready(function(){
        'use strict';

        // sanitize config - depending on filtering script, find the corresponding css classes
        var filters_appl = $('.assigned tbody tr');
        switch (filter_fandoms) {
            case "solo":
                if ($(filters_appl).filter('.solo-fandom').length > 0) { filter_fandoms = '.solo-fandom'; }           // standard and redux
                else if ($(filters_appl).filter('.solo-wrangled').length > 0) { filter_fandoms = '.solo-wrangled'; }  // n-in-1
                break;
            case "shared":
                if ($(filters_appl).filter('.shared-fandom').length > 0) { filter_fandoms = '.shared-fandom'; }       // standard and redux
                else if ($(filters_appl).filter('.co-wrangled').length > 0) { filter_fandoms = '.co-wrangled'; }      // n-in-1
                break;
            default:
                filter_fandoms = '';
                break;
        }

        // Add a link to the page
        $('.assigned p:first-of-type').append('&nbsp;&nbsp;&bullet;&nbsp;&nbsp;<span id="agecheck-container"><a id="agecheck-fandom">check for overdue tags</a></span><span id="agecheck-jail" style="display: none;">false</span>');

        // assign the function to the click event (will trigger only once user clicked the link)
        $('.assigned p a#agecheck-fandom').click(runAgeCheck);

    });

    // is triggered on click of the link
    function runAgeCheck() {
        // remove the link so it can't be pressed again
        var agecheck_progress = $('.assigned p #agecheck-container');
        $(agecheck_progress).html("checking age...");

        // select the bins which should be checked (config) and have unwrangled tags (contain an <a> element)
        var bins = $('.assigned tbody tr'+ filter_fandoms +' td[title~="unwrangled"]').has('a');

        // update user on progress with X of Y message
        $(agecheck_progress).append(' <span id="agecheck-loop"></span> of ' + $(bins).length);

        // loop through the unwrangled bins
        $(bins).each(function(i, bin) {

            // set a delay between bin checks to reduce chances of jail
            setTimeout(function() {

                // if previous loops hit Ao3 Jail, don't try checking age anymore
                if ( $('.assigned p #agecheck-jail').text() == "true") {
                    console.log('previously received "Retry later" response, skipped check on bin #'+i);
                    return false;
                }

                // update user on progress
                $(agecheck_progress).find('#agecheck-loop').text(i+1);

                // need to be sure the URL is "pure" and hasn't been edited by other scripts
                //   [0] is now the /tags/FANDOM/wrangle
                //   [1] is the rest which contains the filters and sort orders
                var binLink = $(bin).find("a").attr("href").split("?");
                // find the show=X and status=X parts of the URL which we'll need for our check
                // then join the whole thing back together - the bin and parameters we need in the URL
                binLink = binLink[0] + "?" + binLink[1].match(/(show=\w*|status=\w*)/ig).join("&");

                // load the bin's first page sorted by age
                $.get(binLink + '&sort_column=created_at&sort_direction=ASC', function(response) {
                    // nothing to do here, all interactions are in done() and failed()

                }).done(function(response) {
                    // from the response, pick the first row/tag and check it's created date
                    var tagCreated = new Date($(response).find('#wrangulator tbody tr:first-of-type td[title="created"]').text());

                    // if creation date is older than our maximum allowed age, add a CSS class to the row and the cell
                    if (tagCreated < agedOut) {
                        $(bin).addClass('has_agedout');
                        $(bin).parent().addClass('has_agedout');
                    }

                // thanks to Przemysław Sienkiewicz on Stackoverflow for the code to catch error responses https://stackoverflow.com/a/40256829
                }).fail(function(data, textStatus, xhr) {
                    //This shows status code eg. 429
                    console.log("bin #"+i+" error", data.status);
                    //This shows status message eg. Too Many Requests
                    //console.log("bin #"+i+" STATUS: "+xhr);

                    // update user on the issue
                    $(agecheck_progress).html('age check has hit "Retry later", sorry!');
                    $('.assigned p #agecheck-jail').text("true"); // set it in DOM so next delayed loops can skip
                    return false;
                });

                if (bins.length == i+1) {
                    // progress update to user: last iteration, we're done!
                    $('.assigned p #agecheck-container').html("age check complete");
                }

            // the each() loops immediately and creates all timeout calls (async) at once, so we need to stagger them
            // by multiplying the 3s delay by the loop number (bin #0 = 3000*0, bin #1 = 3000*1, bin #2 = 3000*2, etc)
            }, interval*i);

        });

    }


})(jQuery);

// convenience function to be able to pass minus values into a Date, so JS will automatically shift correctly over month/year boundaries
// thanks to Phil on Stackoverflow for the code snippet https://stackoverflow.com/a/37003268
function createDate(days, months, years) {
    var date = new Date();
    date.setFullYear(date.getFullYear() + years);
    date.setMonth(date.getMonth() + months);
    date.setDate(date.getDate() + days);
    return date;
}