// ==UserScript== // @name AO3: Badge for Unread Inbox Messages // @namespace https://greasyfork.org/en/users/906106-escctrl // @version 3.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://cdn.jsdelivr.net/npm/webix@11.1.0/webix.min.js // @require https://update.greasyfork.icu/scripts/491888/1355841/Light%20or%20Dark.js // @grant none // @downloadURL https://update.greasyfork.icu/scripts/474892/AO3%3A%20Badge%20for%20Unread%20Inbox%20Messages.user.js // @updateURL https://update.greasyfork.icu/scripts/474892/AO3%3A%20Badge%20for%20Unread%20Inbox%20Messages.meta.js // ==/UserScript== /* global webix, $$, lightOrDark */ (async function() { 'use strict'; // utility to reduce verboseness const qs = (selector, node=document) => node.querySelector(selector); const qa = (selector, node=document) => node.querySelectorAll(selector); const cfg = 'unread_inbox'; // name of dialog and localstorage used throughout const defaults = [{key: "badgeInterval", val: 12}, {key: "badgeColor", val: '#FFD700'}, {key: "badgeIcon", val: 1}, {key: "dashFilter", val: 1}]; const storedConfig = getConfig('stored'); // returns object with key: value pairs // first question: is the user logged in? if not, don't bother with any of this let linkDash = qs("#greeting p.icon a").href || ""; if (linkDash === "") { localStorage.removeItem(cfg+'_count'); localStorage.removeItem(cfg+'_date'); return; } if ( linkDash.includes('?')) linkDash = linkDash.slice(0, linkDash.indexOf('?')); // fix on FAQ pages containing a searchParam qs("head").insertAdjacentHTML('beforeend', ``); // 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 let count = 0; let pageURL = new String(window.location); if (pageURL.includes(linkDash)) { // grab unread msgs # from the sidebar count = (pageURL.includes("/inbox")) ? qs("div#dashboard li span.current").innerHTML : qs("div#dashboard a[href$='inbox']").innerHTML; count = count.match(/\d+/)[0]; // change sidebar inbox link as well to filtered if (storedConfig.dashFilter === 1 && !pageURL.includes("/inbox")) qs("div#dashboard a[href$='inbox']").href = linkInbox; } // 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, storedConfig.badgeInterval*-1, 0, 0, 0); // hours before that's max allowed // if not recent enough, we have to start a background load; otherwise we use what was stored count = (timeStored < timeNow) ? await getUnreadCount(linkDash) : (localStorage.getItem('unread_inbox_count') || 0); } // store the current value with the current date localStorage.setItem(cfg+'_count', count); localStorage.setItem(cfg+'_date', new Date()); // add a little round badge to the user icon in the menu (if there are unread emails) // icon SVGs from https://heroicons.com (MIT license Copyright (c) Tailwind Labs, Inc. https://github.com/tailwindlabs/heroicons/blob/master/LICENSE) const displaytext = (storedConfig.badgeIcon === 1) ? ` ${count}` : `Inbox (${count})`; if (count != "0") qs("#greeting p.icon").insertAdjacentHTML('afterbegin', `${displaytext}`); // function to grab the count of unread inbox messages if we're viewing a page that doesn't have a dashboard async function getUnreadCount(url) { try { let response = await fetch(url); if (!response.ok) throw new Error(`HTTP error: ${response.status}`); // the response has hit an error eg. 429 retry later else { let txt = await response.text(); let parser = new DOMParser(); // Initialize the DOM parser let unread = qs("div#dashboard a[href$='inbox']", parser.parseFromString(txt, "text/html")); // Parse the text into HTML and grab the unread count if (!unread) throw new Error(`response didn't contain inbox count\n${txt}`); // the response has hit a different page e.g. a CF prompt else { unread = unread.innerHTML; return unread.match(/\d+/)[0]; } } } catch(error) { // in case of any other JS errors console.log("[script] Badge for Unread Inbox Messages encountered an error", error.message); return '[ERROR]'; } } /***************** CONFIG DIALOG *****************/ // if no other script has created it yet, write out a "Userscripts" option to the main navigation if (qa('#scriptconfig').length === 0) { qa('#header nav[aria-label="Site"] li.search')[0] // insert as last li before search .insertAdjacentHTML('beforebegin', `