Greasy Fork is available in English.

AO3: Badge for Unread Inbox Messages

puts a little notification badge in the menu for unread messages in your AO3 inbox

// ==UserScript==
// @name         AO3: Badge for Unread Inbox Messages
// @namespace    https://greasyfork.org/en/users/906106-escctrl
// @version      1.1
// @description  puts a little notification badge in the menu for unread messages in your AO3 inbox
// @author       escctrl
// @match        https://*.archiveofourown.org/*
// @license      MIT
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.7.0/jquery.min.js
// @grant        none
// ==/UserScript==

/****************** CONFIGURATION ******************/

// how often the script will check for unread messages (in hours)
const REFRESH_INTERVAL = 12;

// if the badge should show as an icon (true), or as text (false)
const BADGE_ICON = true;

// pick a background color for the badge to stand out more, or leave empty quotes ""
const HIGHLIGHT_COLOR = "gold";

// if the inbox link in the sidebar should automatically filter to unread messages only
const FILTER_INBOX = false;


// ****************** NOTE ON LOCAL STORAGE ******************
// For compatibility between userscript managers, this script uses local storage, which is visible from the Developer console.
// If you ever uninstall the script, unfortunately its data can't be automatically deleted.
// If you want to remove the data it sets, (1) visit archiveofourown.org, (2) go into the Developer console,
// (3) look for the Local Storage (4) and delete the entries for "unread_inbox_count" and "unread_inbox_date".
// The script also removes its data if you ever visit AO3 while logged out.


(function($) {
    'use strict';

    // first question: is the user logged in? if not, don't bother with any of this
    const linkDash = $("#greeting p.icon a").attr('href') || "";
    if (linkDash === "") {
        localStorage.removeItem('unread_inbox_count');
        localStorage.removeItem('unread_inbox_date');
        return;
    }

    var highlight_css = (HIGHLIGHT_COLOR !== "") ? `#greeting #inboxbadge { background-color: ${HIGHLIGHT_COLOR}; border-radius: .25em; }` : "";

    $("head").append(`<style type="text/css"> #inboxbadge .iconify { font-family: FontAwesome, sans-serif; }
        a#inboxbadge { display: block; padding: .25em .75em !important; text-align: center; float: left; margin: 0 1em; line-height: 1.286; height: 1.286em; }
        p.icon a { float: right; } ${highlight_css}</style>`)
        .prepend(`<script src="https://use.fontawesome.com/ed555db3cc.js" />`);

    // build a new inbox link (filtered to unread)
    const linkInbox = linkDash + "/inbox?filters[read]=false&filters[replied_to]=all&filters[date]=desc&commit=Filter";

    // the fun begins: on a page where we're seeing the unread msgs, we simply set the value
    var page_url = window.location.pathname;
    if (page_url.includes(linkDash)) {

        // grab unread msgs # from the sidebar
        var badge = (page_url.includes("/inbox")) ? $("div#dashboard li span.current").html() : $("div#dashboard a[href$='inbox']").html();
        badge = badge.match(/\d+/);

        // store the currently seen value with the current date, on every page visit, no questions asked
        localStorage.setItem('unread_inbox_count', badge);
        localStorage.setItem('unread_inbox_date', new Date());

        // change sidebar inbox link as well to filtered
        if (FILTER_INBOX) $("div#dashboard a[href$='inbox']").attr('href', linkInbox);

        printBadge();
    }
    // on other pages, we check if the stored value is recent enough, otherwise we load it again
    else {

        var timeStored = new Date(localStorage.getItem("unread_inbox_date") || '1970'); // the date when the storage was last refreshed
        var timeNow = createDate(0, 0, REFRESH_INTERVAL*-1, 0, 0, 0); // hours before that's max allowed

        // if recent enough, simply create the badge
        if (timeStored > timeNow) printBadge();

        // if not, we have to start a background load
        else {
            $.get(linkDash, function(response) {
            }).done(function(response) {

                // grab the number from within the response
                if ($(response).find("div#dashboard a[href$='inbox']").length > 0) {
                    var badge = $(response).find("div#dashboard a[href$='inbox']").html();
                    badge = badge.match(/\d+/);

                    // update the stored data with what we just received
                    localStorage.setItem('unread_inbox_count', badge);
                    localStorage.setItem('unread_inbox_date', new Date());

                    printBadge();
                }
                // the response has hit a different page e.g. a CF prompt
                else
                    console.log("[script] Badge for Unread Inbox Messages: ajax error", response);
            }).fail(function(data, textStatus, xhr) {
                //This shows status code eg. 429
                console.log("[script] Badge for Unread Inbox Messages: ajax error", data.status);
            });
        }
    }

    // add a little round badge to the user icon in the menu (if there are unread emails)
    // this is called as a function as it needs to run only when the async ajax page load has completed
    function printBadge() {
        const badge = localStorage.getItem('unread_inbox_count');
        const displaytext = (BADGE_ICON) ? `<span class="iconify">&#xf0e0;</span>&nbsp;&nbsp;${badge}` : `Inbox (${badge})`;
        if (badge != "0") $("#greeting p.icon").prepend(`<a id="inboxbadge" href="${linkInbox}" title="You have unread messages in you inbox">${displaytext}</a>`);
    }

})(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(secs, mins, hours, days, months, years) {
    var date = new Date();
    date.setFullYear(date.getFullYear() + years);
    date.setMonth(date.getMonth() + months);
    date.setDate(date.getDate() + days);
    date.setHours(date.getHours() + hours);
    date.setMinutes(date.getMinutes() + mins);
    date.setSeconds(date.getSeconds() + secs);
    return date;
}