// ==UserScript== // @name BetterFollowingList // @namespace Morimasa // @author Morimasa // @description Tweaks to following lists on media pages // @match https://anilist.co/* // @grant none // @version 0.02 // @downloadURL none // ==/UserScript== let apiCalls = 0; const stats = { element: null, count:0, } const scoreColors = e => { let el = e.querySelector('span') || e.querySelector('svg'); let light = document.body.classList[0]==='site-theme-dark'?45:38; if (el===null) return; else if (el.nodeName==='svg'){ // smiley const scoremap = {'smile': 90, 'meh': 60, 'frown': 30} el.childNodes[0].setAttribute('fill', `hsl(${scoremap[el.dataset.icon]}, 100%, ${light}%)`) } else if (el.nodeName==='SPAN'){ let score = el.innerText.split('/'); score = score.length==1?parseInt(score)*20-10:parseInt(score[1])==10?parseFloat(score[0])*10:parseInt(score[0]); // convert stars, 10 point and 10 point decimal to 100 point el.style.color = `hsl(${score}, 100%, ${light}%)`; if (score>100) console.log('why score is bigger than 100?', el); } } const handler = (data, target, idMap) => { if (target===undefined) return; data.forEach(e=>{ target[idMap[e.user.id]].style.gridTemplateColumns='30px 1.3fr .7fr 1fr .5fr'; const progress = document.createElement('DIV'); progress.innerText = `${e.progress}/${e.media.chapters||e.media.episodes||'?'}`; target[idMap[e.user.id]].insertBefore(progress, target[idMap[e.user.id]].children[2]) }) } const getAPI = (target, elMap) => { let user = []; for (let u in elMap) user.push(u); if (user.length===0) return; console.log(`BetterFollowingList: quering ${user.length} users | ${++apiCalls} api calls since reload`) const mediaID = window.location.pathname.split("/")[2]; const query = 'query($u:[Int],$media:Int){Page {mediaList(userId_in:$u,mediaId:$media){mediaId progress user{id name}media{chapters episodes}}}}' const vars = {u: user, media: mediaID}; const options = { method: 'POST', body: JSON.stringify({query: query, variables: vars}), headers: new Headers({ 'Content-Type': 'application/json' }) }; return fetch('https://graphql.anilist.co/', options) .then(res => res.json()) .then(res => handler(res.data.Page.mediaList, target, elMap)) .catch(error => console.error(`Error: ${error}`)); } const createStat = (text, number) => { let el = document.createElement('span'); el.innerText = text; el.appendChild(document.createElement('span')) el.children[0].innerText = number return el } const MakeStats = () => { if(stats.element) return; // element already injected let main = document.createElement('h2'); let count = createStat('Users: ', stats.count); main.append(count); const parent = document.querySelector('.following'); parent.prepend(main); stats.element = main; } let observer = new MutationObserver(() => { if (window.location.pathname.match(/\/(anime|manga)\/\d+\/.+\/social/)){ MakeStats(); const follows = document.querySelectorAll('.follow'); let idmap = {}; follows.forEach((e, i)=>{ if (e.childNodes.length==7 && !e.dataset.changed){ const avatarURL = e.querySelector('.avatar').dataset.src; if (!avatarURL) return const id = avatarURL.split('/').pop().match(/\d+/g)[0]; idmap[id] = i; // score collors scoreColors(e); // add count ++stats.count; // set state e.dataset.changed=true; } }) if (Object.keys(idmap).length>0){ getAPI(follows, idmap); let statsElements = stats.element.querySelectorAll('span>span'); statsElements[0].innerText = stats.count } } else { stats.element=null; stats.count=0; } }); observer.observe(document.getElementById('app'), {childList: true, subtree: true});