AO3: [Wrangling] Highlight Bins with Overdue Tags

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

ของเมื่อวันที่ 01-05-2022 ดู เวอร์ชันล่าสุด

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         AO3: [Wrangling] Highlight Bins with Overdue Tags
// @namespace    https://greatest.deepsurf.us/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://greatest.deepsurf.us/scripts/381543
//      - "n-in-1 Filters" at https://greatest.deepsurf.us/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;
}