AO3: [Wrangling] Highlight Bins with Overdue Tags

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

  1. // ==UserScript==
  2. // @name AO3: [Wrangling] Highlight Bins with Overdue Tags
  3. // @namespace https://greasyfork.org/en/users/906106-escctrl
  4. // @description Highlight a bin on the Wrangling Home if the oldest tag in it is overdue
  5. // @author escctrl
  6. // @version 2.0
  7. // @match *://*.archiveofourown.org/tag_wranglers/*
  8. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.7.0/jquery.min.js
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. // ******* CONFIGURATION OPTIONS *******
  13.  
  14. // speed in which the bins are checked (in seconds)
  15. // set this number higher if you run into Retry Later errors often
  16. const interval = 3;
  17.  
  18. // 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
  19. const css_link = `font-weight: bold; position: relative; z-index: 1`;
  20. const css_link_before = `background-color: #ffdf35; content: ""; position: absolute; width: calc(100% + 4px); height: 60%; right: -2px; bottom: 2px; z-index: -1; transform: rotate(-8deg);`;
  21. const css_cell = "";
  22.  
  23. // ********* END CONFIGURATION *********
  24.  
  25. (function($) {
  26. 'use strict';
  27.  
  28. // some CSS to make this look palatable
  29. $('head').append(`<style type="text/css">#age-check { font-size: 80%; padding: 0.2em; } #age-check[disabled] { opacity: 80%; }
  30. td a.has_agedout { ${css_link} } td a.has_agedout::before { ${css_link_before} } td.has_agedout { ${css_cell} }</style>`);
  31.  
  32. // add a button to start checking
  33. $('.assigned table thead tr:nth-child(1) th:nth-child(3)').append(
  34. ` <button id='age-check' type='button'><span id='age-status'>Check Age</span><span id='age-progress'></span></button>`);
  35. $('#age-check').on('click', () => { startCheck(); });
  36.  
  37. let maxage = createDate(0, -1, 0); // one month ago
  38.  
  39. // load sessionStorage (remember what we've checked while this tab is open)
  40. let agedout_stored = JSON.parse(sessionStorage.getItem('overdue_bin')) || [];
  41. // load snoozed tags in case the script is installed
  42. let snoozed = new Map(JSON.parse(localStorage.getItem('tags_saved_date_map') || "[]"));
  43. $('.assigned tbody td[title~="unwrangled"] a').each((i, a) => {
  44. // build the same "FANDOM/TAGTYPE" text that's stored for easy comparison
  45. let bin = $(a).attr('href').match(/tags\/(.*?)\/wrangle.*show=(characters|relationships|freeforms)/i);
  46. bin = bin[1] + '/' + bin[2];
  47. if (agedout_stored.includes(bin)) {
  48. // show those as outdated already on pageload (will be overwritten by later checks on buttonclick)
  49. $(a).addClass('has_agedout');
  50. $(a).parent().addClass('has_agedout');
  51. }
  52. });
  53.  
  54. function startCheck() {
  55. // select all the bins with unwrangled tags
  56. let bins = $('.assigned tbody tr:visible td[title~="unwrangled"] a').toArray();
  57.  
  58. // set a loading indicator to user
  59. $('#age-check').attr('disabled', true);
  60. $('#age-status').text('Checking ');
  61. $('#age-progress').text(bins.length+' bins');
  62.  
  63. performCheck(bins);
  64. }
  65.  
  66. function performCheck(bins) {
  67. setTimeout(() => {
  68. // bins is an array of <a> Nodes
  69. $('#age-progress').text(bins.length+' bins');
  70.  
  71. // build the URL to check (oldest tag on first page at the top)
  72. let link = new URL($(bins[0]).prop('href'));
  73. let xhrlink = link.protocol + '//' + link.hostname + link.pathname +
  74. `?show=${link.searchParams.get('show')}&status=unwrangled&sort_column=created_at&sort_direction=ASC`;
  75.  
  76. // check the bin for old tags
  77. $.get(xhrlink, () => {}).done((response) => {
  78.  
  79. // find the first tag that isn't snoozed and check its age
  80. let rows = $(response).find('#wrangulator tbody tr');
  81. for (let row of rows) {
  82. let tagName = $(row).find('th label').text();
  83. if (snoozed.has(tagName)) continue; // skip this tag if it's been snoozed and check the next-oldest instead
  84. else {
  85. let tagCreated = new Date($(row).find('td[title="created"]').text());
  86. setAgeCSS(bins[0], (tagCreated < maxage));
  87. break; // this was the oldest un-snoozed tag, don't need to look any further
  88. }
  89. }
  90.  
  91. bins.shift(); // removes the first node we just checked
  92.  
  93. if (bins.length == 0) finishCheck('Recheck Age'); // if we're done, tell so
  94. else performCheck(bins); // start next loop
  95.  
  96. }).fail(function(data, textStatus, xhr) {
  97. //This shows status code eg. 429
  98. console.log("Bins AgeCheck: bin "+xhrlink+" error", data.status);
  99. finishCheck('Error :( Try Again');
  100. });
  101.  
  102. }, interval * 1000, bins);
  103. }
  104.  
  105. function finishCheck(btnText) {
  106. // update the button appropriately
  107. $('#age-check').attr('disabled', false);
  108. $('#age-status').text(btnText);
  109. $('#age-progress').text('');
  110.  
  111. // save the latest checked list for the moment (while the tab remains open)
  112. let outdated_list = [];
  113. $('table a.has_agedout').each((i, e) => {
  114. let link = $(e).attr('href').match(/tags\/(.*?)\/wrangle.*show=(characters|relationships|freeforms)/i);
  115. link = link[1] + '/' + link[2];
  116. outdated_list.push(link);
  117. });
  118. // stores an array of "FANDOM/TAGTYPE" strings
  119. sessionStorage.setItem('overdue_bin', JSON.stringify(outdated_list));
  120. }
  121.  
  122. function setAgeCSS(a, outdated) {
  123. var ageClass = (outdated) ? 'has_agedout' : 'not_agedout';
  124.  
  125. // reset CSS classes on <a> and on its parent <td>, then set the class we actually want
  126. $(a).removeClass('has_agedout not_agedout').addClass(ageClass);
  127. $(a).parent().removeClass('has_agedout not_agedout').addClass(ageClass);
  128. }
  129.  
  130. // migration: removing old Storage that won't be used anymore
  131. localStorage.removeItem('ao3jail');
  132. localStorage.removeItem('agecheck_old');
  133. localStorage.removeItem('agecheck_new');
  134.  
  135.  
  136. })(jQuery);
  137.  
  138. function createDate(years, months, days) {
  139. let date = new Date();
  140. date.setFullYear(date.getFullYear() + years);
  141. date.setMonth(date.getMonth() + months);
  142. date.setDate(date.getDate() + days);
  143. return date;
  144. }