返回首頁 

Monster debuff checker for Orna.RPG

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


Install this script?
// ==UserScript==// @name         Monster debuff checker for Orna.RPG// @namespace    http://tampermonkey.net/// @version      1.4.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/*/*// @match        https://playorna.com/codex/classes/*/*// @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// @grant        GM_xmlhttpRequest// @connect      playorna.com// @connect      orna.guide// @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 GET(url) {// console.log('GET', {url});return new Promise((resolve, reject) => {GM_xmlhttpRequest({method: 'GET',url: url,anonymous: true,onload: (response) => {resolve(response)},onerror: (response) => {reject(response)},});})}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();linkToGuide(monster);initEffects(monster.effects);initStatus(monster.title);}function linkToGuide(monster) {let h1 = document.querySelector('h1.herotext');h1.innerHTML += ` <a href="https://orna.guide/search?searchstr=${monster.title || ''}" target="guide" title="check in orna.guide">🔍</a>`;}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 = '';// console.log(effects);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 initStatus(name) {let tier = Number(document.querySelector('.codex-page-meta')?.textContent?.match(/★(\d+)/)?.[1]);fetch('https://orna.guide/api/v1/monster', {method: 'post',body: JSON.stringify({name,tier: tier || null,}),}).then(r => r.json()).then(d => {if (d.length !== 1) {return;}// spawnslet catas = ['immune_to','immune_to_status','resistant_to','weak_to',];let data = d[0];let box = document.querySelector('.codex-page');if (data.immune_to_status) {data.immune_to_status.sort(sortStatus);}let html = genDetailsWrapper(catas.map(cata => !data[cata] ? '' :genDetails(_(cata),data[cata].map(i => genDetailsItem(_(i))).join(''),)).join(''))box.innerHTML += `<hr>${html}`;});}function sortStatus(a, b) {return statusOrder.findIndex(s => s === a) - statusOrder.findIndex(s => s === b);}function genStatusHtml(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 = name) {return `<li><label><input type="checkbox" value="${name}" class="cus-checker"><span>${ctx}</span></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+)/) || [,e, 100];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(location.href, 'en'));let h1 = parseHtml(html, 'h1.herotext');let title = h1[0].textContent.trim();let data = itemParse(html);let skillWord = skillWords.find(str => data[str]);let skills = itemParse(html)[skillWord];let effects = await parseSkillEffect(skills);return {title,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]);// console.log(skill.effect);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 GET(url).then(res => res.responseText)// return fetch(url).then(res => {// 	if (res.ok) {// 		return res.text();// 	}// 	window.open(res.url);// });}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;if (!div.querySelector('a[href^="/codex/classes/"]')) { // sucks learning-byall[all.currentProp].push({icon: div.querySelector('img')?.src,url: div.querySelector('a')?.href,title: div.textContent.trim(),});}}return all;}, {});delete data.currentProp;for (let i in data) {if (!data[i]?.length) {delete data[i];}}return data;}function getURL(url = location.href, lang = unsafeWindow.LANG_CODE) {if (lang === 'en') {let a = document.createElement('a');a.href = url;a.search = `lang=en`;// return `https://cors-anywhere.herokuapp.com/${a.href}`;// a.href = 'https://api.codetabs.com/v1/proxy?quest=' + a.href;return a.href;// return `https://api.allorigins.win/raw?url=${encodeURIComponent(a.href)}`;}return url;}const skillWords = ["Skills","Compétences ","Habilidades","Fähigkeiten","Умения","技能","Umiejętności","Készségek","Навички","Abilità","스킬","スキル"].map(str => str.toLowerCase());let i18n = {langs: 					['zh', 'en', ],words: {'immune_to': 	['免#', 'Immune'],'immune_to_status': ['狀態免#', 'Status Immunity'],'resistant_to': ['抗性', 'Resists'],'weak_to': 		['弱點', 'Weakness'],'Water': 			['水',],'Fire': 			['火',],'Earthen': 		['土',],'Lightning': 	['雷',],'Dark': 			['暗',],'Dragon': 		['龍',],'Arcane': 		['奧',],'Holy': 			['聖',],'Physical': 	['物',],'Asleep': 		['入睡',],'Bleeding': 	['流血',],'Blight': 		['枯萎',],'Blind': 			['致盲',],'Burning': 		['燃燒',],'Confused': 	['迷惑',],'Cursed': 		['詛咒',],'Dark Sigil': ['暗之印記',],'Darkblight': ['暗黑#病',],'Doom': 			['厄運'],'Foresight ↓': ['預知 ↓'],'Frozen': ['冰凍'],'Lulled': ['恍惚'],'Paralyzed': ['麻痺'],'Petrified': ['石化'],'Poisoned': ['中#'],'Rot': ['#敗'],'Starstruck': ['暈星'],'Stasis': ['停滯'],'Stunned': ['暈眩'],'Toxic': ['劇#'],'Windswept': ['逆風'],},};const statusOrder = ['Poisoned','Bleeding','Burning','Frozen','Paralyzed','Rot','Cursed','Toxic','Blind','Asleep','Lulled','Drenched','Stunned','Blight','Petrified','Stasis','Doom','Confused',]let langIndex = i18n.langs.findIndex(lang => lang === unsafeWindow.LANG_CODE?.replace(/-.+/, ''));// get i18nfunction _(key) {return i18n.words[key]?.[langIndex] || key;}