IMDb - fix rating

Recompute a new film rating value based on your criterias

// ==UserScript==
// @name         IMDb - fix rating
// @namespace    https://github.com/Procyon-b
// @version      0.5.4
// @description  Recompute a new film rating value based on your criterias
// @author       Achernar
// @include      /^https:\/\/www\.imdb\.com\/title\/[^\/]+\/(episodes|reference|ratings)?(\?.*)?$/
// @match        https://www.imdb.com/chart/*
// @match        https://www.imdb.com/search/title/*
// @match        https://www.imdb.com/list/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM.registerMenuCommand
// @grant        GM_listValues
// @grant        GM_deleteValue
// @run-at       document-end
// ==/UserScript==

(function() {
"use strict";

var E, Ep, Er, rt, tv, opts, dialog,
  Layouts={
    'title':{
      L:'[data-testid="hero-rating-bar__aggregate-rating"]',
      rt:'span[class^="AggregateRatingButton__RatingScore-sc-"], [data-testid="hero-rating-bar__aggregate-rating__score"] span',
      tv:'div[class^="AggregateRatingButton__TotalRatingAmount-sc-"], [data-testid="hero-rating-bar__aggregate-rating__score"] ~ :last-child',
      divide:true,
      type:'page'
      },
    'reference':{
      L:'.titlereference-header .ipl-rating-star',
      rt:'.titlereference-header .ipl-rating-star .ipl-rating-star__rating',
      tv:'.titlereference-header .ipl-rating-star .ipl-rating-star__total-votes',
      tvF:function(tv,r) {
        if (!tv || !r) return;
        let vtv=r.querySelector('.vtv');
        if (!vtv) {
          let RE=/^(.*?)([^()]+)(.*)$/s.exec(tv.innerText);
          tv.innerHTML=(RE[1]||'')+'<span class="vtv">'+RE[2]+'</span>'+(RE[3]||'');
          vtv=tv.querySelector('.vtv');
          }
        return vtv;
        },
      type:'page'
      },
    'ratings':{
      L:'.subpage_title_block__right-column .ipl-rating-widget',
      rt:'.subpage_title_block__right-column .ipl-rating-widget .ipl-rating-star__rating',
      type:'page'
      },
    'EpLst':{
      L:'.eplist .list_item',
      rt:'.ipl-rating-widget .ipl-rating-star .ipl-rating-star__rating',
      tv:'.ipl-rating-widget .ipl-rating-star .ipl-rating-star__total-votes',
      tvF:function(tv,r) {
        if (!tv || !r) return;
        let vtv=r.querySelector('.vtv');
        if (!vtv) {
          let RE=/^(.*?)([^()]+)(.*)$/s.exec(tv.innerText);
          tv.innerHTML=(RE[1]||'')+'<span class="vtv">'+RE[2]+'</span>'+(RE[3]||'');
          vtv=tv.querySelector('.vtv');
          }
        return vtv;
        },
      nm:'a[itemprop="url"]:only-child, a.add-image ~ div',
      url:'a[itemprop="name"]',
      tit:'page',
      type:'list'
      },
    'Chart':{
      k:'chart',
      L:'.lister-list > tr',
      rt:'.ratingColumn.imdbRating > strong',
      url:'.titleColumn a[href*="/title/"]',
      titF:function(tit, r){
        if (!tit || !r) return;
        let n=tit.nextElementSibling;
        return tit.innerText + (n?' '+n.innerText:'');
        },
      tit:'item',
      type:'list'
      },
    'List':{
      k:'list',
      L:'.article.listo .lister-list > div',
      rt:'.ipl-rating-widget .ipl-rating-star__rating',
      tv:'.lister-item-content > p.text-muted.text-small > span[name="nv"]',
      url:'.lister-item-content a[href*="/title/"]',
      titF:function(tit, r){
        if (!tit || !r) return;
        let n=tit.nextElementSibling;
        return tit.innerText + (n?' '+n.innerText:'');
        },
      tit:'item',
      type:'list'
      },
    'Search':{
      k:'search',
      L:'.lister-list > div',
      rt:'.inline-block.ratings-imdb-rating > strong',
      tv:'.sort-num_votes-visible [name="nv"]',
      url:'.lister-item-content a[href*="/title/"]',
      titF:function(tit, r){
        if (!tit || !r) return;
        let n=tit.nextElementSibling;
        return tit.innerText + (n?' '+n.innerText:'');
        },
      tit:'item',
      type:'list'
      },
    'SearchCompact':{
      k:'search',
      L:'.lister-list > div',
      rt:'.col-imdb-rating > strong',
      url:'.lister-item-header a[href*="/title/"]',
      titF:function(tit, r){
        if (!tit || !r) return;
        let n=tit.nextElementSibling;
        return tit.innerText + (n?' '+n.innerText:'');
        },
      tit:'item',
      type:'list'
      }
    };

var XHRqueue=[], XHRqueueV, purging;

function rstXHRq() {
  XHRqueue=[];
  XHRqueueV=Date.now();
  }

var defOpts={
    voters: '',
    k10: false,
    k10_max: false,
    k10_maxmax: false,
    k9_pc_10: 20,
    k9_max_do: false,
    k9_max: false,
    k9_max_10: false,
    k10_9_pcOn: false,
    k10_9_pc: 90,
    k1: false,
    purge: 16,
    search: false,
    chart: false,
    list: false
    };

var List, rtQS, tvQS, nmQS, urlQS, tvF, LO;
// look for rates
for (let k in Layouts) {
  LO=Layouts[k];
  if (LO.type=='page') {
    E=document.querySelector(LO.L);
    if (E) break;
    continue;
    }
  List=document.querySelectorAll(LO.L);
  if (List.length) {
    rtQS=LO.rt;
    // rating found?
    if (!rtQS || (!List[0].querySelector(rtQS) && (List[1] && !List[1].querySelector(rtQS)) && (List[2] && !List[2].querySelector(rtQS)) ) ) {
      List=[]; continue;}
    tvQS=LO.tv;
    nmQS=LO.nm;
    urlQS=LO.url;
    tvF=LO.tvF;
    break;
    }
  }


function getOpts() {
  opts=GM_getValue('opts', {});
  opts= Object.assign({}, defOpts, opts);
}
getOpts();

addSt(`
.oldVal::before {
  content: var(--oldSc);
  font-size: 0.8em;
  margin-right: 0.6em;
  text-decoration: line-through brown;
  line-height: 1em;
  color: gray;
}
.oldVal.noMod::before {
  content: "=" !important;
  text-decoration: none !important;
}
.waiting.oldVal::before {
  content: "?" !important;
  text-decoration: none !important;
}
div[class^="AggregateRatingButton__ContentWrap-sc-"]:hover .oldVal::before,
.rating-bar__base-button:hover .oldVal::before,
.ipl-rating-star.small:hover .oldVal::before, .oldVal:hover::before {
  text-decoration: none !important;
}

[data-testid="hero-rating-bar__aggregate-rating"] {
  position: relative;
}
#fix_rating_opt {
  position: absolute;
  color: black;
  background-color: white;
  top: 5em;
  z-index: 10000 !important;
  font-size: 13px;
  font-family: arial;
  line-height: 1.4em;
  padding: 3px 8px;
  border: 2px solid gray;
  margin: 0 50%;
}
#fix_rating_opt.list {
  position: fixed;
}
#fix_rating_opt {
  white-space: nowrap;
}
#fix_rating_opt #close {
  float: right;
  color: red;
  cursor: pointer;
  z-index: 2;
  position: relative;
}
#fix_rating_opt input[type="number"] {
  width: 3em;
}
#fix_rating_opt input[type="checkbox"] {
  vertical-align: middle;
}
#fix_rating_opt .mrg {
  margin-left: 1em;
}
#fix_rating_opt button, #fix_rating_opt input[type="button"] {
  font-weight: initial;
  padding: 1px 6px;
  cursor: initial;
}
#fix_rating_opt b {
  font-weight: bolder;
}
#fix_rating_opt.list #reset ~ *:not(.show) {
  display: none;
}
#fix_rating_opt span {
  font-size: inherit !important;
  line-height: inherit !important;
}
#fix_rating_opt input[type="checkbox"] {
  margin: 0 3px;
}
#fix_rating_opt input, #fix_rating_opt button {
  margin: 0 0 1px 0;
}
#fix_rating_opt input[type="number"] {
  padding: 0 !important;
  line-height: 1em !important;
}
#fix_rating_opt hr {
  border: none !important;
  height: 0 !important;
  margin-block-start: 0.3em !important;
  margin-block-end: 0.3em !important;
}
#fix_rating_opt #cached {
  display: inline-block;
  vertical-align: top;
}
#fix_rating_opt [name="k9_max_do"]:not(:checked) + .mrg {
  display: none;
}
#fix_rating_opt #hdr {
  cursor: grab;
  background: lightgray;
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  padding: 3px 8px;
}
#fix_rating_opt.grabbed #hdr {
  cursor: grabbing;
}
`);

GM.registerMenuCommand('Settings', function(){
  showCfg();
  });

var trig=E || document.querySelector('h1.header');

if (trig) trig.addEventListener('click', function(ev){
  if (ev.ctrlKey && ev.altKey) {
    ev.preventDefault();
    ev.stopPropagation();
    showCfg();
    }
  }, {capture:true});

// init dialog
if (!E) if ( (!opts.search && (LO.k=='search'))
    || (!opts.chart && (LO.k=='chart'))
    || (!opts.list && (LO.k=='list'))
   ) {
  List=[];
  }


function getListRating() {
  if (!List || !List.length) return;
  rstXHRq();
  getOpts();
  let w=10;
  List.forEach(function(Ep){
    var url=urlQS && Ep.querySelector(urlQS),
      tit=(LO.titF && LO.titF(url,Ep)) || (url && url.innerText),
      nm=nmQS && Ep.querySelector(nmQS),
      num=nm? nm.innerText.replace(/^.*?(\d+).*?(\d+).*$/ms, '$1.$2') :'',
      rt=rtQS && Ep.querySelector(rtQS),
      tv=tvQS && Ep.querySelector(tvQS);

    if (!rt && !tv) return;
    if (tvF) tv=tvF(tv,Ep);
    if (rt && !decSep) {
      let RE=/^.*([\D]).*$/.exec(rt.innerText);
      if (RE) decSep=RE[1];
      }
    if (tv && !thSep) {
      let RE=/\d([\D])\d\d\d$/.exec(tv.innerText);
      if (RE) thSep=RE[1];
      }

    if (tit && (LO.tit=='page')) tit=document.title.replace(/ +- imdb *$/i,'')+(num?' - '+num:'')+' - '+tit;
    setTimeout(function(){display(url.pathname, rt, tv, tit);},w);
    w+=10;
    });
  }


var decSep, thSep;

if (E) {
  rt=document.querySelector(LO.rt);
  tv=document.querySelector(LO.tv);
  if (!rt) return;
  if (tv && LO.tvF) tv=LO.tvF(tv,E);
  if (rt && !decSep) {
    let RE=/^.*([\D]).*$/.exec(rt.innerText);
    if (RE) decSep=RE[1];
    }
  if (tv && !thSep) {
    let RE=/\d([\D])\d\d\d$/.exec(tv.innerText);
    if (RE) thSep=RE[1];
    }
  let RE=/^.*([\D]).*$/.exec(rt.innerText);
  if (RE) decSep=RE[1];
  // imdb script rewriting the values
  let obs=new MutationObserver(function(mutL){
    for (let mut of mutL) {
      if (mut.type=='characterData') {
        let n=mut.target.parentNode;
        if (n.curV && (n.curV != n.innerText)) {
          n.innerText=n.curV;
          }
        }
      }
    });
  obs.observe(E, {childList: false, subtree: true, characterData:true});

  display(location.pathname, rt, tv, '', {divide:LO.divide});
  }
else {
  getListRating();
  if (List.length) {
    var ec=document.querySelector('#episodes_content');
    if (ec) {
      new MutationObserver(function(mutL){
        if (mutL[0].addedNodes.length) {
          setTimeout(function(){
            List=document.querySelectorAll(LO.L);
            getListRating();
            },0);
          }
        }).observe(ec, {childList: true, subtree: false});
      }
    else if (ec=document.querySelector('.lister.list.detail.sub-list > .row.lister-working.hidden')) {
      new MutationObserver(function(mutL){
        if ( (mutL[0].attributeName == 'style') && (mutL[0].target.style.display == 'none') ) {
          setTimeout(function(){
            List=document.querySelectorAll(LO.L);
            getListRating();
            },0);
          }
        }).observe(ec, {childList: true, subtree: false, attributes: true});
      }
    }
  }

// init dialog
if (!dialog) display();


function addSt(s,t) {
  let st=document.createElement('style');
  try{
    (document.head || document.documentElement).appendChild(st);
    st.textContent=s;
  }catch(e){
    if (t) document.addEventListener('DOMContentLoaded',function(){addSt(s);});
    else setTimeout(function(){addSt(s,t);},0);
    }
  }

function showCfg() {
  let xy=E && E.getBoundingClientRect();
  if (E) dialog.style='top:'+(window.scrollY+parseInt(xy.y)+80)+'px;';
  else dialog.classList.add('list');
  document.body.appendChild(dialog);
  }


function display(href, rt, tv, tit='', o={}) {

function waiting(v) {
  rt && rt.classList.toggle('waiting', v);
  tv && tv.classList.toggle('waiting', v);
}

rt && rt.classList.add('oldVal');
tv && tv.classList.add('oldVal');

function sendXHR(a) {
  if (!a || !a.R) return;
  a.R.open('GET', '/title/'+a.id+'/ratings'+a.v);
  a.R.send();
  }

function getRatings(id, vt=opts.voters) {
//TMP new design 202305
vt='';

  waiting(true);
  var v, R=new XMLHttpRequest(), XHRqV=XHRqueueV;
  R.addEventListener('error',function(){
    if (XHRqV != XHRqueueV) return;
    setTimeout(function(){
      sendXHR(XHRqueue.shift());
      },0);
    });

  R.addEventListener('load',function(r){
    if (XHRqV != XHRqueueV) return;
    parse(this.responseText, vt);
    setTimeout(function(){
      sendXHR(XHRqueue.shift());
      },50);
    });

  switch(vt) {
    case '': v=''; break;
    case 'us': v='?demo=us_users'; break;
    case 'nus': v='?demo=non_us_users'; break;
    case 'top': v='?demo=top_1000_voters'; break;
    default: return;
    }

  if (!XHRqueue.length) {
    setTimeout(function(){ sendXHR(XHRqueue.shift()); },0);
    setTimeout(function(){ sendXHR(XHRqueue.shift()); },400);
    }
  XHRqueue.push({R,id,v});
  } // END getRatings()

var v, pc, tot, A={}, A0={}, A8={}, voters, votersL, h=[-1,-1], H=[], mod;

function fill_H(a) {
  H=[];
  for (let k in a) {
    H.push( ('000000000'+a[k].tot).substr(-10) +'_'+ ('0'+k).substr(-2) );
    }
  H.sort().reverse();
  for (let i in H) H[i]=[ parseInt(H[i].split('_')[1]), parseInt(H[i].split('_')[0])];
  }

function parse(s,voters='') {
  var parser=new DOMParser(), S=Date.now(),
    h=parser.parseFromString(s, 'text/html'),
    data=h.querySelectorAll('table td'),
    fData=h.querySelector('#__NEXT_DATA__'),
    R={};

  if (fData) {
    try {
      fData=JSON.parse(fData.textContent).props.pageProps.contentData.histogramData;
    }catch(e){fData=null;}
    }

  // extract rating value % tot
  var i, j, k;
  k=pc=tot=null; A={}; A0={}; h=[-1,-1]; /*H=[];/**/

  // formatted data (starting 2023-05)
  if (fData) {
    let A=fData.histogramValues;
    for (let i=0; A[i]; i++) {
      A0[A[i].rating]={k:A[i].rating, pc:'-', tot: A[i].voteCount};
      }
    }
  // error in parsing
  else if ( !data[0] || ((v=parseInt(data[0].innerText)) !==10) ) {
    R.status='data unavailable';
    R.maxAge=S+3600000; // +1h
    for (k=1; k<11 ; k++) {
      A0[k]={k,pc:0,tot:0};
      }
    }
  else for (i=0; v=data[i].innerText; i++) {
    if (k==null) k=parseInt(v);
    else if (pc==null) pc=parseFloat(v) || 0;
    else if (tot==null) {
      tot=parseInt(v.replace(/\D/g,'')) || 0;
      if (tot>h[1]) h=[k,tot];
      A0[k]={k,pc,tot};
      if (k==1) break;
      k=pc=tot=null;
      }
    }

  data=h=null;

  fill_H(A0);

  let d=new Date(), key=id+voters;
  let ep=document.querySelector('[class*="EpisodeNavigationForEpisode__SeasonEpisodeNumbers-"]') || '';
  if (ep) ep=ep.innerText.replace(/^.*?(\d+).*?(\d+).*$/s, '$1.$2');
  let t=d.getFullYear()+'/'+String(d.getMonth() + 1).padStart(2, '0')+'/'+ String(d.getDate()).padStart(2, '0') +' '+
    String(d.getHours()).padStart(2, '0')+':'+String(d.getMinutes()).padStart(2, '0');

  Object.assign(R,
   {t:S, key, cmt: t +' -- '+
    ( tit? tit : document.title.replace(/ +- +imdb *$/i,'').replace(/"(.+?)"/, ep?'$1 - '+ep+' -':'$1') )
    , data:JSON.stringify({A0,H}) });
  GM_setValue(key, R);
  fill_vars(R);
  upd_ratings();
  // upd cache date
  upd_dialog();
  } // END parse()

function upd_ratings() {
  if (!ratings || !ratings.key || (ratings.key != id+opts.voters) ) {
    loadRatings();
    return;
    }

  A=JSON.parse(JSON.stringify(A0));
  mod=false;

  if (opts.k10_maxmax && (H[0][0]==10) ) {
    if (A[9].tot < (A[10].tot * opts.k9_pc_10 /100) ) { delete A[10]; mod=true; }
    }

  if (opts.k10_max && (H[0][0]==10) ) {
    let noCv=true;
    if (noCv) {
      delete A[10]; mod=true;
      if (H[1][0]==9) {
        let _9=A[9].tot, _7=A[7].tot;
        if (opts.k9_7_pcOn && opts.k9_7_pc) if (A[7].tot / H[1][1] < (opts.k9_7_pc / 100) ) {delete A[9]; mod=true;}
        }
      }
    }
  if ( (H[0][0]==9) && opts.k9_max_do) {
    let _9=A[9].tot, _10=A[10].tot;
    if (opts.k9_max) { delete A[9]; mod=true; }
    if (opts.k9_max_10) { delete A[10]; mod=true; }
    else if (opts.k10_9_pcOn && opts.k10_9_pc) if (_10 >= (_9 * opts.k10_9_pc / 100) ) {delete A[10]; mod=true;}
    }
  if (opts.k10) delete A[10];
  if (opts.k1) delete A[1];
  var e, newR=0, newRAlt=0;
  tot=v=0;
  for (let i in A) {
    let k=A[i];
    tot += k.tot;
    v += k.k * k.tot;
    }

  // !!!!! temp
  mod=true;

  newR=(v/tot) || 0;

  for (let i in A) {
    let k=A[i];
    newRAlt += k.k * k.tot / tot;
    }

  waiting(false);

  if (e=rt) {
    if (!e.style.cssText) {
      e.style='--oldSc:"'+e.innerText+'"';
      e.oldV=e.innerText;
      if (decSep) e.oldV=e.oldV.replace( new RegExp('\\'+decSep), '.');
      }
    let t=newR && newR.toFixed(1);
    if (o.no0) t=t.replace(/\.0$/,'');
    e.curV=t;
    e.innerText= t || '!';
    e.classList.toggle('noMod',t == e.oldV);
    }
  if (e=tv) {
    if (!e.style.cssText) {
      e.style='--oldSc:"'+e.innerText+'"';
      e.oldV=e.innerText;
      if (thSep) e.oldV=e.oldV.replace( new RegExp('\\'+thSep,'g'), '');
      if (decSep) e.oldV=e.oldV.replace( new RegExp('\\'+decSep), '.');
      }
    let t = (o.divide && tot)? round(tot) : tot;
    e.curV=t;
    e.innerText=t || '!';
    e.classList.toggle('noMod', parseFloat(t) == parseFloat(e.oldV) );
    }
  // upd cache date
  upd_dialog();
  } // END upd_ratings()

function round(n) {
  var U='';
  for (let u of ['K','M']) {
    if (n>1000) {
      n=(n/1000).toFixed( n<10000? 1:0 );
      U=u;
      }
    }
  return n.toString().replace(/\.0$/,'')+U;
  }

function fill_vars(v) {
  ratings=v;
  ratings.data=JSON.parse(ratings.data);
  A0=ratings.data.A0;
  H=ratings.data.H;
  }

function purge_cache() {
  purging=false;
  var all=GM_listValues(), purge=1000*60*60* (opts.purge||8), now=Date.now();
  for (let i of all) {
    let d=GM_getValue(i);
    if (d && d.t &&
        ( (d.t + purge < now )
        || (d.maxAge && (d.maxAge < now))
        )
       ) {
      GM_deleteValue(i);
      }
    }
}

function getValues(k) {
  ratings=GM_getValue(k, false);
  let now=Date.now();
  if (ratings &&
     ( (ratings.t + (1000*60*60* (opts.purge||8)) < now)
      || (ratings.maxAge && (ratings.maxAge < now) )
     ) 
    ) {
    return 0;
    }
  return Boolean(ratings);
  }

function loadRatings() {
  var unk=[];
  if (!voters) {
    A8={}; ratings8={t:[], key:id+opts.voters, data:{}};
    if (opts.voters=='usnus') {
      voters=['us','nus'];
      }
    else voters=[opts.voters];
    votersL=voters.length;
    }

  for (let i=0,vt; i < voters.length; i++) {
    vt=voters[i];
    if (!getValues(id+vt)) {
      unk.push(vt);
      if (ratings) {
        ratings=null;
        }
      if (voters.length == votersL) { 
        getRatings(id, vt);
        if (!purging) setTimeout(purge_cache,3000);
        purging=true;
        }
      continue;
      }

    fill_vars(ratings);

    if (votersL > 1) {
      merge_A();
      ratings8.t.push(ratings.t);
      }
    else unk=null;
    }

  voters=unk;
  if (votersL > 1) {
    if (!voters.length) {
      voters=null;
      ratings=ratings8;
      A0=ratings.data.A0=A8;
      fill_H(A0);
      ratings.data.H=H;
      }
    }

  if (!voters) {
    upd_ratings();
    }
  }

function merge_A() {
  for (let k in A0) {
    let a=A8[k], b=A0[k];
    if (!a && !b) return;
    A8[k]={k, 'tot': (a?a.tot:0)+b.tot};
    }
  } 


var ratings, ratings8, id, re= /^\/title\/([^\/]+)\/(rating)?/.exec(href);

if (re && re[1]) {
  id=re[1];
  loadRatings();
  }

// exit if dialog already created.
if (dialog) return;

dialog=document.createElement('div');
dialog.id='fix_rating_opt';
dialog.innerHTML=`<div id="close">&#10062;</div>
<!--h3>(Userscript) Fix rating - Settings</h3><!-->
<b id="hdr">Fix rating - Settings</b><hr><br>
<hr>
Count votes from: <select name="voters"><option value="">all voters</option>
<option disabled title="Currently disabled with new design" value="us">US voters</option>
<option disabled title="Currently disabled with new design" value="nus">non-US voters</option>
<option disabled title="Currently disabled with new design" value="top">Top 1000 voters</option>
<option disabled title="Currently disabled with new design" value="usnus">US & non-US voters</option></select>
<hr>
<input name="k10" type="checkbox" data-group="10">Ignore "10"<br>
<input name="k10_max" type="checkbox" data-group="10">Ignore "10" if is highest count<br>
<input name="k10_maxmax" type="checkbox" data-group="10">... "10" highest and if "9" is &lt; <input name="k9_pc_10" type="number" min="1" max="99">% of "10"<br>
<input name="k1" type="checkbox">Ignore "1"<br>
<hr>
<b>If "9" is highest : </b><input name="k9_max_do" type="checkbox">
<div class="mrg"><input name="k9_max" type="checkbox">discard "9"<br>
<input name="k9_max_10" type="checkbox" data-group="9_10">discard "10"<br>
<input name="k10_9_pcOn" type="checkbox" data-group="9_10">discard "10" if &gt;= <input name="k10_9_pc" type="number" min="1" max="99">% of "9"</div>
<hr>
Expire cached values after <input name="purge" type="number" data-norld min="1" max="99"> hours
<hr>
<nobr><input type="button" id="rldSettings" value="Reload settings"> (if modified in another window)</nobr><br>
<input type="button" id="reset" value="Reset to default"><br>
<hr>
<span>Rating values cached on: <span id="cached"></span></span><br>
<input type="button" id="clrCache" value="Refresh cache"><br class="show"><hr class="show"><br>
<div class="show">
<input type="checkbox" name="search" data-norld>Enable on "search" (need page reload)<br>
<input type="checkbox" name="chart" data-norld>Enable on "chart" (need page reload)<br>
<input type="checkbox" name="list" data-norld>Enable on "list" (need page reload)</div>
`;
dialog.querySelector('#close').onclick=function(){dialog.remove();dialog.style='';};
dialog.querySelector('#rldSettings').onclick=function(){
  getOpts();
  upd_dialog();
  upd_ratings();
  getListRating();
  };
dialog.querySelector('#clrCache').onclick=function(){
  getRatings(id, opts.voters);
  };
dialog.querySelector('#reset').onclick=function(){
  if (!confirm("Reset? You'll lose your changes.")) return;
  GM_deleteValue('opts');
  getOpts();
  upd_dialog();
  upd_ratings();
  getListRating();
  };

var cached=dialog.querySelector('#cached');

function upd_opts(e) {
  let k=e.name;
  if (k in opts) {
    if (e.type=='checkbox') opts[k]=e.checked;
    else opts[k]=e.value;
    }
  }

dialog.addEventListener('change', function(ev){
  let t=ev.target, g;
  if (g=t.dataset.group) {
    let a=dialog.querySelectorAll('[data-group="'+g+'"]:checked');
    a.forEach(function(e){
      if (e!==t) {
        e.checked=false;
        upd_opts(e);
        }
      });
    }
  upd_opts(t)
  GM_setValue('opts', opts);
  if ('norld' in t.dataset) return;
  upd_ratings();
  getListRating();
  });

var x=0, y=0, move=dialog.querySelector('#hdr');

function moving(ev) {
  var st=dialog.style;
  var Y= (E?ev.pageY:ev.clientY) -y, X= (E?ev.pageX:ev.clientX) -x;
  if ( (Y<0) || (X<0) ) return;
  st.top=Y+'px';
  st.left=X+'px';
  st.margin='0';
  }

move.onmousedown=function(ev){
  // ignore if not left click
  if (ev.buttons != 1) return;
  ev.preventDefault();
  var cCss=window.getComputedStyle(dialog);
  x=ev.offsetX+parseInt(cCss.borderLeftWidth); y=ev.offsetY+parseInt(cCss.borderTopWidth);
  document.body.addEventListener('mousemove', moving);
  dialog.onmouseup=mouseup;
  dialog.classList.add('grabbed');
  }

function mouseup(ev){
  document.body.removeEventListener('mousemove', moving);
  dialog.onmouseup=null;
  dialog.classList.remove('grabbed');
  }

function fixed(s,n) {
  return ('000'+s).substr(- n);
}

function upd_dialog() {
  if (!dialog) return;
  for (let k in opts) {
    let e=dialog.querySelector('[name="'+k+'"]');
    if (e) {
      if (e.type=='checkbox') e.checked=opts[k];
      else e.value=opts[k];
      }
    }
  if (cached && ratings) {
    let s='', a=ratings.t || [];
    if (!Array.isArray(a)) a=[a];

    for (let t of a) {
      t=new Date(t);
      if (s) s+='<br>';
      s+=t.getFullYear()+'/'+ fixed(t.getMonth()+1,2) +'/'+ fixed(t.getDate(),2) +' '+fixed(t.getHours(),2)+':'+fixed(t.getMinutes(),2);
      }
    cached.innerHTML=s;
    }
  }
upd_dialog();


} // END display()

})();