Last active
June 24, 2019 16:59
-
-
Save embarq/833762451e756e44c6f61f5671a3ebf5 to your computer and use it in GitHub Desktop.
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
/** | |
* Items parser | |
* --- | |
* https://liquipedia.net/dota2/Portal:Items | |
*/ | |
var data = []; | |
var itemsNodes = document.querySelector('.mw-parser-output').querySelectorAll('div.responsive'); | |
for (let item of itemsNodes.values()) { | |
const itemElem = item.children[0]; | |
const pictureElem = itemElem.children[0] | |
if (pictureElem == null) { | |
continue; | |
} | |
data.push({ | |
url: itemElem.href, | |
picture: pictureElem.src | |
}); | |
} |
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
/** | |
* An Item parser | |
* --- | |
* https://liquipedia.net/dota2/Portal:Items | |
* https://liquipedia.net/dota2/Blade_Mail | |
* https://liquipedia.net/dota2/Scythe_of_Vyse | |
* https://liquipedia.net/dota2/Quelling_Blade | |
* https://liquipedia.net/dota2/Ring_of_Protection | |
*/ | |
function getItemModel() { | |
const safeGet = (o, p) => p.reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, o) | |
function toSnakeCase(str = '') { | |
return (str || this).replace(/\s/g, '_'); | |
} | |
function toEntityUid(str) { | |
return toSnakeCase.call(str.trim().toLowerCase()) | |
} | |
function toCamelCase(str) { | |
return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function(match, index) { | |
if (+match === 0) return ""; // or if (/\s+/.test(match)) for white spaces | |
return index == 0 ? match.toLowerCase() : match.toUpperCase(); | |
}); | |
} | |
/** @returns {number} */ | |
function getSectionIndex(items, section) { | |
return items.findIndex(elem => { | |
const title = elem.innerText.trim().toLowerCase(); | |
return section === title; | |
}); | |
} | |
/** @returns {Array<HTMLElement>} */ | |
function getSectionElements(items, section) { | |
const startIndex = getSectionIndex(items, section) + 1; | |
let endIndex = startIndex; | |
while( | |
items[endIndex].querySelector('.infobox-header') == null && | |
endIndex <= items.length | |
) { | |
endIndex++; | |
} | |
return items.slice(startIndex, endIndex); | |
} | |
/** @returns {Array<HTMLElement>} */ | |
function getSections(selector) { | |
const elem = document.querySelector(selector); | |
const items = Array.from(elem.children); | |
items.at = function(index) { | |
return this[index]; | |
} | |
return items; | |
} | |
/** @returns {string} */ | |
function getTitle(elem) { | |
return elem.querySelector('.infobox-header').childNodes[1].nodeValue.trim(); | |
} | |
/** @returns {number} */ | |
function getCost(elem) { | |
const value = elem.querySelector('.infobox-gold').children[0].children[1].textContent; | |
return Number(value); | |
} | |
/** @returns {object<description,url,pictureUrl>} */ | |
function getMetadata(elem) { | |
const dataElem = elem.querySelector('.infobox-center'); | |
const [ pictureLinkElem, br, descriptionElem ] = Array.from(dataElem.children); | |
const url = pictureLinkElem.href; | |
const pictureUrl = pictureLinkElem.children[0].currentSrc; | |
return { | |
description: descriptionElem.textContent, | |
url, | |
pictureUrl | |
} | |
} | |
function getStats(statsElem) { | |
return [ ...statsElem.children ].reduce((_stats, elem) => { | |
const [ statLinkElem, , valueElem ] = [ ...elem.children ]; | |
const title = statLinkElem.title.trim().toLowerCase(); | |
const value = valueElem.textContent.trim().match(/\d+/)[0]; | |
return { | |
..._stats, | |
[title]: Number(value) | |
}; | |
}, {}); | |
} | |
function getAttributes(attrElements) { | |
return attrElements.reduce((attrs, attrElem) => { | |
if (attrElem.querySelector('.infobox-cell-3') != null) { | |
return { | |
...attrs, | |
stats: getStats(attrElem) | |
}; | |
} | |
const [ attribute, value ] = [ ...attrElem.children ].map(entry => entry.innerText); | |
const attrName = attribute.trim().toLowerCase(); | |
return { | |
...attrs, | |
[attrName]: parseFloat(value) | |
} | |
}, { }); | |
} | |
function getAbilities(elements, itemId) { | |
function getAbilityId(str) { | |
return str.trim().replace(/\s/g, '_'); | |
} | |
function parseAbility(elem, itemId) { | |
const abilityType = elem.children[0].childNodes[0].textContent; | |
const abilityTitle = elem.children[0].childNodes[elem.children[0].childNodes.length - 1].textContent; | |
const isActive = abilityType.toLowerCase() === 'active'; | |
const abilityMetaElem = document.getElementById(getAbilityId(abilityTitle)).children[0]; | |
const paramsElem = abilityMetaElem.children.item(1); | |
const statsElem = abilityMetaElem.children.item(2); | |
const description = paramsElem.children.item(2).textContent; | |
// TODO: add interactions into params | |
const params = [ ...paramsElem.children.item(1).children ].reduce((_params, elem) => { | |
const paramId = toCamelCase(elem.childNodes.item(0).textContent); | |
const paramValue = elem.childNodes.item(2).textContent.toLowerCase(); | |
return { | |
..._params, | |
[paramId]: paramValue | |
}; | |
}, {}); | |
// TODO: add support active abilities | |
const _statsEntryElems = [ ...statsElem.children ]; | |
const statsEntryElems = isActive | |
? _statsEntryElems.slice(0, _statsEntryElems.length - 2) | |
: _statsEntryElems.slice(); | |
const stats = statsEntryElems.map(elem => { | |
const trElem = elem.querySelector('tr'); | |
const elemStats = [ ...trElem.children ].map(child => child.textContent); | |
const [ title, value ] = elemStats; | |
return { | |
id: toEntityUid(title), | |
value: parseFloat(value), | |
valueType: /\d+\%/.test(value) ? 'percentage' : 'scalar', | |
title | |
} | |
}); | |
if (isActive) { | |
const manaCost = safeGet(statsElem.querySelector('[title="Mana Cost"]'), [ | |
'parentElement', | |
'parentElement', | |
'textContent' | |
]); | |
const cooldown = safeGet(statsElem.querySelector('[title="Cooldown"]'), [ | |
'parentElement', | |
'parentElement', | |
'textContent' | |
]) | |
params['manaCost'] = parseFloat((manaCost + '').trim()); | |
params['cooldown'] = parseFloat((cooldown + '').trim()); | |
} | |
// TODO: add ability picture ref | |
return { | |
isActive, | |
title: abilityTitle, | |
description, | |
id: `${ toEntityUid(itemId) }_${ toEntityUid(abilityTitle) }`, | |
params, | |
stats | |
} | |
} | |
return elements.map((elem) => parseAbility(elem, itemId)); | |
} | |
function getRecipe(recipeElem) { | |
const _recipeElem = recipeElem.children[0]; | |
const componentsElem = _recipeElem.childNodes.item(_recipeElem.childNodes.length - 1); | |
// FIXME: change to return array of objects like { title, id, pictureUrl } | |
return [ ...componentsElem.children ].map(elem => { | |
return { | |
id: toSnakeCase(elem.title.toLowerCase()), | |
title: elem.title.trim(), | |
pictureUrl: elem.children[0].currentSrc | |
} | |
}); | |
} | |
const items = getSections('.fo-nttax-infobox'); | |
const sectionMap = { | |
attributes: getSectionElements(items, 'attributes'), | |
abilities: getSectionElements(items, 'ability'), | |
recipe: items.at(getSectionIndex(items, 'recipe') + 1) | |
}; | |
const model = { | |
title: getTitle(items.at(0)), | |
cost: getCost(items.at(1)), | |
...getMetadata(items.at(2)), | |
attributes: getAttributes(sectionMap.attributes), | |
abilities: [], | |
recipe: getRecipe(sectionMap.recipe) | |
}; | |
model.abilities = getAbilities(sectionMap.abilities, model.title) | |
return model; | |
} | |
console.log(getItemModel()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment