hypixel-auction-notifier/src/helperFunctions.ts
2024-12-27 22:16:26 +01:00

241 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// utils.ts
import nbt from 'prismarine-nbt';
import { Item } from './Item';
import { loadConfig } from './configLoader';
const config = loadConfig();
// If you have a defined structure for your config file, feel free to refine this type.
interface ConfigType {
data: {
minPriceForRawcraft: number;
[key: string]: any;
};
filters: {
EnchantThreshold: Record<string, number>;
EnchantThresholdConditionalBypass: Record<string, Record<string, number>>;
[key: string]: any;
};
}
interface NbtData {
[key: string]: any;
}
// Keep track of current intervals by name
let currentAsyncIntervals: Record<string, boolean> = {};
type ProfitItem = {
RCProfit: number;
RCPP: number;
snipeProfit: number;
snipePP: number;
};
function numberWithCommas(x: number | string): string {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
function addNotation(type: string, value: number): string {
let returnVal: string = value.toString();
let notList: string[] = [];
if (type === 'shortScale') {
notList = [' Thousand', ' Million', ' Billion', ' Trillion', ' Quadrillion', ' Quintillion'];
} else if (type === 'oneLetters') {
notList = ['K', 'M', 'B', 'T'];
}
let checkNum = 1000;
if (type !== 'none' && type !== 'commas') {
let notValue = notList[notList.length - 1] ?? '';
for (let u = notList.length; u >= 1; u--) {
notValue = notList.shift() ?? '';
for (let o = 3; o >= 1; o--) {
if (value >= checkNum) {
// Use a fixed decimal approach
returnVal = (
+(Math.floor(value / (checkNum / 100)) / Math.pow(10, o)) * 10
).toFixed(o - 1) + notValue;
}
checkNum *= 10;
}
}
} else {
returnVal = numberWithCommas(value.toFixed(0));
}
return returnVal;
}
async function getParsed(encoded: string): Promise<any> {
return new Promise((resolve, reject) => {
const buf = Buffer.from(encoded, 'base64');
// The callback signature must match the prismarine-nbt types:
nbt.parse(buf, (err: Error | null, data) => {
if (err) return reject(err);
resolve(nbt.simplify(data));
});
});
}
function getProfit(price: number, rcCost: number, lbin: number): ProfitItem {
const profitItem: ProfitItem = {
RCProfit: 0,
RCPP: 0,
snipeProfit: 0,
snipePP: 0,
};
if (price >= 1_000_000) {
profitItem.RCProfit = lbin + rcCost - price - (lbin + rcCost) * 0.02;
profitItem.RCPP = parseFloat(((profitItem.RCProfit * 100) / lbin).toFixed(1));
profitItem.snipeProfit = lbin - price - lbin * 0.02;
profitItem.snipePP = parseFloat(((profitItem.snipeProfit * 100) / lbin).toFixed(1));
} else {
profitItem.RCProfit = lbin + rcCost - price - (lbin + rcCost) * 0.01;
profitItem.RCPP = parseFloat(((profitItem.RCProfit * 100) / lbin).toFixed(1));
profitItem.snipeProfit = lbin - price - lbin * 0.01;
profitItem.snipePP = parseFloat(((profitItem.snipeProfit * 100) / lbin).toFixed(1));
}
return profitItem;
}
function splitNumber(num: number = 1, parts: number = 1): number[] {
const n: number = Math.floor(num / parts);
const arr: number[] = [];
for (let i = 0; i < parts; i++) {
arr.push(n);
}
if (arr.reduce((a, b) => a + b, 0) === num) {
return arr;
}
for (let i = 0; i < parts; i++) {
arr[i]++;
if (arr.reduce((a, b) => a + b, 0) === num) {
return arr;
}
}
// If we still havent balanced it out, itll just return this array
return arr;
}
interface ItemType {
itemData: {
id: string;
enchants?: Record<string, number>;
aow?: boolean;
recomb?: boolean;
hpbs?: number;
fpbs?: number;
};
auctionData: {
lbin: number;
category?: string;
};
[key: string]: any;
}
interface LbinsType {
[key: string]: {
lbin: number;
};
}
interface BazaarPriceType {
[key: string]: number;
}
function getRawCraft(
item: Item,
bazaarPrice: BazaarPriceType,
lbins: LbinsType
): number {
let price = 0;
const typedConfig = config as ConfigType;
const ignoreMatch: string =
Object.keys(typedConfig.filters.EnchantThresholdConditionalBypass ?? {}).find((key) => {
return item.itemData.id.includes(key);
}) ?? '';
if (item.auctionData.lbin < typedConfig.data.minPriceForRawcraft) return 0;
const isInIgnore = !!ignoreMatch;
if (item.itemData.enchants && !item.itemData.id.includes(';')) {
for (const enchant of Object.keys(item.itemData.enchants)) {
const degree = item.itemData.enchants[enchant];
const badEnchant =
typeof typedConfig.filters.EnchantThreshold[enchant] === 'number'
? degree >= typedConfig.filters.EnchantThreshold[enchant]
: false;
if (isInIgnore) {
const enchantMinValue =
typedConfig.filters.EnchantThresholdConditionalBypass[ignoreMatch]?.[enchant];
if (enchantMinValue && degree <= enchantMinValue) continue;
}
if (badEnchant) {
price += lbins[`${enchant.toUpperCase()};${degree.toString()}`]
? lbins[`${enchant.toUpperCase()};${degree.toString()}`].lbin * 0.5
: 0;
}
}
}
if (item.itemData.aow) {
price += lbins['THE_ART_OF_WAR'].lbin * 0.3;
}
if (
item.itemData.recomb &&
['weapon', 'armor', 'accessories'].includes(item.auctionData.category ?? '')
) {
price += bazaarPrice['RECOMBOBULATOR_3000'] * 0.5;
}
price += (item.itemData.hpbs ?? 0) * bazaarPrice['HOT_POTATO_BOOK'] * 0.05;
price += (item.itemData.fpbs ?? 0) * bazaarPrice['FUMING_POTATO_BOOK'] * 0.1;
return price;
}
// Schedules a recurring async task.
async function asyncInterval(
asyncTask: () => Promise<void>,
intervalName: string,
timeout: number
): Promise<void> {
currentAsyncIntervals[intervalName] = true;
setTimeout(async function () {
if (!currentAsyncIntervals[intervalName]) return;
await asyncTask();
await asyncInterval(asyncTask, intervalName, timeout);
}, timeout);
}
function stopAsyncInterval(intervalName: string): void {
currentAsyncIntervals[intervalName] = false;
}
function currentIntervals(): Record<string, boolean> {
return currentAsyncIntervals;
}
// Exports
export {
addNotation,
getParsed,
getProfit,
splitNumber,
getRawCraft,
asyncInterval,
stopAsyncInterval,
currentIntervals,
};