chuni-net - display level

Display song levels on chunithm-net

// ==UserScript==
// @name        chuni-net - display level
// @namespace   esterTion
// @license     MIT
// @match       https://chunithm-net-eng.com/*
// @match       https://new.chunithm-net.com/*
// @match       https://chunithm.wahlap.com/*
// @grant       GM.xmlHttpRequest
// @version     1.0.5
// @author      esterTion
// @description Display song levels on chunithm-net
// @run-at      document-end
// ==/UserScript==

(async function() {

const host = location.hostname
const server = host === 'new.chunithm-net.com' ? 'jp' : host === 'chunithm-net-eng.com' ? 'ex' : host === 'chunithm.wahlap.com' ? 'cn' : ''
if (!server) throw new Error('unknown server')

// createElement
function _(e,t,i){var a=null;if("text"===e)return document.createTextNode(t);a=document.createElement(e);for(var n in t)if("style"===n)for(var o in t.style)a.style[o]=t.style[o];else if("className"===n)a.className=t[n];else if("event"===n)for(var o in t.event)a.addEventListener(o,t.event[o]);else a.setAttribute(n,t[n]);if(i)if("string"==typeof i)a.innerHTML=i;else if(Array.isArray(i))for(var l=0;l<i.length;l++)null!=i[l]&&a.appendChild(i[l]);return a}

const localStorageTimeKey = 'CNDL_music_level_info_time'
const localStorageDataKey = 'CNDL_music_level_info'
let musicLevelInfo = {}
function loadLocalInfo() {
	if (!localStorage[localStorageDataKey]) return
	musicLevelInfo = JSON.parse(localStorage[localStorageDataKey])
}
function checkUpdateForLocalInfo() {
	const today = getDateStringForUpdate()
	if (!localStorage[localStorageTimeKey] || localStorage[localStorageTimeKey] !== today) {
		downloadInfo(today)
	}
}
async function downloadInfo(today) {
	console.log('downloading music info')
	switch (server) {
		case 'jp': {
			const resp = await Promise.all([
				fetchJson('https://estertion.win/__private__/chuni-jp-base-music.json'),
				fetchJson('https://estertion.win/__private__/chuni-jp-doc-music.json'),
				fetchJson('https://chunithm.sega.jp/storage/json/music.json'),
			])
			fillInfo(resp[0])
			fillInfoNonMatch(resp[1])
			fillInfoNonMatch(resp[2])
			break;
		}
		case 'ex': {
			const resp = await Promise.all([
				fetchJson('https://estertion.win/__private__/chuni-intl-option-music.json'),
				fetchJson('https://chunithm.sega.com/assets/data/music.json'),
			])
			fillInfo(resp[0])
			fillInfoNonMatch(resp[1])
			break;
		}
		case 'cn': {
			await fetchJson('https://estertion.win/__private__/chuni-chn-music.json').then(fillInfo)
			break;
		}
	}
	localStorage[localStorageDataKey] = JSON.stringify(musicLevelInfo)
	localStorage[localStorageTimeKey] = today
	console.log('stored music info: ', Object.keys(musicLevelInfo).length, 'entries')
	addLevelToPage()
}
function fillInfoNonMatch(list) {
	fillInfo(list, false)
}
function fillInfo(list, alwaysOverwrite = true) {
	list.forEach(i => {
		if (i.we_kanji) return
		const title = i.title.trim()
		const music = {}
		if (musicLevelInfo[title]) {
			Object.assign(music, musicLevelInfo[title])
		}
		Object.assign(music, Object.fromEntries(
			Object.keys(i).filter(k => k.startsWith('lev_')&&i[k]!=='').filter(k => alwaysOverwrite || isDifferentLevelTag(music[k.substring(4)], i[k])).map(k => ([k.substring(4), i[k]]))
		))
		musicLevelInfo[title] = music
	})
}
function isDifferentLevelTag(l1, l2) {
	if (!l1 || !l2) return true
	if (l1.indexOf('.') !== -1 && l2.indexOf('.') === -1) return false
	if (l1.indexOf('.') === -1 && l2.indexOf('.') !== -1) return true
	return Math.floor(l1.replace(/\+/, '.5')*2) !== Math.floor(l2.replace(/\+/, '.5')*2)
}

function getDateStringForUpdate() {
	const d = new Date
	d.setTime(d.getTime() + d.getTimezoneOffset() * 60e3 + {jp:11,ex:11,cn:10}[server]*3600e3)
	return [d.getUTCFullYear(), d.getUTCMonth()+1, d.getUTCDate()].join('/')
}
function fetchJson(url) {
	return new Promise((res, rej) => {
		GM.xmlHttpRequest({
			url: url + '?_=' + Date.now(),
			responseType: 'json',
			method: 'GET',

			onload: r => res(r.response),
			onerror: e => rej(e),
		})
	})
}

function addLevelToPage() {
	[...document.querySelectorAll('.CNDL_level_container')].forEach(i => i.remove());
	[...document.querySelectorAll('.play_track_block+.play_musicdata_block .play_musicdata_title')].forEach(addLevelToPlaylog);
	[...document.querySelectorAll('.course_musicdata_title_text')].forEach(addLevelToCourse);
	[...document.querySelectorAll('.musiclist_box .music_title')].forEach(addLevelToList);
	[...document.querySelectorAll('.music_box .play_musicdata_icon')].forEach(addLevelToDetail);
	[...document.querySelectorAll('.CNDL_level_container')].forEach(i => i.textContent===''&&i.remove());
}
function addLevelToPlaylog(titleDiv) {
	const difImg = titleDiv.parentNode.parentNode.querySelector('.play_track_result img')
	const path = difImg.getAttribute('src').split('/').pop().split('?')[0]
	const dif = getDifFromPath(path)
	if (!dif) return
	const levelDiv = _('div', {style:{float:'right',padding:'5px 0',height:'42px',lineHeight:'30px',fontFamily:'Arial'}})
	titleDiv.parentNode.insertBefore(levelDiv, titleDiv)
	addLevelToContainer(titleDiv.textContent.trim(), dif, levelDiv)
}
function addLevelToCourse(titleDiv) {
	const difImg = titleDiv.parentNode.parentNode.querySelector('.course_musicdata_track_diff img,.course_musicdata_diff img')
	const path = difImg.getAttribute('src').split('/').pop().split('?')[0]
	const dif = getDifFromPath(path)
	if (!dif) return
	const levelDiv = _('div', {style:{float:'right',fontFamily:'Arial',fontWeight:'initial'}})
	titleDiv.insertBefore(levelDiv, titleDiv.lastChild)
	addLevelToContainer(titleDiv.textContent.trim(), dif, levelDiv)
}
function addLevelToList(titleDiv) {
	const dif = getDifFromClass(titleDiv.parentNode)
	if (!dif) return
	const levelDiv = _('div', {style:{float:'right',fontFamily:'Arial',fontWeight:'initial'}})
	titleDiv.insertBefore(levelDiv, titleDiv.lastChild)
	addLevelToContainer(titleDiv.textContent.trim(), dif, levelDiv)
}
function addLevelToDetail(iconDiv) {
	const difDiv = iconDiv.parentNode.parentNode.firstElementChild
	const dif = getDifFromClass(difDiv)
	if (!dif) return
	const levelDiv = _('span', {style:{paddingLeft: '1em',fontFamily:'Arial',fontWeight:'initial'}})
	difDiv.appendChild(levelDiv)
	addLevelToContainer(document.querySelector('.play_musicdata_title').textContent.trim(), dif, levelDiv)
}
function addLevelToContainer(title, dif, levelDiv) {
	levelDiv.className = 'CNDL_level_container'
	if (!musicLevelInfo[title]) return
	if (!musicLevelInfo[title][dif]) return
	levelDiv.textContent = musicLevelInfo[title][dif]
}
function getDifFromPath(path) {
	switch (path) {
		case 'musiclevel_basic.png': { return 'bas' }
		case 'musiclevel_advanced.png': { return 'adv' }
		case 'musiclevel_expert.png': { return 'exp' }
		case 'musiclevel_master.png': { return 'mas' }
		case 'musiclevel_ultimate.png': { return 'ult' }
	}
	return null
}
function getDifFromClass(div) {
	const divClass = [...div.classList].filter(i => i.startsWith('bg_') || i.startsWith('title_'))
	if (!divClass.length) return null
	switch (divClass[0]) {
		case 'bg_basic': { return 'bas' }
		case 'bg_advanced': { return 'adv' }
		case 'bg_expert': { return 'exp' }
		case 'bg_master': { return 'mas' }
		case 'bg_ultima': { return 'ult' }

		case 'title_basic': { return 'bas' }
		case 'title_advanced': { return 'adv' }
		case 'title_expert': { return 'exp' }
		case 'title_master': { return 'mas' }
		case 'title_ultima': { return 'ult' }
	}
	return null
}
function fixEmblemOverflow() {
	const style = _('style', {}, `#wrap{overflow:hidden}`)
	document.head.appendChild(style)
}

fixEmblemOverflow()
loadLocalInfo()
checkUpdateForLocalInfo()
addLevelToPage()

})()