Code:
// ==UserScript==
// @name Achievements Display
// @version 0.1
// @author Calculamatrise
// @match *://frhd.kanoapps.com/t/*
// @match *://www.freeriderhd.com/t/*
// @grant none
// @run-at document-start
// ==/UserScript==
{
let achievements, countdown, countdownTimer;
let container = createElement('div', {
children: [
createElement('a', {
children: [
createElement('span', {
innerText: 'Daily Achievements',
style: {
float: 'left'
}
}),
countdown = createElement('span', {
className: 'time-remaining',
innerText: '00:00:00',
style: {
float: 'right'
}
})
],
href: '/achievements',
style: {
borderBottom: '1px solid gray',
color: 'white',
fontFamily: 'helsinki',
paddingBottom: '5px'
}
}),
achievements = createElement('div', {
id: 'achievements-container',
style: {
display: 'flex',
flexDirection: 'column',
fontFamily: 'riffic',
gap: '0.4rem'
}
})
],
className: 'simplemodal-container',
style: {
backgroundColor: 'rgb(27 82 100)',
backgroundImage: 'linear-gradient(#1B5264,#143F4D)',
borderRadius: '1rem',
boxShadow: '0px 4px 8px 0px black',
color: 'white',
display: 'flex',
flexDirection: 'column',
gap: '0.6rem',
margin: '0 10px',
padding: '1.5rem',
width: '-webkit-fill-available'
}
});
refresh().then(function(response) {
countdown.innerText = [String(Math.floor(response.time_left / 3600)).padStart(2, '0'), String(Math.floor((response.time_left % 3600) / 60)).padStart(2, '0'), String(Math.floor(response.time_left % 60)).padStart(2, '0')].join(':');
countdownTimer = setInterval(function() {
let lastTime = countdown.innerText.split(':').map(e => parseInt(e));
if (lastTime[2] === 0) {
if (lastTime[1] === 0) {
lastTime[0]--;
lastTime[1] = 59;
}
lastTime[1]--;
lastTime[2] = 59;
}
lastTime[2]--;
lastTime.reduce((sum, remainingTime) => sum += remainingTime, 0) === 0 && clearInterval(countdownTimer);
countdown.innerText = lastTime.map(e => String(e).padStart(2, '0')).join(':');
}, 1e3)
waitForElm('#right_content').then((elm) => {
elm.prepend(container);
});
});
addEventListener('load', function() {
Application.events.subscribe('mainview.loaded', function() {
waitForElm('#right_content').then((elm) => {
elm.prepend(container);
});
});
// Application.events.subscribe('refresh', load);
Application.Helpers.AjaxHelper._check_event_notification = function(e) {
Object.getPrototypeOf(Application.Helpers.AjaxHelper)._check_event_notification.apply(this, arguments);
if ("undefined" != typeof e.data && ("undefined" != typeof e.data.achievements_earned)) {
Application.events.publish("achievementsEarned", e.data.achievements_earned);
refresh(); // add animation? // refresh everything
}
// console.log("other", e);
refresh() // only update percentages
}
});
function refresh() {
return fetch("/achievements?ajax").then(r => r.json()).then(function(response) {
achievements.replaceChildren(...response.achievements.filter(a => !a.complete).sort((a, b) => b.tot_num - a.tot_num).slice(0, 3).map(createProgressElement));
return response;
});
}
}
function createElement(type, options) {
const callback = arguments[arguments.length - 1];
const element = document.createElement(type);
if ('innerText' in options) {
element.innerText = options.innerText;
delete options.innerText;
}
for (const attribute in options) {
if (typeof options[attribute] == 'object') {
if (options[attribute] instanceof Array) {
if (/^children$/i.test(attribute)) {
element.append(...options[attribute]);
} else if (/^on/i.test(attribute)) {
for (const listener of options[attribute]) {
element.addEventListener(attribute.slice(2), listener);
}
}
} else if (/^style$/i.test(attribute)) {
Object.assign(element[attribute.toLowerCase()], options[attribute]);
}
delete options[attribute];
}
}
Object.assign(element, options);
return typeof callback == 'function' && callback(element), element;
}
function createProgressElement(achievement) {
let container = createElement('div', {
children: [
createElement('a', {
children: [
createElement('b', {
innerText: achievement.title
}),
createElement('h6', {
innerText: achievement.desc,
style: {
color: 'darkgray',
fontFamily: 'roboto_bold',
margin: 0
}
})
],
href: '',
style: {
width: '-webkit-fill-available'
}
}),
// achievement.coins
createElement('span', {
innerText: achievement.current, // achievement.progress // achievement.current + '/' + achievement.max
style: {
fontFamily: 'helsinki',
fontSize: '2rem'
}
})
],
style: {
display: 'flex',
gap: '0.25rem'
}
});
return container;
}
function waitForElm(selector) {
return new Promise(resolve => {
if (document.querySelector(selector)) {
return resolve(document.querySelector(selector));
}
const observer = new MutationObserver(mutations => {
if (document.querySelector(selector)) {
resolve(document.querySelector(selector));
observer.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
});
}