Monster debuff checker for Orna.RPG

Let you check monster's debuff in official Orna Codex page.

当前为 2022-09-15 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Monster debuff checker for Orna.RPG
// @namespace    http://tampermonkey.net/
// @version      1.1.2
// @description  Let you check monster's debuff in official Orna Codex page.
// @author       RplusTW
// @match        https://playorna.com/codex/raids/*/*
// @match        https://playorna.com/codex/bosses/*/*
// @match        https://playorna.com/codex/followers/*/*
// @match        https://playorna.com/codex/monsters/*/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=playorna.com
// @require      https://cdn.jsdelivr.net/npm/[email protected]
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-end
// @license MIT
// ==/UserScript==

let autoInit = GM_getValue('autoInit') || false;

GM_registerMenuCommand('Auto Init. ?', toggleAutoInit, 'A');
function toggleAutoInit() {
	autoInit = window.confirm('Enable Auto initialize for debuff checker?')
	GM_setValue('autoInit', autoInit);
}


window.addEventListener('load', function() {
	if (autoInit) {
		init();
	} else {
		document.querySelector('.codex-page-icon')?.addEventListener('dblclick', init, { once: true, });
	}
}, false);

async function init() {
	let style = document.createElement('style');
	style.textContent = `.cus-checker{opacity:.3}.cus-checker:checked{opacity:.75}.cus-checker:checked+*{opacity:.5}`;
	document.head.append(style);
	collapsePage();
	let monster = await getEnInfo();
	initEffects(monster.effects);
}

function collapsePage() {
	let tags = [...document.querySelectorAll('.codex-page h4, .codex-page h4 ~ div')];
	if (!tags.length) { return; }

	let box = null;

	let sections = tags.reduce((all, tag) => {
		if (tag.tagName === 'H4') {
			all[all.length] = [
				tag,
				[]
			];
		} else if (tag.tagName === 'DIV') {
			all[all.length - 1][1].push(genDetailsItem('', tag.innerHTML));
			tag.remove();
		}
		return all;
	}, []);

	sections.forEach(section => {
		section[0].insertAdjacentHTML(
			'beforebegin',
			genDetailsWrapper(
				genDetails(
					section[0].textContent.trim(),
					section[1].join('')
				)
			)
		);
		section[0].remove();
	});
}

function initEffects(effects) {
	let box = document.querySelector('.codex-page');
	let html = '';
	for (let prop in effects) {
		// effects[prop] = slimEffects(effects[prop]);
		html += genEffectHtml(prop, slimEffects(effects[prop]));
	};
	box.innerHTML += `<hr>${genDetailsWrapper(html)}`;
}

function genEffectHtml(prop, effects) {
	let items = effects.map(eff => genDetailsItem(eff[0], `
		<span>
			${eff[0]},
			<sub>${eff[1].join()}%</sub>
		</span>
	`)).join('');

	return genDetails(prop, items);
}

function genDetailsItem(name, ctx) {
	return `
		<li>
			<label>
				<input type="checkbox" name="${name}" class="cus-checker">
				${ctx}
			</label>
		</li>
	`;
}

function genDetailsWrapper(html) {
	return `<div style="display:flex;justify-content:space-evenly;flex-wrap:wrap;">${html}</div>`
}

function genDetails(title, listHtml) {
	return `
		<details open style="width:fit-content;">
			<summary style="text-transform:capitalize;">
				${title}
			</summary>
			<ul style="list-style:none;text-align:start;padding:0;">${listHtml}</ul>
		</details>`
}

function slimEffects(effects) {
	let eff = effects.reduce((all, e) => {
		let o = e.match(/^(\D+)\s\((\d+)/);
		all[o[1]] = all[o[1]] || [];
		all[o[1]].push(+o[2]);
		return all;
	}, {});

	return Object.keys(eff).map(prop => {
		return [prop, [...new Set(eff[prop])].sort().reverse()];
	}).sort((a, b) => a[0].localeCompare(b[0]));
	return eff;
}

async function getEnInfo() {
	let html = await getUrlSource(getURL());
	let h1 = parseHtml(html, 'h1.herotext');
	let data = itemParse(html);
	let skillWord = skillWords.find(str => data[str]);
	let skills = itemParse(html)[skillWord];
	let effects = await parseSkillEffect(skills);
	return {
		title: h1[0].textContent.trim(),
		skills,
		effects,
	};
}

async function parseSkillEffect(skills) {
	// getURL()
	let sources = await Promise.all(
		skills.map( skill => getUrlSource(getURL(skill.url)) )
	);

	let effects = skills.reduce((all, skill, index) => {
		skill.effect = itemParse(sources[index]);
		for (let prop in skill.effect) {
			if (!all[prop]) {
				all[prop] = [];
			}
			let _es = skill.effect[prop].map(e => e.title);
			all[prop] = all[prop].concat(_es);
		}
		return all;
	}, {});

	return effects;
}

async function getUrlSource(url) {
	return fetch(url).then(res => res.text());
}

function parseHtml(html, selectoor = '') {
	let doc = document.implementation.createHTMLDocument();
	doc.body.innerHTML = html;
	return [...doc.querySelectorAll(selectoor)];
}

function itemParse(html) {
	let dataDivs = parseHtml(html, '.codex-page h4, .codex-page h4 ~ div');
	let data = dataDivs.reduce((all, div) => {
		if (div.tagName === 'H4') {
			let _prop = div.textContent.replace(/[::]/, '').trim().toLowerCase();
			all.currentProp = _prop;
			all[_prop] = all[_prop] || [];
		} else if (div.tagName === 'DIV') {
			let icon = div.querySelector('img')?.src;
			all[all.currentProp].push({
				icon: div.querySelector('img')?.src,
				url: div.querySelector('a')?.href,
				title: div.textContent.trim(),
			});
		}
		return all;
	}, {});
	delete data.currentProp;
	return data;
}

function getURL(url = location.href, lang = 'en') {
	return url;
	// let a = document.createElement('a');
	// a.href = url;
	// a.search = `lang=${lang}`;
	// a.href = 'https://api.codetabs.com/v1/proxy?quest=' + a.href;
	// return a.href;
}

const skillWords = [
  "Skills",
  "Compétences ",
  "Habilidades",
  "Fähigkeiten",
  "Умения",
  "技能",
  "Umiejętności",
  "Készségek",
  "Навички",
  "Abilità",
  "스킬",
  "スキル"
].map(str => str.toLowerCase());