Skip to content

Instantly share code, notes, and snippets.

@embarq
Last active June 24, 2019 16:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save embarq/833762451e756e44c6f61f5671a3ebf5 to your computer and use it in GitHub Desktop.
Save embarq/833762451e756e44c6f61f5671a3ebf5 to your computer and use it in GitHub Desktop.
/**
* 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
});
}
/**
* 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