Last active
December 4, 2022 09:10
-
-
Save Rplus/3ddc17e84a4db125b4ed6a033cff5af8 to your computer and use it in GitHub Desktop.
bookemarklet to check-orna-item-quality
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
javascript: (function() { | |
var s = document.createElement('script'); | |
s.setAttribute('src', 'https://cdn.jsdelivr.net/npm/lil-gui@0.17'); | |
document.body.appendChild(s); | |
s.onload = () => { | |
var GUI = lil.GUI; | |
var itemname = location.href.match(/items\/([^/]+)/); | |
var data = { | |
'%': 100, | |
'assess': () => { | |
window.open(`https://orna.guide/search?searchstr=${itemname?.[1].replace(/\W/g, ' ')}`); | |
}, | |
}; | |
let div = document.createElement('div'); | |
document.querySelector('.codex-stats').after(div); | |
div.style = `display:flex;justify-content:center;`; | |
var gui = new GUI({ | |
autoPlace: false, | |
container: div, | |
}); | |
let stats = document.querySelectorAll('.codex-stat'); | |
stats.forEach(stat => { | |
let info = stat.textContent.trim().match(/(\D+)(\d+)/); | |
let p = info?.[1]; | |
let v = info?.[2]; | |
if (p) { | |
data[p] = +v; | |
gui.add(data, p, ~~(v * 0.7), v * 2, 1).onChange(_v => { | |
quality.setValue(~~(100 * _v / +v)) | |
}); | |
} | |
}); | |
let quality = gui.add(data, '%', 70, 200); | |
gui.add(data, 'assess').name('GO TO ASSESS'); | |
}; | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Item quality checker for Orna.RPG | |
// @namespace http://tampermonkey.net/ | |
// @version 1.2.2 | |
// @description Let you easily calculate item quality in official Orna Codex page. | |
// @author RplusTW | |
// @match https://playorna.com/codex/items/*/ | |
// @match https://playorna.com/codex/items/*/?* | |
// @icon https://www.google.com/s2/favicons?sz=64&domain=playorna.com | |
// @require https://cdn.jsdelivr.net/npm/lil-gui@0.17 | |
// @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', async function() { | |
'use strict'; | |
if (autoInit) { | |
init(); | |
} else { | |
document.querySelector('.codex-page-icon')?.addEventListener('dblclick', init, { once: true, }); | |
} | |
async function init() { | |
var s = document.createElement('script'); | |
s.setAttribute('src', 'https://cdn.jsdelivr.net/npm/lil-gui@0.17'); | |
document.body.appendChild(s); | |
s.onload = scriptOnload; | |
} | |
}, false); | |
async function scriptOnload() { | |
let GUI = window.lil.GUI; | |
let urlPath = location.href.match(/items\/([^/]+)/); | |
let assessUrl = `https://orna.guide/search?searchstr=${urlPath?.[1].replace(/\W/g, ' ')}`; | |
let gid = ''; | |
let data = { | |
'%': 100, | |
'assess_guide': () => { window.open(assessUrl, 'guide'); }, | |
'assess_api': () => { initAssess(gid); }, | |
} | |
let div = document.createElement('div'); | |
div.id = 'gui-div'; | |
let statsDiv = document.querySelector('.codex-stats'); | |
statsDiv.after(div); | |
div.style = `display:flex;justify-content:center;flex-wrap:wrap;`; | |
var gui = new GUI({ | |
autoPlace: false, | |
container: div, | |
}); | |
let stats = getStatValues(statsDiv); | |
console.log({stats}); | |
stats?.forEach(stat => { | |
data[stat.prop] = stat.value; | |
gui.add( | |
data, stat.prop, | |
~~(stat.value * 0.7), | |
stat.value * 2, | |
1 | |
) | |
.onChange(_v => { | |
quality.setValue(~~(100 * _v / stat.value)) | |
}); | |
}); | |
let quality = gui.add(data, '%', 70, 200); | |
let assessFolder = gui.addFolder( 'Assess' ); | |
assessFolder.close(); | |
let assessOnGuide = assessFolder.add(data, 'assess_guide').name(`🔍 on Orna.Guide `); | |
let itemInfo = await getItemInfo(); | |
gid = itemInfo.id; | |
if (gid) { | |
assessUrl = `https://orna.guide/items?show=${gid}`; | |
assessOnGuide.name(`🔍 ${itemInfo.name} on Orna.Guide 🔗`); | |
let assessByAPI = assessFolder.add(data, 'assess_api').name(`Assess ${itemInfo.name} Here! 🌟`); | |
} | |
} | |
function getStatValues(statDom) { | |
let statDivs = [...statDom.querySelectorAll('.codex-stat')]; | |
if (!statDivs?.length) { | |
return null; | |
} | |
return statDivs.map(div => { | |
let info = div.textContent.trim().match(/(\D+)(\d+)/); | |
let prop = info?.[1].trim().replace(':', '').toLowerCase(); | |
let value = +info?.[2]; | |
return { | |
prop, | |
value, | |
}; | |
}); | |
} | |
function initAssess(gid) { | |
if (window.assessInited) { | |
return; | |
} | |
window.assessInited = true; | |
let divForm = document.createElement('div'); | |
let resultBox = document.createElement('details'); | |
resultBox.style = 'width:100%'; | |
let optionsHtml = ['level', 'attack', 'defense', 'magic', 'resistance', 'hp', 'mana', 'dexterity', 'ward', 'crit', ] | |
.map(i => genLabel(i)).join(''); | |
divForm.innerHTML = ` | |
<details open> | |
<form id="assess-form" class="lil-gui"> | |
${genLabel('id', gid, 'readonly')} | |
${optionsHtml} | |
<div class="controller"> | |
<div class="widget"><input type="submit"></div> | |
<div class="widget"></div> | |
<div class="widget"><input type="reset"></div> | |
</div> | |
</form> | |
</details>`; | |
document.querySelector('#gui-div').appendChild(divForm); | |
document.querySelector('#gui-div').appendChild(resultBox); | |
let form = divForm.querySelector('#assess-form'); | |
form.addEventListener('submit', (e) => { | |
e.preventDefault(); | |
const formData = {}; | |
for (const pair of new FormData(form)) { | |
if (pair[1]) { | |
formData[pair[0]] = +pair[1]; | |
} | |
} | |
fetch('https://orna.guide/api/v1/assess', { | |
method: 'post', | |
body: JSON.stringify(formData), | |
}).then(r => r.json()) | |
.then(d => { | |
resultBox.innerHTML = genAssessTable(d); | |
resultBox.open = true; | |
}); | |
}); | |
} | |
function genAssessTable(data) { | |
let stats = data.stats; | |
let props = Object.keys(stats); | |
let title = document.querySelector('h1.herotext')?.textContent || ''; | |
let ths = props.map(prop => genTd(prop, 'th')).join(''); | |
let tbody = stats[props[0]].values.map((_i, index) => { | |
let tds = props.map(prop => { | |
return genTd(stats[prop].values[index]); | |
}).join(''); | |
return `<tr> | |
${genTd(index + 1)} | |
${tds} | |
</tr>` | |
}).join(''); | |
props.map(prop => { | |
stats[prop].values | |
}); | |
return ` | |
<table style="margin:auto;text-transform:capitalize;"> | |
<caption>${title} ${data.quality * 100}%</caption> | |
<style>details th {border-bottom:1px dotted #fff6;}</style> | |
<tr> | |
<th>Lv</th> | |
${ths} | |
</tr> | |
${data.quality * 1 ? tbody : ''} | |
</table> | |
`; | |
} | |
function genTd(str, tag = 'td') { | |
return `<${tag}>${str}</${tag}>`; | |
} | |
function genLabel(prop = '', value = '', attr) { | |
return ` | |
<label class="controller number"> | |
<div class="name" style="text-transform:capitalize;">${prop}</div> | |
<div class="widget"> | |
<input type="number" value="${value}" name="${prop}" ${attr} /> | |
</div> | |
</label>`; | |
} | |
async function getItemInfo() { | |
let info = await getEnInfo(); | |
let name = info.title; | |
if (!name) { | |
return false; | |
} | |
let itemData = await postData('https://orna.guide/api/v1/item', {name}); | |
if (itemData?.length !== 1) { | |
return false; | |
} | |
return { | |
id: itemData[0]?.id, | |
name, | |
}; | |
} | |
function postData(url, data) { | |
return fetch(url, { | |
method: 'POST', | |
body: JSON.stringify(data) | |
}).then(res => res.json()); | |
} | |
function getEnURL() { | |
let a = document.createElement('a'); | |
a.href = location.href; | |
a.search = 'lang=en'; | |
a.href = `https://api.allorigins.win/raw?url=${encodeURIComponent(a.href)}`; | |
// a.href = 'https://api.codetabs.com/v1/proxy?quest=' + a.href; | |
// a.href = ' https://fast-dawn-89938.herokuapp.com/' + a.href; | |
return a.href; | |
} | |
async function getEnInfo() { | |
let html = await fetch(getEnURL()).then(res => res.text()); | |
let doc = document.implementation.createHTMLDocument(); | |
doc.body.innerHTML = html; | |
let h1 = doc.querySelector('h1.herotext'); | |
return { | |
title: h1.textContent.trim(), | |
// stats: getStatValues(doc.querySelector('.codex-stats')), | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Monster debuff checker for Orna.RPG | |
// @namespace http://tampermonkey.net/ | |
// @version 1.2.0 | |
// @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/lil-gui@0.17 | |
// @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(); | |
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 = ''; | |
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; | |
} | |
// spawns | |
let 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+)/); | |
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]); | |
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) { | |
if (lang === 'en') { | |
let a = document.createElement('a'); | |
a.href = url; | |
a.search = `lang=en`; | |
// 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'], | |
words: { | |
'immune_to': ['免疫',], | |
'immune_to_status': ['狀態免疫',], | |
'resistant_to': ['抗性',], | |
'weak_to': ['弱點',], | |
'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 i18n | |
function _(key) { | |
return i18n.words[key]?.[langIndex] || key; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment