chuni-net - display overpower

Display song levels on chunithm-net

As of 2024-03-25. See the latest version.

// ==UserScript==
// @name        chuni-net - display overpower
// @namespace   esterTion
// @license     MIT
// @match       https://chunithm-net-eng.com/mobile/record/music*
// @match       https://chunithm.wahlap.com/mobile/record/music*
// @version     1.0.3
// @author      esterTion
// @description Display song levels on chunithm-net
// ==/UserScript==

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')

const disabledSongs = {
  ex: [],
  cn: ["\u3054\u307e\u304b\u3057","\u771f\u8d64\u306a\u8a93\u3044","\u6b69\u3044\u3066\u3044\u3053\u3046\uff01","\u541b\u306e\u305b\u3044","Phantom Joke"],
}
if (Date.now() > 1711580400000) { // 2024-03-28 07:00
  disabledSongs.cn = ["\u3054\u307e\u304b\u3057","\u771f\u8d64\u306a\u8a93\u3044","\u6b69\u3044\u3066\u3044\u3053\u3046\uff01","\u541b\u306e\u305b\u3044","Phantom Joke","\u30b5\u30af\u30ea\u30d5\u30a1\u30a4\u30b9"]
}

// 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])
}
let resultCount = {
  sssp: 0,
  sss: 0,
  ssp: 0,
  ss: 0,
  sp: 0,
  s: 0,
  other: 0,

  max: 0,
  aj: 0,
  fc: 0,
  nonfc: 0,
}
function addOverPowerToPage() {
  const musics = [...document.querySelectorAll('.musiclist_box .music_title')].map(addLevelToList)
  if (!musics.length) return
  const total = musics.reduce((a, i) => (i.disabled ? a : {rate: a.rate+(i.rate), max: a.max+i.max, playedmax: a.playedmax+(i.rate>0?i.max:0)}), {rate: 0, max: 0, playedmax: 0})
  total.rate /= 100
  total.max /= 100
  total.playedmax /= 100
  console.log(total, total.rate/total.max*100)

  const countedSize = musics.filter(i => !i.disabled).length
  const scoreList = document.querySelector('#scoreList_result')
  if (!scoreList) return
  scoreList.appendChild(_('div', {className: 'box01 w420'}, [
    _('style', {}, `

.overpower-graph-base {
  position:relative;
  width:100%;
  height:22px;
  background:#6e6e6e;
  outline:#dddddd 1px solid
}
.overpower-graph-base .bg-gold,
.overpower-graph-base .bg-platinum,
.overpower-graph-base .bg-silver,
.overpower-graph-base .bg-white {
  position:absolute;
  left:0;
  top:0;
  bottom:0
}
.overpower-graph-base .bg-white {
  z-index:12;
  background:linear-gradient(180deg,#fff,#fff 50%,#d0d6da 50%,#e4edf3 75%,#fff)!important
}
.overpower-graph-base .bg-platinum {
  z-index:10;
  background:linear-gradient(180deg,#fff1ba,#ffeb9c 50%,#fc0 50%,#ffeca4 75%,#fff)!important
}
.overpower-graph-base .bg-gold {
  z-index:8;
  background:linear-gradient(180deg,#ffe4a3,#efae10 50%,#ff8300 50%,#ffc947 75%,#fff)!important
}
.overpower-graph-base .bg-silver {
  z-index:6;
  background:linear-gradient(180deg,#c8e7ff,#8fceff 50%,#6eb5ff 50%,#b7ddff 75%,#fff)!important
}

    `),
    _('div', {className: 'narrow_block clearfix', style: {whiteSpace:'pre-wrap'}}, [
      _('text', [
        `共计 ${countedSize} 曲目`,
        `${total.rate} / ${total.max}`,
        (total.rate/total.max*100).toFixed(4)+'%',
        '',
        '已游玩平均:',
        //`${total.rate} / ${total.playedmax}`,
        (total.rate/total.playedmax*100).toFixed(4)+'%',
        '','',
      ].join('\n')),
      _('table', { style: {width: '100%', fontSize: '16px'} }, [
        _('tr', {}, [
          _('td', {}, [_('text', 'SSS+')]),
          _('td', {}, [_('text', 'SSS')]),
          _('td', {}, [_('text', 'SS+')]),
          _('td', {}, [_('text', 'SS')]),
          _('td', {}, [_('text', 'S+')]),
          _('td', {}, [_('text', 'S')]),
          _('td', {}, [_('text', 'OTHER')]),
        ]),
        _('tr', {}, [
          _('td', {}, [_('text', resultCount.sssp)]),
          _('td', {}, [_('text', resultCount.sss - resultCount.sssp)]),
          _('td', {}, [_('text', resultCount.ssp - resultCount.sss)]),
          _('td', {}, [_('text', resultCount.ss - resultCount.ssp)]),
          _('td', {}, [_('text', resultCount.sp - resultCount.ss)]),
          _('td', {}, [_('text', resultCount.s - resultCount.sp)]),
          _('td', {}, [_('text', resultCount.other)]),
        ]),
      ]),
      _('div', {className: 'overpower-graph-base'}, [
        _('div', {className: 'bg-platinum', style: {width: `${resultCount.sss/countedSize*100}%`}}),
        _('div', {className: 'bg-gold', style: {width: `${resultCount.ss/countedSize*100}%`}}),
        _('div', {className: 'bg-silver', style: {width: `${resultCount.s/countedSize*100}%`}}),
      ]),
      _('table', { style: {width: '100%', fontSize: '16px'} }, [
        _('tr', {}, [
          _('td', {}, [_('text', 'MAX')]),
          _('td', {}, [_('text', 'AJ')]),
          _('td', {}, [_('text', 'FC')]),
          _('td', {}, [_('text', 'OTHER')]),
        ]),
        _('tr', {}, [
          _('td', {}, [_('text', resultCount.max)]),
          _('td', {}, [_('text', resultCount.aj - resultCount.max)]),
          _('td', {}, [_('text', resultCount.fc - resultCount.aj)]),
          _('td', {}, [_('text', resultCount.nonfc)]),
        ]),
      ]),
      _('div', {className: 'overpower-graph-base'}, [
        _('div', {className: 'bg-white', style: {width: `${resultCount.max/countedSize*100}%`}}),
        _('div', {className: 'bg-platinum', style: {width: `${resultCount.aj/countedSize*100}%`}}),
        _('div', {className: 'bg-gold', style: {width: `${resultCount.fc/countedSize*100}%`}}),
      ]),
    ]),
  ]))
}
function addLevelToList(titleDiv) {
  const dif = getDifFromClass(titleDiv.parentNode)
  if (!dif) return null
  const box = titleDiv.parentNode
  const titleText = titleDiv.lastChild.textContent.trim()
  const level = getLevelByTitleAndDif(titleText, dif) * 1
  const returnData = {
    rate: 0,
    disabled: disabledSongs[server].includes(titleText),
    max: (level+3) * 500
  }
  resultCount.other++
  const scoreBox = box.querySelector('.play_musicdata_highscore .text_b')
  if (!scoreBox) return returnData
  if (returnData.disabled) {
    resultCount.other--
    return returnData
  }
  const obj = {
    scoreMax:      scoreBox.textContent.replace(/,/g, '')*1,
    maxComboCount: 0,
    isFullCombo:   false,
    isAllJustice:  false,
    isSuccess:     0,
    fullChain:     0,
    maxChain:      0,
    scoreRank:     0,
    isLock:        false,
    theoryCount:   0,
  }
  if (obj.scoreMax === 1010000) { obj.theoryCount = 1; }
  [...box.querySelectorAll('.play_musicdata_icon img')].forEach(i => {
    const icon = i.src.replace(/.+\//, '').replace(/icon_(.+)\.png/, '$1')
    switch (icon) {
      case 'clear': { obj.isSuccess = 1; break }
      case 'hard': { obj.isSuccess = 2; break }
      case 'absolute': { obj.isSuccess = 3; break }
      case 'absolutep': { obj.isSuccess = 4; break }
      case 'absolutepp': { obj.isSuccess = 5; break }
      case 'catastrophy': { obj.isSuccess = 6; break }
      
      case 'rank_1': { obj.scoreRank = 1; break }
      case 'rank_2': { obj.scoreRank = 2; break }
      case 'rank_3': { obj.scoreRank = 3; break }
      case 'rank_4': { obj.scoreRank = 4; break }
      case 'rank_5': { obj.scoreRank = 5; break }
      case 'rank_6': { obj.scoreRank = 6; break }
      case 'rank_7': { obj.scoreRank = 7; break }
      case 'rank_8': { obj.scoreRank = 8; break }
      case 'rank_9': { obj.scoreRank = 9; break }
      case 'rank_10': { obj.scoreRank = 10; break }
      case 'rank_11': { obj.scoreRank = 11; break }
      case 'rank_12': { obj.scoreRank = 12; break }
      case 'rank_13': { obj.scoreRank = 13; break }
      
      case 'alljustice': { obj.isAllJustice = true }
      case 'fullcombo': { obj.isFullCombo = true; obj.missCount = 0; break }
      
      case 'fullchain2': { obj.fullChain = 1; break }
      case 'fullchain': { obj.fullChain = 2; break }
    }
  })

  if (obj.scoreRank >=8) { resultCount.other--; }
  if (obj.scoreRank >=8) { resultCount.s++; }
  if (obj.scoreRank >=9) { resultCount.sp++; }
  if (obj.scoreRank >=10) { resultCount.ss++; }
  if (obj.scoreRank >=11) { resultCount.ssp++; }
  if (obj.scoreRank >=12) { resultCount.sss++; }
  if (obj.scoreRank >=13) { resultCount.sssp++; }
  if (obj.theoryCount) { resultCount.max++; }

  if (obj.isAllJustice) { resultCount.aj++; }
  if (obj.isFullCombo) { resultCount.fc++; }
  if (!obj.isFullCombo) { resultCount.nonfc++; }

  returnData.rating = getRatingFromConstantAndScore(level, obj.scoreMax)
  const comboRate = obj.theoryCount ? 125 :
                    obj.isAllJustice ? 100 :
                    obj.isFullCombo ? 50 : 0
  if (obj.scoreMax <= 1007500) {
    // 低于sss
    returnData.rate = Math.floor(returnData.rating*500 * 2) / 2 + comboRate
    addOverPowerAfterScore(scoreBox, returnData)
    return returnData
  }
  const sssRate = Math.floor((obj.scoreMax - 1007500) * 0.15 * 2) / 2
  returnData.rate = (level + 2) * 500 + comboRate + sssRate
  addOverPowerAfterScore(scoreBox, returnData)
  return returnData
}
function addOverPowerAfterScore(scoreBox, returnData) {
  scoreBox.parentNode.appendChild(_('span', {style: {marginLeft: '2em', fontFamily: 'Arial'}}, [
    _('text', (Math.floor(returnData.rate)/100).toFixed(2) + ' / ' + (returnData.max/100).toFixed(2) + ' ' + (returnData.rate/returnData.max*100).toFixed(2)+'%'),
  ]))
}
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 getLevelByTitleAndDif(title, dif) {
  if (!musicLevelInfo[title]) return 0
  if (!musicLevelInfo[title][dif]) return 0
  return musicLevelInfo[title][dif]
}
var ratingBorder = [
  [1009000, +2.15, 1],
  [1007500, +2.0, 1],
  [1005000, +1.5, 1],
  [1000000, +1.0, 1],
  [975000, +0.0, 1],
  [925000, -3.0, 1],
  [900000, -5.0, 1],
  [800000, -5.0, 2],
  [500000, 0, 1]
];
function getRatingFromConstantAndScore(constant, score) {
  if (constant == 0) return 0;
  if (score >= ratingBorder[0][0]) return constant + ratingBorder[0][1];
  for (var i = 0; i < ratingBorder.length - 1; i++) {
    if (score >= ratingBorder[i + 1][0]) {
      return (constant + ratingBorder[i + 1][1]) / ratingBorder[i + 1][2]
      + (score - ratingBorder[i + 1][0]) / (ratingBorder[i][0] - ratingBorder[i + 1][0])
      * (
      (constant + ratingBorder[i][1]) / ratingBorder[i][2] -
      (constant + ratingBorder[i + 1][1]) / ratingBorder[i + 1][2]
      )
    }
  }
  return 0;
}

loadLocalInfo()
addOverPowerToPage()