IMDb - fix rating

Recompute a new film rating value based on your criterias

Per 31-03-2022. Zie de nieuwste versie.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         IMDb - fix rating
// @namespace    https://github.com/Procyon-b
// @version      0.5.1
// @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=[]; 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) {
      var obs=new MutationObserver(function(mutL){
        if (mutL[0].addedNodes.length) {
          setTimeout(function(){
            List=document.querySelectorAll(LO.L);
            getListRating();
            },0);
          }
        });
      obs.observe(ec, {childList: true, subtree: false});
      }
    }
  }

// 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) {
  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'),
    R={};

  // extract rating value % tot
  var i, j, k;
  k=pc=tot=null; A={}; A0={}; h=[-1,-1]; /*H=[];/**/
  // error in parsing
  if ( (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();



  }


var rootSection=document.querySelector('section[class*="TitleMainHeroGroup__"]');

// 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 value="us">US voters</option>
<option value="nus">non-US voters</option>
<option value="top">Top 1000 voters</option>
<option 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) {
  if (!ev.pageY && !ev.pageX) return;
  var st=dialog.style;
  var Y=ev.pageY -y, X=ev.pageX -x;
  if ( (Y<0) || (X<0) ) return;
  st.top=Y+'px';
  st.left=X+'px';
  st.margin='0';
  }

move.onmousedown=function(ev){
  ev.preventDefault();
  var cCss=window.getComputedStyle(dialog);
  x=ev.layerX+parseInt(cCss.borderLeftWidth); y=ev.layerY+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()




})();