IMDb - fix rating

Recompute a new film rating value based on your criterias

  1. // ==UserScript==
  2. // @name IMDb - fix rating
  3. // @namespace https://github.com/Procyon-b
  4. // @version 0.5.4
  5. // @description Recompute a new film rating value based on your criterias
  6. // @author Achernar
  7. // @include /^https:\/\/www\.imdb\.com\/title\/[^\/]+\/(episodes|reference|ratings)?(\?.*)?$/
  8. // @match https://www.imdb.com/chart/*
  9. // @match https://www.imdb.com/search/title/*
  10. // @match https://www.imdb.com/list/*
  11. // @grant GM_getValue
  12. // @grant GM_setValue
  13. // @grant GM.registerMenuCommand
  14. // @grant GM_listValues
  15. // @grant GM_deleteValue
  16. // @run-at document-end
  17. // ==/UserScript==
  18.  
  19. (function() {
  20. "use strict";
  21.  
  22. var E, Ep, Er, rt, tv, opts, dialog,
  23. Layouts={
  24. 'title':{
  25. L:'[data-testid="hero-rating-bar__aggregate-rating"]',
  26. rt:'span[class^="AggregateRatingButton__RatingScore-sc-"], [data-testid="hero-rating-bar__aggregate-rating__score"] span',
  27. tv:'div[class^="AggregateRatingButton__TotalRatingAmount-sc-"], [data-testid="hero-rating-bar__aggregate-rating__score"] ~ :last-child',
  28. divide:true,
  29. type:'page'
  30. },
  31. 'reference':{
  32. L:'.titlereference-header .ipl-rating-star',
  33. rt:'.titlereference-header .ipl-rating-star .ipl-rating-star__rating',
  34. tv:'.titlereference-header .ipl-rating-star .ipl-rating-star__total-votes',
  35. tvF:function(tv,r) {
  36. if (!tv || !r) return;
  37. let vtv=r.querySelector('.vtv');
  38. if (!vtv) {
  39. let RE=/^(.*?)([^()]+)(.*)$/s.exec(tv.innerText);
  40. tv.innerHTML=(RE[1]||'')+'<span class="vtv">'+RE[2]+'</span>'+(RE[3]||'');
  41. vtv=tv.querySelector('.vtv');
  42. }
  43. return vtv;
  44. },
  45. type:'page'
  46. },
  47. 'ratings':{
  48. L:'.subpage_title_block__right-column .ipl-rating-widget',
  49. rt:'.subpage_title_block__right-column .ipl-rating-widget .ipl-rating-star__rating',
  50. type:'page'
  51. },
  52. 'EpLst':{
  53. L:'.eplist .list_item',
  54. rt:'.ipl-rating-widget .ipl-rating-star .ipl-rating-star__rating',
  55. tv:'.ipl-rating-widget .ipl-rating-star .ipl-rating-star__total-votes',
  56. tvF:function(tv,r) {
  57. if (!tv || !r) return;
  58. let vtv=r.querySelector('.vtv');
  59. if (!vtv) {
  60. let RE=/^(.*?)([^()]+)(.*)$/s.exec(tv.innerText);
  61. tv.innerHTML=(RE[1]||'')+'<span class="vtv">'+RE[2]+'</span>'+(RE[3]||'');
  62. vtv=tv.querySelector('.vtv');
  63. }
  64. return vtv;
  65. },
  66. nm:'a[itemprop="url"]:only-child, a.add-image ~ div',
  67. url:'a[itemprop="name"]',
  68. tit:'page',
  69. type:'list'
  70. },
  71. 'Chart':{
  72. k:'chart',
  73. L:'.lister-list > tr',
  74. rt:'.ratingColumn.imdbRating > strong',
  75. url:'.titleColumn a[href*="/title/"]',
  76. titF:function(tit, r){
  77. if (!tit || !r) return;
  78. let n=tit.nextElementSibling;
  79. return tit.innerText + (n?' '+n.innerText:'');
  80. },
  81. tit:'item',
  82. type:'list'
  83. },
  84. 'List':{
  85. k:'list',
  86. L:'.article.listo .lister-list > div',
  87. rt:'.ipl-rating-widget .ipl-rating-star__rating',
  88. tv:'.lister-item-content > p.text-muted.text-small > span[name="nv"]',
  89. url:'.lister-item-content a[href*="/title/"]',
  90. titF:function(tit, r){
  91. if (!tit || !r) return;
  92. let n=tit.nextElementSibling;
  93. return tit.innerText + (n?' '+n.innerText:'');
  94. },
  95. tit:'item',
  96. type:'list'
  97. },
  98. 'Search':{
  99. k:'search',
  100. L:'.lister-list > div',
  101. rt:'.inline-block.ratings-imdb-rating > strong',
  102. tv:'.sort-num_votes-visible [name="nv"]',
  103. url:'.lister-item-content a[href*="/title/"]',
  104. titF:function(tit, r){
  105. if (!tit || !r) return;
  106. let n=tit.nextElementSibling;
  107. return tit.innerText + (n?' '+n.innerText:'');
  108. },
  109. tit:'item',
  110. type:'list'
  111. },
  112. 'SearchCompact':{
  113. k:'search',
  114. L:'.lister-list > div',
  115. rt:'.col-imdb-rating > strong',
  116. url:'.lister-item-header a[href*="/title/"]',
  117. titF:function(tit, r){
  118. if (!tit || !r) return;
  119. let n=tit.nextElementSibling;
  120. return tit.innerText + (n?' '+n.innerText:'');
  121. },
  122. tit:'item',
  123. type:'list'
  124. }
  125. };
  126.  
  127. var XHRqueue=[], XHRqueueV, purging;
  128.  
  129. function rstXHRq() {
  130. XHRqueue=[];
  131. XHRqueueV=Date.now();
  132. }
  133.  
  134. var defOpts={
  135. voters: '',
  136. k10: false,
  137. k10_max: false,
  138. k10_maxmax: false,
  139. k9_pc_10: 20,
  140. k9_max_do: false,
  141. k9_max: false,
  142. k9_max_10: false,
  143. k10_9_pcOn: false,
  144. k10_9_pc: 90,
  145. k1: false,
  146. purge: 16,
  147. search: false,
  148. chart: false,
  149. list: false
  150. };
  151.  
  152. var List, rtQS, tvQS, nmQS, urlQS, tvF, LO;
  153. // look for rates
  154. for (let k in Layouts) {
  155. LO=Layouts[k];
  156. if (LO.type=='page') {
  157. E=document.querySelector(LO.L);
  158. if (E) break;
  159. continue;
  160. }
  161. List=document.querySelectorAll(LO.L);
  162. if (List.length) {
  163. rtQS=LO.rt;
  164. // rating found?
  165. if (!rtQS || (!List[0].querySelector(rtQS) && (List[1] && !List[1].querySelector(rtQS)) && (List[2] && !List[2].querySelector(rtQS)) ) ) {
  166. List=[]; continue;}
  167. tvQS=LO.tv;
  168. nmQS=LO.nm;
  169. urlQS=LO.url;
  170. tvF=LO.tvF;
  171. break;
  172. }
  173. }
  174.  
  175.  
  176. function getOpts() {
  177. opts=GM_getValue('opts', {});
  178. opts= Object.assign({}, defOpts, opts);
  179. }
  180. getOpts();
  181.  
  182. addSt(`
  183. .oldVal::before {
  184. content: var(--oldSc);
  185. font-size: 0.8em;
  186. margin-right: 0.6em;
  187. text-decoration: line-through brown;
  188. line-height: 1em;
  189. color: gray;
  190. }
  191. .oldVal.noMod::before {
  192. content: "=" !important;
  193. text-decoration: none !important;
  194. }
  195. .waiting.oldVal::before {
  196. content: "?" !important;
  197. text-decoration: none !important;
  198. }
  199. div[class^="AggregateRatingButton__ContentWrap-sc-"]:hover .oldVal::before,
  200. .rating-bar__base-button:hover .oldVal::before,
  201. .ipl-rating-star.small:hover .oldVal::before, .oldVal:hover::before {
  202. text-decoration: none !important;
  203. }
  204.  
  205. [data-testid="hero-rating-bar__aggregate-rating"] {
  206. position: relative;
  207. }
  208. #fix_rating_opt {
  209. position: absolute;
  210. color: black;
  211. background-color: white;
  212. top: 5em;
  213. z-index: 10000 !important;
  214. font-size: 13px;
  215. font-family: arial;
  216. line-height: 1.4em;
  217. padding: 3px 8px;
  218. border: 2px solid gray;
  219. margin: 0 50%;
  220. }
  221. #fix_rating_opt.list {
  222. position: fixed;
  223. }
  224. #fix_rating_opt {
  225. white-space: nowrap;
  226. }
  227. #fix_rating_opt #close {
  228. float: right;
  229. color: red;
  230. cursor: pointer;
  231. z-index: 2;
  232. position: relative;
  233. }
  234. #fix_rating_opt input[type="number"] {
  235. width: 3em;
  236. }
  237. #fix_rating_opt input[type="checkbox"] {
  238. vertical-align: middle;
  239. }
  240. #fix_rating_opt .mrg {
  241. margin-left: 1em;
  242. }
  243. #fix_rating_opt button, #fix_rating_opt input[type="button"] {
  244. font-weight: initial;
  245. padding: 1px 6px;
  246. cursor: initial;
  247. }
  248. #fix_rating_opt b {
  249. font-weight: bolder;
  250. }
  251. #fix_rating_opt.list #reset ~ *:not(.show) {
  252. display: none;
  253. }
  254. #fix_rating_opt span {
  255. font-size: inherit !important;
  256. line-height: inherit !important;
  257. }
  258. #fix_rating_opt input[type="checkbox"] {
  259. margin: 0 3px;
  260. }
  261. #fix_rating_opt input, #fix_rating_opt button {
  262. margin: 0 0 1px 0;
  263. }
  264. #fix_rating_opt input[type="number"] {
  265. padding: 0 !important;
  266. line-height: 1em !important;
  267. }
  268. #fix_rating_opt hr {
  269. border: none !important;
  270. height: 0 !important;
  271. margin-block-start: 0.3em !important;
  272. margin-block-end: 0.3em !important;
  273. }
  274. #fix_rating_opt #cached {
  275. display: inline-block;
  276. vertical-align: top;
  277. }
  278. #fix_rating_opt [name="k9_max_do"]:not(:checked) + .mrg {
  279. display: none;
  280. }
  281. #fix_rating_opt #hdr {
  282. cursor: grab;
  283. background: lightgray;
  284. position: absolute;
  285. left: 0;
  286. right: 0;
  287. top: 0;
  288. padding: 3px 8px;
  289. }
  290. #fix_rating_opt.grabbed #hdr {
  291. cursor: grabbing;
  292. }
  293. `);
  294.  
  295. GM.registerMenuCommand('Settings', function(){
  296. showCfg();
  297. });
  298.  
  299. var trig=E || document.querySelector('h1.header');
  300.  
  301. if (trig) trig.addEventListener('click', function(ev){
  302. if (ev.ctrlKey && ev.altKey) {
  303. ev.preventDefault();
  304. ev.stopPropagation();
  305. showCfg();
  306. }
  307. }, {capture:true});
  308.  
  309. // init dialog
  310. if (!E) if ( (!opts.search && (LO.k=='search'))
  311. || (!opts.chart && (LO.k=='chart'))
  312. || (!opts.list && (LO.k=='list'))
  313. ) {
  314. List=[];
  315. }
  316.  
  317.  
  318. function getListRating() {
  319. if (!List || !List.length) return;
  320. rstXHRq();
  321. getOpts();
  322. let w=10;
  323. List.forEach(function(Ep){
  324. var url=urlQS && Ep.querySelector(urlQS),
  325. tit=(LO.titF && LO.titF(url,Ep)) || (url && url.innerText),
  326. nm=nmQS && Ep.querySelector(nmQS),
  327. num=nm? nm.innerText.replace(/^.*?(\d+).*?(\d+).*$/ms, '$1.$2') :'',
  328. rt=rtQS && Ep.querySelector(rtQS),
  329. tv=tvQS && Ep.querySelector(tvQS);
  330.  
  331. if (!rt && !tv) return;
  332. if (tvF) tv=tvF(tv,Ep);
  333. if (rt && !decSep) {
  334. let RE=/^.*([\D]).*$/.exec(rt.innerText);
  335. if (RE) decSep=RE[1];
  336. }
  337. if (tv && !thSep) {
  338. let RE=/\d([\D])\d\d\d$/.exec(tv.innerText);
  339. if (RE) thSep=RE[1];
  340. }
  341.  
  342. if (tit && (LO.tit=='page')) tit=document.title.replace(/ +- imdb *$/i,'')+(num?' - '+num:'')+' - '+tit;
  343. setTimeout(function(){display(url.pathname, rt, tv, tit);},w);
  344. w+=10;
  345. });
  346. }
  347.  
  348.  
  349. var decSep, thSep;
  350.  
  351. if (E) {
  352. rt=document.querySelector(LO.rt);
  353. tv=document.querySelector(LO.tv);
  354. if (!rt) return;
  355. if (tv && LO.tvF) tv=LO.tvF(tv,E);
  356. if (rt && !decSep) {
  357. let RE=/^.*([\D]).*$/.exec(rt.innerText);
  358. if (RE) decSep=RE[1];
  359. }
  360. if (tv && !thSep) {
  361. let RE=/\d([\D])\d\d\d$/.exec(tv.innerText);
  362. if (RE) thSep=RE[1];
  363. }
  364. let RE=/^.*([\D]).*$/.exec(rt.innerText);
  365. if (RE) decSep=RE[1];
  366. // imdb script rewriting the values
  367. let obs=new MutationObserver(function(mutL){
  368. for (let mut of mutL) {
  369. if (mut.type=='characterData') {
  370. let n=mut.target.parentNode;
  371. if (n.curV && (n.curV != n.innerText)) {
  372. n.innerText=n.curV;
  373. }
  374. }
  375. }
  376. });
  377. obs.observe(E, {childList: false, subtree: true, characterData:true});
  378.  
  379. display(location.pathname, rt, tv, '', {divide:LO.divide});
  380. }
  381. else {
  382. getListRating();
  383. if (List.length) {
  384. var ec=document.querySelector('#episodes_content');
  385. if (ec) {
  386. new MutationObserver(function(mutL){
  387. if (mutL[0].addedNodes.length) {
  388. setTimeout(function(){
  389. List=document.querySelectorAll(LO.L);
  390. getListRating();
  391. },0);
  392. }
  393. }).observe(ec, {childList: true, subtree: false});
  394. }
  395. else if (ec=document.querySelector('.lister.list.detail.sub-list > .row.lister-working.hidden')) {
  396. new MutationObserver(function(mutL){
  397. if ( (mutL[0].attributeName == 'style') && (mutL[0].target.style.display == 'none') ) {
  398. setTimeout(function(){
  399. List=document.querySelectorAll(LO.L);
  400. getListRating();
  401. },0);
  402. }
  403. }).observe(ec, {childList: true, subtree: false, attributes: true});
  404. }
  405. }
  406. }
  407.  
  408. // init dialog
  409. if (!dialog) display();
  410.  
  411.  
  412. function addSt(s,t) {
  413. let st=document.createElement('style');
  414. try{
  415. (document.head || document.documentElement).appendChild(st);
  416. st.textContent=s;
  417. }catch(e){
  418. if (t) document.addEventListener('DOMContentLoaded',function(){addSt(s);});
  419. else setTimeout(function(){addSt(s,t);},0);
  420. }
  421. }
  422.  
  423. function showCfg() {
  424. let xy=E && E.getBoundingClientRect();
  425. if (E) dialog.style='top:'+(window.scrollY+parseInt(xy.y)+80)+'px;';
  426. else dialog.classList.add('list');
  427. document.body.appendChild(dialog);
  428. }
  429.  
  430.  
  431. function display(href, rt, tv, tit='', o={}) {
  432.  
  433. function waiting(v) {
  434. rt && rt.classList.toggle('waiting', v);
  435. tv && tv.classList.toggle('waiting', v);
  436. }
  437.  
  438. rt && rt.classList.add('oldVal');
  439. tv && tv.classList.add('oldVal');
  440.  
  441. function sendXHR(a) {
  442. if (!a || !a.R) return;
  443. a.R.open('GET', '/title/'+a.id+'/ratings'+a.v);
  444. a.R.send();
  445. }
  446.  
  447. function getRatings(id, vt=opts.voters) {
  448. //TMP new design 202305
  449. vt='';
  450.  
  451. waiting(true);
  452. var v, R=new XMLHttpRequest(), XHRqV=XHRqueueV;
  453. R.addEventListener('error',function(){
  454. if (XHRqV != XHRqueueV) return;
  455. setTimeout(function(){
  456. sendXHR(XHRqueue.shift());
  457. },0);
  458. });
  459.  
  460. R.addEventListener('load',function(r){
  461. if (XHRqV != XHRqueueV) return;
  462. parse(this.responseText, vt);
  463. setTimeout(function(){
  464. sendXHR(XHRqueue.shift());
  465. },50);
  466. });
  467.  
  468. switch(vt) {
  469. case '': v=''; break;
  470. case 'us': v='?demo=us_users'; break;
  471. case 'nus': v='?demo=non_us_users'; break;
  472. case 'top': v='?demo=top_1000_voters'; break;
  473. default: return;
  474. }
  475.  
  476. if (!XHRqueue.length) {
  477. setTimeout(function(){ sendXHR(XHRqueue.shift()); },0);
  478. setTimeout(function(){ sendXHR(XHRqueue.shift()); },400);
  479. }
  480. XHRqueue.push({R,id,v});
  481. } // END getRatings()
  482.  
  483. var v, pc, tot, A={}, A0={}, A8={}, voters, votersL, h=[-1,-1], H=[], mod;
  484.  
  485. function fill_H(a) {
  486. H=[];
  487. for (let k in a) {
  488. H.push( ('000000000'+a[k].tot).substr(-10) +'_'+ ('0'+k).substr(-2) );
  489. }
  490. H.sort().reverse();
  491. for (let i in H) H[i]=[ parseInt(H[i].split('_')[1]), parseInt(H[i].split('_')[0])];
  492. }
  493.  
  494. function parse(s,voters='') {
  495. var parser=new DOMParser(), S=Date.now(),
  496. h=parser.parseFromString(s, 'text/html'),
  497. data=h.querySelectorAll('table td'),
  498. fData=h.querySelector('#__NEXT_DATA__'),
  499. R={};
  500.  
  501. if (fData) {
  502. try {
  503. fData=JSON.parse(fData.textContent).props.pageProps.contentData.histogramData;
  504. }catch(e){fData=null;}
  505. }
  506.  
  507. // extract rating value % tot
  508. var i, j, k;
  509. k=pc=tot=null; A={}; A0={}; h=[-1,-1]; /*H=[];/**/
  510.  
  511. // formatted data (starting 2023-05)
  512. if (fData) {
  513. let A=fData.histogramValues;
  514. for (let i=0; A[i]; i++) {
  515. A0[A[i].rating]={k:A[i].rating, pc:'-', tot: A[i].voteCount};
  516. }
  517. }
  518. // error in parsing
  519. else if ( !data[0] || ((v=parseInt(data[0].innerText)) !==10) ) {
  520. R.status='data unavailable';
  521. R.maxAge=S+3600000; // +1h
  522. for (k=1; k<11 ; k++) {
  523. A0[k]={k,pc:0,tot:0};
  524. }
  525. }
  526. else for (i=0; v=data[i].innerText; i++) {
  527. if (k==null) k=parseInt(v);
  528. else if (pc==null) pc=parseFloat(v) || 0;
  529. else if (tot==null) {
  530. tot=parseInt(v.replace(/\D/g,'')) || 0;
  531. if (tot>h[1]) h=[k,tot];
  532. A0[k]={k,pc,tot};
  533. if (k==1) break;
  534. k=pc=tot=null;
  535. }
  536. }
  537.  
  538. data=h=null;
  539.  
  540. fill_H(A0);
  541.  
  542. let d=new Date(), key=id+voters;
  543. let ep=document.querySelector('[class*="EpisodeNavigationForEpisode__SeasonEpisodeNumbers-"]') || '';
  544. if (ep) ep=ep.innerText.replace(/^.*?(\d+).*?(\d+).*$/s, '$1.$2');
  545. let t=d.getFullYear()+'/'+String(d.getMonth() + 1).padStart(2, '0')+'/'+ String(d.getDate()).padStart(2, '0') +' '+
  546. String(d.getHours()).padStart(2, '0')+':'+String(d.getMinutes()).padStart(2, '0');
  547.  
  548. Object.assign(R,
  549. {t:S, key, cmt: t +' -- '+
  550. ( tit? tit : document.title.replace(/ +- +imdb *$/i,'').replace(/"(.+?)"/, ep?'$1 - '+ep+' -':'$1') )
  551. , data:JSON.stringify({A0,H}) });
  552. GM_setValue(key, R);
  553. fill_vars(R);
  554. upd_ratings();
  555. // upd cache date
  556. upd_dialog();
  557. } // END parse()
  558.  
  559. function upd_ratings() {
  560. if (!ratings || !ratings.key || (ratings.key != id+opts.voters) ) {
  561. loadRatings();
  562. return;
  563. }
  564.  
  565. A=JSON.parse(JSON.stringify(A0));
  566. mod=false;
  567.  
  568. if (opts.k10_maxmax && (H[0][0]==10) ) {
  569. if (A[9].tot < (A[10].tot * opts.k9_pc_10 /100) ) { delete A[10]; mod=true; }
  570. }
  571.  
  572. if (opts.k10_max && (H[0][0]==10) ) {
  573. let noCv=true;
  574. if (noCv) {
  575. delete A[10]; mod=true;
  576. if (H[1][0]==9) {
  577. let _9=A[9].tot, _7=A[7].tot;
  578. 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;}
  579. }
  580. }
  581. }
  582. if ( (H[0][0]==9) && opts.k9_max_do) {
  583. let _9=A[9].tot, _10=A[10].tot;
  584. if (opts.k9_max) { delete A[9]; mod=true; }
  585. if (opts.k9_max_10) { delete A[10]; mod=true; }
  586. else if (opts.k10_9_pcOn && opts.k10_9_pc) if (_10 >= (_9 * opts.k10_9_pc / 100) ) {delete A[10]; mod=true;}
  587. }
  588. if (opts.k10) delete A[10];
  589. if (opts.k1) delete A[1];
  590. var e, newR=0, newRAlt=0;
  591. tot=v=0;
  592. for (let i in A) {
  593. let k=A[i];
  594. tot += k.tot;
  595. v += k.k * k.tot;
  596. }
  597.  
  598. // !!!!! temp
  599. mod=true;
  600.  
  601. newR=(v/tot) || 0;
  602.  
  603. for (let i in A) {
  604. let k=A[i];
  605. newRAlt += k.k * k.tot / tot;
  606. }
  607.  
  608. waiting(false);
  609.  
  610. if (e=rt) {
  611. if (!e.style.cssText) {
  612. e.style='--oldSc:"'+e.innerText+'"';
  613. e.oldV=e.innerText;
  614. if (decSep) e.oldV=e.oldV.replace( new RegExp('\\'+decSep), '.');
  615. }
  616. let t=newR && newR.toFixed(1);
  617. if (o.no0) t=t.replace(/\.0$/,'');
  618. e.curV=t;
  619. e.innerText= t || '!';
  620. e.classList.toggle('noMod',t == e.oldV);
  621. }
  622. if (e=tv) {
  623. if (!e.style.cssText) {
  624. e.style='--oldSc:"'+e.innerText+'"';
  625. e.oldV=e.innerText;
  626. if (thSep) e.oldV=e.oldV.replace( new RegExp('\\'+thSep,'g'), '');
  627. if (decSep) e.oldV=e.oldV.replace( new RegExp('\\'+decSep), '.');
  628. }
  629. let t = (o.divide && tot)? round(tot) : tot;
  630. e.curV=t;
  631. e.innerText=t || '!';
  632. e.classList.toggle('noMod', parseFloat(t) == parseFloat(e.oldV) );
  633. }
  634. // upd cache date
  635. upd_dialog();
  636. } // END upd_ratings()
  637.  
  638. function round(n) {
  639. var U='';
  640. for (let u of ['K','M']) {
  641. if (n>1000) {
  642. n=(n/1000).toFixed( n<10000? 1:0 );
  643. U=u;
  644. }
  645. }
  646. return n.toString().replace(/\.0$/,'')+U;
  647. }
  648.  
  649. function fill_vars(v) {
  650. ratings=v;
  651. ratings.data=JSON.parse(ratings.data);
  652. A0=ratings.data.A0;
  653. H=ratings.data.H;
  654. }
  655.  
  656. function purge_cache() {
  657. purging=false;
  658. var all=GM_listValues(), purge=1000*60*60* (opts.purge||8), now=Date.now();
  659. for (let i of all) {
  660. let d=GM_getValue(i);
  661. if (d && d.t &&
  662. ( (d.t + purge < now )
  663. || (d.maxAge && (d.maxAge < now))
  664. )
  665. ) {
  666. GM_deleteValue(i);
  667. }
  668. }
  669. }
  670.  
  671. function getValues(k) {
  672. ratings=GM_getValue(k, false);
  673. let now=Date.now();
  674. if (ratings &&
  675. ( (ratings.t + (1000*60*60* (opts.purge||8)) < now)
  676. || (ratings.maxAge && (ratings.maxAge < now) )
  677. )
  678. ) {
  679. return 0;
  680. }
  681. return Boolean(ratings);
  682. }
  683.  
  684. function loadRatings() {
  685. var unk=[];
  686. if (!voters) {
  687. A8={}; ratings8={t:[], key:id+opts.voters, data:{}};
  688. if (opts.voters=='usnus') {
  689. voters=['us','nus'];
  690. }
  691. else voters=[opts.voters];
  692. votersL=voters.length;
  693. }
  694.  
  695. for (let i=0,vt; i < voters.length; i++) {
  696. vt=voters[i];
  697. if (!getValues(id+vt)) {
  698. unk.push(vt);
  699. if (ratings) {
  700. ratings=null;
  701. }
  702. if (voters.length == votersL) {
  703. getRatings(id, vt);
  704. if (!purging) setTimeout(purge_cache,3000);
  705. purging=true;
  706. }
  707. continue;
  708. }
  709.  
  710. fill_vars(ratings);
  711.  
  712. if (votersL > 1) {
  713. merge_A();
  714. ratings8.t.push(ratings.t);
  715. }
  716. else unk=null;
  717. }
  718.  
  719. voters=unk;
  720. if (votersL > 1) {
  721. if (!voters.length) {
  722. voters=null;
  723. ratings=ratings8;
  724. A0=ratings.data.A0=A8;
  725. fill_H(A0);
  726. ratings.data.H=H;
  727. }
  728. }
  729.  
  730. if (!voters) {
  731. upd_ratings();
  732. }
  733. }
  734.  
  735. function merge_A() {
  736. for (let k in A0) {
  737. let a=A8[k], b=A0[k];
  738. if (!a && !b) return;
  739. A8[k]={k, 'tot': (a?a.tot:0)+b.tot};
  740. }
  741. }
  742.  
  743.  
  744. var ratings, ratings8, id, re= /^\/title\/([^\/]+)\/(rating)?/.exec(href);
  745.  
  746. if (re && re[1]) {
  747. id=re[1];
  748. loadRatings();
  749. }
  750.  
  751. // exit if dialog already created.
  752. if (dialog) return;
  753.  
  754. dialog=document.createElement('div');
  755. dialog.id='fix_rating_opt';
  756. dialog.innerHTML=`<div id="close">&#10062;</div>
  757. <!--h3>(Userscript) Fix rating - Settings</h3><!-->
  758. <b id="hdr">Fix rating - Settings</b><hr><br>
  759. <hr>
  760. Count votes from: <select name="voters"><option value="">all voters</option>
  761. <option disabled title="Currently disabled with new design" value="us">US voters</option>
  762. <option disabled title="Currently disabled with new design" value="nus">non-US voters</option>
  763. <option disabled title="Currently disabled with new design" value="top">Top 1000 voters</option>
  764. <option disabled title="Currently disabled with new design" value="usnus">US & non-US voters</option></select>
  765. <hr>
  766. <input name="k10" type="checkbox" data-group="10">Ignore "10"<br>
  767. <input name="k10_max" type="checkbox" data-group="10">Ignore "10" if is highest count<br>
  768. <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>
  769. <input name="k1" type="checkbox">Ignore "1"<br>
  770. <hr>
  771. <b>If "9" is highest : </b><input name="k9_max_do" type="checkbox">
  772. <div class="mrg"><input name="k9_max" type="checkbox">discard "9"<br>
  773. <input name="k9_max_10" type="checkbox" data-group="9_10">discard "10"<br>
  774. <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>
  775. <hr>
  776. Expire cached values after <input name="purge" type="number" data-norld min="1" max="99"> hours
  777. <hr>
  778. <nobr><input type="button" id="rldSettings" value="Reload settings"> (if modified in another window)</nobr><br>
  779. <input type="button" id="reset" value="Reset to default"><br>
  780. <hr>
  781. <span>Rating values cached on: <span id="cached"></span></span><br>
  782. <input type="button" id="clrCache" value="Refresh cache"><br class="show"><hr class="show"><br>
  783. <div class="show">
  784. <input type="checkbox" name="search" data-norld>Enable on "search" (need page reload)<br>
  785. <input type="checkbox" name="chart" data-norld>Enable on "chart" (need page reload)<br>
  786. <input type="checkbox" name="list" data-norld>Enable on "list" (need page reload)</div>
  787. `;
  788. dialog.querySelector('#close').onclick=function(){dialog.remove();dialog.style='';};
  789. dialog.querySelector('#rldSettings').onclick=function(){
  790. getOpts();
  791. upd_dialog();
  792. upd_ratings();
  793. getListRating();
  794. };
  795. dialog.querySelector('#clrCache').onclick=function(){
  796. getRatings(id, opts.voters);
  797. };
  798. dialog.querySelector('#reset').onclick=function(){
  799. if (!confirm("Reset? You'll lose your changes.")) return;
  800. GM_deleteValue('opts');
  801. getOpts();
  802. upd_dialog();
  803. upd_ratings();
  804. getListRating();
  805. };
  806.  
  807. var cached=dialog.querySelector('#cached');
  808.  
  809. function upd_opts(e) {
  810. let k=e.name;
  811. if (k in opts) {
  812. if (e.type=='checkbox') opts[k]=e.checked;
  813. else opts[k]=e.value;
  814. }
  815. }
  816.  
  817. dialog.addEventListener('change', function(ev){
  818. let t=ev.target, g;
  819. if (g=t.dataset.group) {
  820. let a=dialog.querySelectorAll('[data-group="'+g+'"]:checked');
  821. a.forEach(function(e){
  822. if (e!==t) {
  823. e.checked=false;
  824. upd_opts(e);
  825. }
  826. });
  827. }
  828. upd_opts(t)
  829. GM_setValue('opts', opts);
  830. if ('norld' in t.dataset) return;
  831. upd_ratings();
  832. getListRating();
  833. });
  834.  
  835. var x=0, y=0, move=dialog.querySelector('#hdr');
  836.  
  837. function moving(ev) {
  838. var st=dialog.style;
  839. var Y= (E?ev.pageY:ev.clientY) -y, X= (E?ev.pageX:ev.clientX) -x;
  840. if ( (Y<0) || (X<0) ) return;
  841. st.top=Y+'px';
  842. st.left=X+'px';
  843. st.margin='0';
  844. }
  845.  
  846. move.onmousedown=function(ev){
  847. // ignore if not left click
  848. if (ev.buttons != 1) return;
  849. ev.preventDefault();
  850. var cCss=window.getComputedStyle(dialog);
  851. x=ev.offsetX+parseInt(cCss.borderLeftWidth); y=ev.offsetY+parseInt(cCss.borderTopWidth);
  852. document.body.addEventListener('mousemove', moving);
  853. dialog.onmouseup=mouseup;
  854. dialog.classList.add('grabbed');
  855. }
  856.  
  857. function mouseup(ev){
  858. document.body.removeEventListener('mousemove', moving);
  859. dialog.onmouseup=null;
  860. dialog.classList.remove('grabbed');
  861. }
  862.  
  863. function fixed(s,n) {
  864. return ('000'+s).substr(- n);
  865. }
  866.  
  867. function upd_dialog() {
  868. if (!dialog) return;
  869. for (let k in opts) {
  870. let e=dialog.querySelector('[name="'+k+'"]');
  871. if (e) {
  872. if (e.type=='checkbox') e.checked=opts[k];
  873. else e.value=opts[k];
  874. }
  875. }
  876. if (cached && ratings) {
  877. let s='', a=ratings.t || [];
  878. if (!Array.isArray(a)) a=[a];
  879.  
  880. for (let t of a) {
  881. t=new Date(t);
  882. if (s) s+='<br>';
  883. s+=t.getFullYear()+'/'+ fixed(t.getMonth()+1,2) +'/'+ fixed(t.getDate(),2) +' '+fixed(t.getHours(),2)+':'+fixed(t.getMinutes(),2);
  884. }
  885. cached.innerHTML=s;
  886. }
  887. }
  888. upd_dialog();
  889.  
  890.  
  891. } // END display()
  892.  
  893. })();