RYM: Weighted average track rating (updated)

calculate weighted average track rating based on track lengths

// ==UserScript==
// @name       RYM: Weighted average track rating (updated)
// @version    2.4.2
// @description  calculate weighted average track rating based on track lengths
// @match      https://rateyourmusic.com/release/*
// @match      https://rateyourmusic.com/song/*
// @copyright  2021, w_biggs (originally by thought_house)
// @namespace https://greasyfork.org/users/170755
// ==/UserScript==

const addWeightedAvg = function addWeightedAvg(selector, trackLengths) {
  const areas = document.querySelectorAll(selector);
  areas.forEach((area) => {
    const weightedRow = area.querySelector('.avg-rating');
    if (weightedRow) {
      weightedRow.remove();
    }

    let rating = 0;
    let rawRating = 0;
    let trackCount = 0;
    let rated = 0;

    let allFound = true;

    const tracks = area.querySelectorAll('.tracklist_line');
    tracks.forEach((track) => {
      const n = track.querySelector('.tracklist_num').textContent.trim();
      const name = track.querySelector('.tracklist_title').textContent.trim();

      let starsVal = 0;


      const ratingNum = track.querySelector('.rating_num');
      if (ratingNum) {
        starsVal = parseFloat(ratingNum.textContent.trim(), 10);
      } else {
        const starsImg = track.querySelector('.track_rating_disp img');
        if (starsImg) {
          starsVal = parseFloat(starsImg.getAttribute('title'), 10);
        }
      }

      if (starsVal > 0) {
        if (n in trackLengths && trackLengths[n].name.includes(name)) {
          rating += trackLengths[n].duration * starsVal;
          rated += trackLengths[n].duration * 1;
        } else {
          allFound = false;
        }

        rawRating += starsVal;
        trackCount += 1;
      }
    });

    let unweightedRating = rawRating / trackCount;

    let weightedRating = 0;

    if (allFound) {
      weightedRating = rating / rated;
    }

    let avgHTML = '';

    if (unweightedRating > 0) {
      avgHTML += 'Average: <b>' + unweightedRating.toFixed(2) + '</b>';
    }
    if (weightedRating > 0) {
      avgHTML += ', Weighted: <b>' + weightedRating.toFixed(2) + '</b>';
    }

    const row = document.createElement('li');
    row.classList.add('track');
    row.classList.add('avg-rating');
    const text = document.createElement('span');
    text.style = 'float:right;margin-right:10px;color:var(--mono-2);font-size:1.1em;margin-top:6px;margin-bottom:6px;';
    text.innerHTML = avgHTML;
    row.appendChild(text);
    area.appendChild(row);
  });
};

const initRatings = function initRatings() {
  const tracklistTracks = document.querySelectorAll('#tracks li.track');

  const trackLengths = {};

  let prevNum = 0;

  tracklistTracks.forEach((track) => {
    const numEl = track.querySelector('.tracklist_num');

    // skip non track rows
    if (numEl) {
      const num = numEl.textContent.trim();

      const nameEl = track.querySelector('.tracklist_title');
      let name = nameEl.textContent.trim();

      const durationEl = track.querySelector('span.tracklist_duration');
      let duration = 0;

      if (durationEl) {
        duration = parseInt(durationEl.dataset.inseconds, 10);
        name = name.replace(new RegExp(`${durationEl.textContent.trim()}$`, 'g'), '').trim();
      }

      // if track number is blank, add duration to last song with a track number
      if (num === '') {
        // exclude silence tracks or first track cd titles
        if (!(name.includes('[silence]')) && prevNum !== 0) {
          trackLengths[prevNum].duration += duration;
        }
      } else {
        trackLengths[num] = {
          name,
          duration,
        };
        prevNum = num;
      }
    }
  });

  console.log(trackLengths);

  addWeightedAvg('#track_ratings', trackLengths);
  addWeightedAvg('.track_rating_hide ul', trackLengths);

  const catalogList = document.getElementById('catalog_list');
  if (catalogList) {
    const catalogObserver = new MutationObserver(function(records, observer) {
      records.forEach((mutation) => {
        if (mutation.type === 'childList') {
          addWeightedAvg('.track_rating_hide ul', trackLengths);
        }
      });
    });

    catalogObserver.observe(catalogList, {
      childList: true,
      attributes: false,
      subtree: false
    });
  }


  const ratingStarButtons = document.querySelectorAll('.my_catalog_rating');
  ratingStarButtons.forEach((button) => {
    button.addEventListener('click', function() {
      addWeightedAvg('#track_ratings', trackLengths);
    });
  });
};

if (document.readyState === 'complete') {
  initRatings();
} else {
  window.addEventListener('load', function() {
    initRatings();
  });
}