Skip to content

Commit

Permalink
Updated items endpoint to use official list
Browse files Browse the repository at this point in the history
  • Loading branch information
builder-247 committed Jan 22, 2023
1 parent b34d32b commit c89fd08
Show file tree
Hide file tree
Showing 6 changed files with 526 additions and 153 deletions.
2 changes: 0 additions & 2 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@ const defaults = {
FRONTEND_PORT: 5000,
ITEMS_PORT: 5100,
MOJANG_STATUS_INTERVAL: 15000, // Interval between refreshing Mojang status in milliseconds
ITEMS_HOST: 'http://localhost:5100', // host of the items server
REDIS_URL: 'redis://127.0.0.1:6379/0', // connection string for Redis
SENTRY_URL: '',
ITEMS_PERCENT: 1, // probability of submitting skyblock inventories to item service
API_FREE_LIMIT: 50000, // number of api requests per month before 429 is returned.
NO_API_KEY_PER_MIN_LIMIT: 60, // Rate limit per minute if not using an API key
DEFAULT_DELAY: 1000, // delay between API requests
Expand Down
57 changes: 1 addition & 56 deletions processors/processSkyBlock.js
Original file line number Diff line number Diff line change
@@ -1,62 +1,7 @@
const got = require('got');
const { Profile } = require('skyblock-parser');
const config = require('../config');
const { logger, removeFormatting } = require('../util/utility');

// Submit items to the items service
async function checkItems(members = {}) {
// We can control sample size to prevent overload
if (Math.random() >= Number(config.ITEMS_PERCENT)) {
return;
}
let inventories = [];
// Merge all inventories
Object.values(members).forEach((member) => {
inventories = inventories.concat(member.inventory || []);
});
// Remove empty inventory slots
inventories = inventories.filter((i) => 'name' in i);
// Get unique items
try {
const items = [...new Set(inventories.map((i) => i.attributes.id))]
.flatMap((id) => {
const item = inventories.find((i) => i.attributes.id === id);
// Filter unfit items
if (![null, undefined].includes(item.attributes.modifier)
|| id.startsWith('MAP:')
|| item.name === '§fnull'
|| item.name.includes('⚚')
|| !/[!-~]/.test(item.name) || !/[!-~]/.test(item.type)
|| !item.name.match(/[a-z]/i)
|| item.attributes.wood_singularity_count
|| item.attributes.rarity_upgrades) return [];
return [{
id,
name: removeFormatting(item.name).replace(/✦|✪/g, '').trim(),
tier: item.rarity,
category: item.type || 'misc',
damage: item.damage || null,
item_id: item.item_id,
texture: item.attributes.texture || null,
}];
});
try {
await got.post(config.ITEMS_HOST, {
json: items,
responseType: 'json',
});
} catch (error) {
logger.warn(`Failed to insert item updates, is the items service running? ${error}`);
}
} catch {
// we don't mind
}
}

async function processSkyBlock(profile) {
const data = await new Profile(profile);
checkItems(data.members);
return data;
return new Profile(profile);
}

module.exports = processSkyBlock;
3 changes: 1 addition & 2 deletions routes/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ const {
logger, generateJob, getData, typeToCleanName, getPlayerFields,
} = require('../util/utility');
const {
playerNameParam, gameNameParam, typeParam, columnParam, filterParam, sortByParam,
limitParam, significantParam, populatePlayersParam, templateParam, bazaarItemIdParam,
playerNameParam, gameNameParam, limitParam, populatePlayersParam, bazaarItemIdParam,
auctionUUIDParam, pageParam, sortOrderParam, profileIdParam, guildNameParam, guildIDParam,
calendarEventsParam, calendarFromParam, calendarToParam, calendarYearsParam, calendarStopAtYearEndParam,
} = require('./parameters');
Expand Down
147 changes: 54 additions & 93 deletions svc/items.js
Original file line number Diff line number Diff line change
@@ -1,111 +1,72 @@
/*
* Worker to generate SkyBlock item schema from inventories processed by web
* Worker to generate SkyBlock item schema
*/
const express = require('express');
const bodyParser = require('body-parser');
const compression = require('compression');
const config = require('../config');
const bukkitToId = require('../util/bukkitToId.json');
const redis = require('../store/redis');
const {
logger, invokeInterval,
logger, invokeInterval, generateJob, getData,
} = require('../util/utility');

const app = express();
const port = config.PORT || config.ITEMS_PORT;

let discoveredItems;
let bazaarProducts = [];
let itemList = {};
const updateQueue = new Set();

(async function init() {
// TODO - expose this in sb-parser
function getSkinHash(base64) {
let texture = null;
try {
itemList = JSON.parse(await redis.get('skyblock_items')) || {};
const items = Object.keys(itemList);
logger.info(`Caching existing ${items.length} item IDs`);
discoveredItems = new Set(items);
} catch (error) {
logger.error(`Failed retrieving skyblock_items from redis: ${error}`);
texture = JSON.parse(Buffer.from(base64, 'base64').toString()).textures.SKIN.url.split('/').pop();
} catch (e) {
// do nothing
}
}());
return texture;
}

/*
* Retrieve items resource from Hypixel API and do some processing
*/
async function updateItemList() {
const itemsObject = {};
const { url } = generateJob('skyblock_items');
const { items } = await getData(redis, url);
items.forEach((item) => {
const {
id, name, tier, category, skin, durability, ...rest
} = item;
const obj = {
id,
name,
item_id: bukkitToId[item?.material] || 0,
...rest,
};
if (tier) {
obj.tier = tier.toLowerCase();
}
if (category) {
obj.category = category.toLowerCase();
}
if (durability) {
obj.damage = durability;
}
if (skin) {
obj.texture = getSkinHash(skin);
}
itemsObject[id] = obj;
});
// Get bazaar status
try {
logger.info('Updating item list to redis...');
await redis.set('skyblock_items', JSON.stringify(itemList));
const data = await redis.get('skyblock_bazaar');
const bazaarProducts = Object.keys(JSON.parse(data));
bazaarProducts.forEach((id) => {
if (Object.hasOwn(itemsObject, id)) {
itemsObject[id].bazaar = true;
}
});
} catch (error) {
logger.error(`Failed updating skyblock_items: ${error}`);
logger.error(`Failed getting bazaar data: ${error}`);
}
}

async function updateBazaar() {
try {
const data = await redis.get('skyblock_bazaar');
bazaarProducts = Object.keys(JSON.parse(data));
try {
logger.info('[Bazaar] Updated item IDs');
let changes = 0;
discoveredItems.forEach((id) => {
if (bazaarProducts.includes(id)) {
itemList[id].bazaar = true;
changes += 1;
}
});
if (changes > 0) {
logger.info(`Discovered ${changes} new bazaar items!`);
updateItemList();
}
} catch (error) {
logger.error(error.message);
}
logger.info('Updating item list to redis...');
await redis.set('skyblock_items', JSON.stringify(itemsObject));
} catch (error) {
logger.error(`Failed updating bazaar products: ${error}`);
logger.error(`Failed updating skyblock_items: ${error}`);
}
}

invokeInterval(updateBazaar, 60 * 60 * 1000);

app.use(compression());
app.use(bodyParser.json());
app.post('/', (request, response, _callback) => {
const items = request.body;
let updates = false;
items.forEach((item) => {
const { id } = item;
if (!discoveredItems.has(id) || updateQueue.has(id)) {
updates = true;
logger.info(`Found new item ID ${id}`);
if (item.texture === null) {
delete item.texture;
}
if (item.damage === null || item.texture) {
delete item.damage;
}
delete item.id;
itemList[id] = item;
discoveredItems.add(id);
if (updateQueue.has(id)) {
updateQueue.delete(id);
}
}
});
if (updates) {
updateItemList();
}
response.json({ status: 'ok' });
});
app.delete('/:id', (request, response, _callback) => {
const { id } = request.params;
if (id in itemList) {
logger.info(`Adding item ${id} to update queue`);
updateQueue.add(id);
}
response.json({ status: 'ok' });
});
app.use((error, _, response) => response.status(500).json({
error,
}));
const server = app.listen(port, () => {
const host = server.address().address;
logger.info(`[ITEMS] listening at http://${host}:${port}`);
});
invokeInterval(updateItemList, 15 * 60 * 1000);
Loading

0 comments on commit c89fd08

Please sign in to comment.