Compare commits
No commits in common. "72e483d75d7a64fe5fceb2e2a6b3b64eebb60a53" and "cf0652585614e3abc366dcd17661f717ad3a9803" have entirely different histories.
72e483d75d
...
cf06525856
16 changed files with 248 additions and 475 deletions
|
@ -4,5 +4,4 @@ Docker*
|
||||||
README*
|
README*
|
||||||
node_modules/
|
node_modules/
|
||||||
.*
|
.*
|
||||||
pnpm-lock*
|
pnpm-lock*
|
||||||
dist
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,4 +1,3 @@
|
||||||
node_modules
|
node_modules
|
||||||
.env
|
.env
|
||||||
pnpm-lock*
|
pnpm-lock*
|
||||||
dist
|
|
|
@ -1,9 +1,8 @@
|
||||||
{
|
{
|
||||||
"trailingComma": "none",
|
"trailingComma": "es5",
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"parser": "babel-ts",
|
"parser": "babel-flow",
|
||||||
"bracketSameLine": true,
|
"bracketSameLine": true
|
||||||
"printWidth": 120
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,18 @@
|
||||||
import axios from 'axios';
|
const { default: axios } = require('axios');
|
||||||
import { getParsed, getProfit, splitNumber, getRawCraft } from './src/helperFunctions';
|
const {
|
||||||
import { parentPort, workerData, isMainThread } from 'worker_threads';
|
getParsed,
|
||||||
import { Item } from './src/Item';
|
getProfit,
|
||||||
|
splitNumber,
|
||||||
import { loadConfig } from './src/configLoader';
|
getRawCraft,
|
||||||
const config = loadConfig();
|
} = require('./src/helperFunctions');
|
||||||
|
const { parentPort, workerData } = require('worker_threads');
|
||||||
if (isMainThread || !parentPort) {
|
const config = require('./config.json');
|
||||||
throw new Error('This module can only be run in a Worker thread.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const threadsToUse: number = config.data['threadsToUse/speed'];
|
|
||||||
|
|
||||||
let minProfit = config.data.minSnipeProfit;
|
let minProfit = config.data.minSnipeProfit;
|
||||||
let minPercentProfit = config.data.minSnipePP;
|
let minPercentProfit = config.data.minSnipePP;
|
||||||
let ignoredAuctions: any[] = [];
|
let ignoredAuctions = [];
|
||||||
const promises: Promise<any>[] = [];
|
const { Item } = require('./src/Item');
|
||||||
|
const threadsToUse = require('./config.json').data['threadsToUse/speed'];
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
console.log(`[Worker ${workerData.workerNumber}] Worker started`);
|
console.log(`[Worker ${workerData.workerNumber}] Worker started`);
|
||||||
|
|
||||||
|
@ -97,7 +94,7 @@ async function parsePage(i) {
|
||||||
) {
|
) {
|
||||||
if (lbin + rcCost - startingBid > minProfit) {
|
if (lbin + rcCost - startingBid > minProfit) {
|
||||||
const profitData = getProfit(startingBid, rcCost, lbin);
|
const profitData = getProfit(startingBid, rcCost, lbin);
|
||||||
let auctionType: string | null = null;
|
let auctionType = null;
|
||||||
if (
|
if (
|
||||||
rcCost > lbin - startingBid &&
|
rcCost > lbin - startingBid &&
|
||||||
profitData.snipeProfit < minProfit
|
profitData.snipeProfit < minProfit
|
||||||
|
@ -121,7 +118,7 @@ async function parsePage(i) {
|
||||||
) {
|
) {
|
||||||
prettyItem.auctionData.profit = profitData.RCProfit;
|
prettyItem.auctionData.profit = profitData.RCProfit;
|
||||||
prettyItem.auctionData.percentProfit = profitData.RCPP;
|
prettyItem.auctionData.percentProfit = profitData.RCPP;
|
||||||
parentPort!.postMessage(prettyItem);
|
parentPort.postMessage(prettyItem);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
|
@ -130,7 +127,7 @@ async function parsePage(i) {
|
||||||
) {
|
) {
|
||||||
prettyItem.auctionData.profit = profitData.snipeProfit;
|
prettyItem.auctionData.profit = profitData.snipeProfit;
|
||||||
prettyItem.auctionData.percentProfit = profitData.snipePP;
|
prettyItem.auctionData.percentProfit = profitData.snipePP;
|
||||||
parentPort!.postMessage(prettyItem);
|
parentPort.postMessage(prettyItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,7 +156,8 @@ async function doTask(totalPages) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let pageToStop = startingPage + pagePerThread[workerData.workerNumber];
|
let pageToStop =
|
||||||
|
parseInt(startingPage) + parseInt(pagePerThread[workerData.workerNumber]);
|
||||||
|
|
||||||
if (pageToStop !== totalPages) {
|
if (pageToStop !== totalPages) {
|
||||||
pageToStop -= 1;
|
pageToStop -= 1;
|
||||||
|
@ -174,5 +172,5 @@ async function doTask(totalPages) {
|
||||||
}
|
}
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
console.log(`[Worker ${workerData.workerNumber}] Finished task`);
|
console.log(`[Worker ${workerData.workerNumber}] Finished task`);
|
||||||
parentPort!.postMessage('finished');
|
parentPort.postMessage('finished');
|
||||||
}
|
}
|
|
@ -6,6 +6,4 @@ WORKDIR /app
|
||||||
|
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
RUN npm run build
|
CMD ["node", "index.js"]
|
||||||
|
|
||||||
CMD npm start
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"thread_count/speed": 48,
|
"threadsToUse/speed": 48,
|
||||||
|
|
||||||
"minSnipeProfit": 900000,
|
"minSnipeProfit": 900000,
|
||||||
"minAvgProfit": 500000,
|
"minAvgProfit": 500000,
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
---
|
---
|
||||||
services:
|
services:
|
||||||
bot:
|
hypixel-auc-notifier:
|
||||||
container_name: hypixel-auc-notifier
|
container_name: hypixel-auc-notifier
|
||||||
build: .
|
build: .
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
|
||||||
NODE_ENV: "production"
|
|
||||||
volumes:
|
volumes:
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
- /etc/timezone:/etc/timezone:ro
|
- /etc/timezone:/etc/timezone:ro
|
||||||
- ./config.json:/app/dist/src/config.json
|
- ./config.json:/app/config.json
|
10
env.d.ts
vendored
10
env.d.ts
vendored
|
@ -1,10 +0,0 @@
|
||||||
declare global {
|
|
||||||
namespace NodeJS {
|
|
||||||
interface ProcessEnv {
|
|
||||||
WEBHOOK_URL: string;
|
|
||||||
WEBHOOK_NAME: string;
|
|
||||||
WEBHOOK_PROFILE_PICTURE: string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export { };
|
|
|
@ -1,20 +1,17 @@
|
||||||
import axios from 'axios';
|
const { default: axios } = require('axios');
|
||||||
import { WebhookClient, EmbedBuilder, Embed } from 'discord.js';
|
const config = require('./config.json');
|
||||||
import { Worker } from 'worker_threads';
|
const { WebhookClient, EmbedBuilder, Embed } = require('discord.js');
|
||||||
import { asyncInterval, addNotation } from './src/helperFunctions';
|
const { Worker } = require('worker_threads');
|
||||||
import { string } from 'prismarine-nbt';
|
const { asyncInterval, addNotation } = require('./src/helperFunctions');
|
||||||
|
const { string } = require('prismarine-nbt');
|
||||||
|
|
||||||
import { loadConfig } from './src/configLoader';
|
let threadsToUse = config.data['threadsToUse/speed'] ?? 1;
|
||||||
const config = loadConfig();
|
|
||||||
|
|
||||||
|
|
||||||
let thread_count = config.data['thread_count/speed'] ?? 1;
|
|
||||||
let lastUpdated = 0;
|
let lastUpdated = 0;
|
||||||
let doneWorkers = 0;
|
let doneWorkers = 0;
|
||||||
let startingTime;
|
let startingTime;
|
||||||
let maxPrice = 0;
|
let maxPrice = 0;
|
||||||
let itemDatas = {};
|
let itemDatas = {};
|
||||||
const workers: Worker[] = [];
|
const workers = [];
|
||||||
const webhookRegex = /https:\/\/discord.com\/api\/webhooks\/(.+)\/(.+)/;
|
const webhookRegex = /https:\/\/discord.com\/api\/webhooks\/(.+)\/(.+)/;
|
||||||
|
|
||||||
const bazaarPrice = {
|
const bazaarPrice = {
|
||||||
|
@ -32,8 +29,8 @@ async function initialize() {
|
||||||
await getMoulberry();
|
await getMoulberry();
|
||||||
await getLBINs();
|
await getLBINs();
|
||||||
|
|
||||||
for (let j = 0; j < thread_count; j++) {
|
for (let j = 0; j < threadsToUse; j++) {
|
||||||
workers[j] = new Worker('/app/dist/AuctionHandler.worker.js', {
|
workers[j] = new Worker('./AuctionHandler.js', {
|
||||||
workerData: {
|
workerData: {
|
||||||
itemDatas: itemDatas,
|
itemDatas: itemDatas,
|
||||||
bazaarData: bazaarPrice,
|
bazaarData: bazaarPrice,
|
||||||
|
@ -47,7 +44,7 @@ async function initialize() {
|
||||||
let averagePrice = itemDatas[result.itemData.id]?.cleanPrice || 'N/A';
|
let averagePrice = itemDatas[result.itemData.id]?.cleanPrice || 'N/A';
|
||||||
if (
|
if (
|
||||||
result.auctionData.lbin - result.auctionData.price >=
|
result.auctionData.lbin - result.auctionData.price >=
|
||||||
config.data.minSnipeProfit &&
|
config.data.minSnipeProfit &&
|
||||||
averagePrice - result.auctionData.price >= config.data.minAvgProfit
|
averagePrice - result.auctionData.price >= config.data.minAvgProfit
|
||||||
) {
|
) {
|
||||||
let mustBuyMessage = '';
|
let mustBuyMessage = '';
|
||||||
|
@ -80,7 +77,7 @@ async function initialize() {
|
||||||
}
|
}
|
||||||
} else if (result === 'finished') {
|
} else if (result === 'finished') {
|
||||||
doneWorkers++;
|
doneWorkers++;
|
||||||
if (doneWorkers === thread_count) {
|
if (doneWorkers === threadsToUse) {
|
||||||
doneWorkers = 0;
|
doneWorkers = 0;
|
||||||
console.log(
|
console.log(
|
||||||
`Completed in ${(Date.now() - startingTime) / 1000} seconds`
|
`Completed in ${(Date.now() - startingTime) / 1000} seconds`
|
||||||
|
@ -141,7 +138,7 @@ async function initialize() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getLBINs(): Promise<void> {
|
async function getLBINs() {
|
||||||
const lbins = await axios.get('https://moulberry.codes/lowestbin.json');
|
const lbins = await axios.get('https://moulberry.codes/lowestbin.json');
|
||||||
const lbinData = lbins.data;
|
const lbinData = lbins.data;
|
||||||
for (const item of Object.keys(lbinData)) {
|
for (const item of Object.keys(lbinData)) {
|
||||||
|
@ -150,7 +147,7 @@ async function getLBINs(): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMoulberry(): Promise<void> {
|
async function getMoulberry() {
|
||||||
const moulberryAvgs = await axios.get(
|
const moulberryAvgs = await axios.get(
|
||||||
'https://moulberry.codes/auction_averages/3day.json'
|
'https://moulberry.codes/auction_averages/3day.json'
|
||||||
);
|
);
|
||||||
|
@ -170,12 +167,12 @@ async function getMoulberry(): Promise<void> {
|
||||||
cleanPriceData[item] !== undefined
|
cleanPriceData[item] !== undefined
|
||||||
? Math.round(cleanPriceData[item])
|
? Math.round(cleanPriceData[item])
|
||||||
: itemInfo.clean_price !== undefined
|
: itemInfo.clean_price !== undefined
|
||||||
? itemInfo.clean_price
|
? itemInfo.clean_price
|
||||||
: itemInfo.price;
|
: itemInfo.price;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getBzData(): Promise<void> {
|
async function getBzData() {
|
||||||
const bzData = await axios.get('https://api.hypixel.net/skyblock/bazaar');
|
const bzData = await axios.get('https://api.hypixel.net/skyblock/bazaar');
|
||||||
bazaarPrice['RECOMBOBULATOR_3000'] =
|
bazaarPrice['RECOMBOBULATOR_3000'] =
|
||||||
bzData.data.products.RECOMBOBULATOR_3000.quick_status.buyPrice;
|
bzData.data.products.RECOMBOBULATOR_3000.quick_status.buyPrice;
|
39
package.json
39
package.json
|
@ -1,29 +1,22 @@
|
||||||
{
|
{
|
||||||
"name": "hypixel-auction-flipper",
|
"name": "hypixel-auction-flipper",
|
||||||
"version": "1.0.0",
|
"version": "0.6.9",
|
||||||
"description": "Hypixel Skyblock Auction House Flip Notifier",
|
|
||||||
"main": "dist/index.js",
|
|
||||||
"author": "DuckySoLucky + MashClashXD + Ulysia + Sol",
|
|
||||||
"keywords": [],
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc",
|
|
||||||
"start": "node dist/index.js",
|
|
||||||
"dev": "ts-node index.ts"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^22.10.2",
|
|
||||||
"@types/toastify-js": "^1.12.3",
|
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
"copy-paste": "^1.5.3",
|
"copy-paste": "^1.3.0",
|
||||||
"discord.js": "^14.16.3",
|
"discord.js": "^14.16.3",
|
||||||
"express": "^4.21.2",
|
"express": "^4.17.1",
|
||||||
"node": "^23.5.0",
|
"node-notifier": "^10.0.0",
|
||||||
"node-notifier": "^10.0.1",
|
"open": "^8.4.0",
|
||||||
"open": "^8.4.2",
|
"prismarine-nbt": "^2.0.0",
|
||||||
"prismarine-nbt": "^2.7.0",
|
"socket.io": "^4.4.0",
|
||||||
"socket.io": "^4.8.1",
|
"toastify-js": "^1.11.2"
|
||||||
"toastify-js": "^1.12.0",
|
},
|
||||||
"ts-node": "^10.9.2",
|
"description": "Hypixel Skyblock Auction House Flip Notifier",
|
||||||
"typescript": "^5.7.2"
|
"main": "index.js",
|
||||||
}
|
"scripts": {
|
||||||
|
"start": "node ."
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "DuckySoLucky + MashClashXD"
|
||||||
}
|
}
|
||||||
|
|
28
src/Item.js
Normal file
28
src/Item.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
function Item(name, auctionID, price, rarity, enchants, hpbs, fpbs, recomb, artofwar, stars, gemstones, id, category, profit, percentProfit, lbin, sales, lore) {
|
||||||
|
this.itemData = {
|
||||||
|
"name": name,
|
||||||
|
"id": id,
|
||||||
|
"stars": stars,
|
||||||
|
"rarity": rarity,
|
||||||
|
"recomb": recomb,
|
||||||
|
"enchants": enchants,
|
||||||
|
"hpbs": hpbs,
|
||||||
|
"fpbs": fpbs,
|
||||||
|
"gemstones": gemstones,
|
||||||
|
"aow": artofwar,
|
||||||
|
"lore": lore
|
||||||
|
}
|
||||||
|
this.auctionData = {
|
||||||
|
"auctionID": auctionID,
|
||||||
|
"category": category,
|
||||||
|
"sales": sales,
|
||||||
|
"price": price,
|
||||||
|
"profit": profit,
|
||||||
|
"percentProfit": percentProfit,
|
||||||
|
"lbin": lbin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Item
|
||||||
|
}
|
78
src/Item.ts
78
src/Item.ts
|
@ -1,78 +0,0 @@
|
||||||
// item.ts
|
|
||||||
|
|
||||||
export interface ItemData {
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
stars: number;
|
|
||||||
rarity: string;
|
|
||||||
recomb: boolean;
|
|
||||||
enchants: string[];
|
|
||||||
hpbs: number;
|
|
||||||
fpbs: number;
|
|
||||||
gemstones: string[];
|
|
||||||
aow: boolean;
|
|
||||||
lore: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AuctionData {
|
|
||||||
auctionID: string;
|
|
||||||
category: string;
|
|
||||||
sales: number;
|
|
||||||
price: number;
|
|
||||||
profit: number;
|
|
||||||
percentProfit: number;
|
|
||||||
lbin: number;
|
|
||||||
ahType: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Item {
|
|
||||||
public itemData: ItemData;
|
|
||||||
public auctionData: AuctionData;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
name: string,
|
|
||||||
auctionID: string,
|
|
||||||
price: number,
|
|
||||||
rarity: string,
|
|
||||||
enchants: string[],
|
|
||||||
hpbs: number,
|
|
||||||
fpbs: number,
|
|
||||||
recomb: boolean,
|
|
||||||
artofwar: boolean,
|
|
||||||
stars: number,
|
|
||||||
gemstones: string[],
|
|
||||||
id: string,
|
|
||||||
category: string,
|
|
||||||
profit: number,
|
|
||||||
percentProfit: number,
|
|
||||||
lbin: number,
|
|
||||||
sales: number,
|
|
||||||
lore: string,
|
|
||||||
ahType: string | null = null
|
|
||||||
) {
|
|
||||||
this.itemData = {
|
|
||||||
name,
|
|
||||||
id,
|
|
||||||
stars,
|
|
||||||
rarity,
|
|
||||||
recomb,
|
|
||||||
enchants,
|
|
||||||
hpbs,
|
|
||||||
fpbs,
|
|
||||||
gemstones,
|
|
||||||
aow: artofwar,
|
|
||||||
lore,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.auctionData = {
|
|
||||||
auctionID,
|
|
||||||
category,
|
|
||||||
sales,
|
|
||||||
price,
|
|
||||||
profit,
|
|
||||||
percentProfit,
|
|
||||||
lbin,
|
|
||||||
ahType,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
export interface ConfigType {
|
|
||||||
data: {
|
|
||||||
'threadsToUse/speed': number;
|
|
||||||
minSnipeProfit: number;
|
|
||||||
minAvgProfit: number;
|
|
||||||
minCraftProfit: number;
|
|
||||||
maxAvgLbinDiff: number;
|
|
||||||
rawCraftMaxWeightPP: number;
|
|
||||||
minSnipePP: number;
|
|
||||||
minCraftPP: number;
|
|
||||||
ignoreCategories: {
|
|
||||||
weapon: boolean;
|
|
||||||
accessories: boolean;
|
|
||||||
armor: boolean;
|
|
||||||
misc: boolean;
|
|
||||||
blocks: boolean;
|
|
||||||
consumables: boolean;
|
|
||||||
}
|
|
||||||
minSales: number;
|
|
||||||
includeCraftCost: boolean;
|
|
||||||
minPriceForRawcraft: number;
|
|
||||||
};
|
|
||||||
filters: {
|
|
||||||
EnchantThresholdConditionalBypass: Record<string, Record<string, number>>;
|
|
||||||
EnchantThreshold: Record<string, number>;
|
|
||||||
itemIDExclusions: string[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadConfig(): ConfigType {
|
|
||||||
const configPath = path.join(__dirname, 'config.json');
|
|
||||||
try {
|
|
||||||
const fileContents = fs.readFileSync(configPath, 'utf8');
|
|
||||||
return JSON.parse(fileContents) as ConfigType;
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Failed to load config.json at ${configPath}: ${error}`);
|
|
||||||
}
|
|
||||||
}
|
|
159
src/helperFunctions.js
Normal file
159
src/helperFunctions.js
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
const config = require('../config.json')
|
||||||
|
const nbt = require('prismarine-nbt')
|
||||||
|
let currentAsyncIntervals = {}
|
||||||
|
|
||||||
|
function addNotation(type, value) {
|
||||||
|
let returnVal = value;
|
||||||
|
let notList = [];
|
||||||
|
if (type === "shortScale") {
|
||||||
|
notList = [
|
||||||
|
" Thousand",
|
||||||
|
" Million",
|
||||||
|
" Billion",
|
||||||
|
" Trillion",
|
||||||
|
" Quadrillion",
|
||||||
|
" Quintillion"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
returnVal = value / (checkNum / 100);
|
||||||
|
returnVal = Math.floor(returnVal);
|
||||||
|
returnVal = (returnVal / Math.pow(10, o)) * 10;
|
||||||
|
returnVal = +returnVal.toFixed(o - 1) + notValue;
|
||||||
|
}
|
||||||
|
checkNum *= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
returnVal = numberWithCommas(value.toFixed(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getParsed(encoded) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let buf = Buffer.from(encoded, 'base64');
|
||||||
|
nbt.parse(buf, (err, dat) => {
|
||||||
|
if (err) throw err;
|
||||||
|
resolve(nbt.simplify(dat))
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProfit(price, rcCost, lbin) {
|
||||||
|
const profitItem = {}
|
||||||
|
if (price >= 1000000) {
|
||||||
|
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 = 1, parts = 1) {
|
||||||
|
let n = Math.floor(num / parts);
|
||||||
|
const arr = [];
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRawCraft(item, bazaarPrice, lbins) {
|
||||||
|
let price = 0
|
||||||
|
const ignoreMatch = Object.keys(
|
||||||
|
config.filters.EnchantThresholdConditionalBypass
|
||||||
|
).find((key) => {
|
||||||
|
if (item.itemData.id.includes(key)) return true;
|
||||||
|
});
|
||||||
|
if (item.auctionData.lbin < config.data.minPriceForRawcraft) return 0
|
||||||
|
let isInIgnore = ignoreMatch ? ignoreMatch : false
|
||||||
|
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 config.filters.EnchantThreshold[enchant] === 'number'
|
||||||
|
? degree >= config.filters.EnchantThreshold[enchant]
|
||||||
|
: false;
|
||||||
|
if (isInIgnore) {
|
||||||
|
const enchantMinValue =
|
||||||
|
config.filters.EnchantThresholdConditionalBypass[ignoreMatch][
|
||||||
|
enchant
|
||||||
|
];
|
||||||
|
if (enchantMinValue >= degree) 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'] * 0.3
|
||||||
|
}
|
||||||
|
if (item.itemData.recomb && (item.auctionData.category === 'weapon' || item.auctionData.category === 'armor' || item.auctionData.category === 'accessories')) {
|
||||||
|
price += bazaarPrice['RECOMBOBULATOR_3000'] * 0.5
|
||||||
|
}
|
||||||
|
price += (item.itemData.hpbs ? item.itemData.hpbs : 0) * bazaarPrice['HOT_POTATO_BOOK'] * 0.05
|
||||||
|
price += (item.itemData.fpbs ? item.itemData.fpbs : 0) * bazaarPrice['FUMING_POTATO_BOOK'] * 0.1
|
||||||
|
|
||||||
|
return price
|
||||||
|
}
|
||||||
|
|
||||||
|
async function asyncInterval(asyncTask, intervalname, timeout) {
|
||||||
|
currentAsyncIntervals[intervalname] = true
|
||||||
|
setTimeout(async function () {
|
||||||
|
if (!currentAsyncIntervals[intervalname]) return
|
||||||
|
asyncTask().then(async function () {
|
||||||
|
await asyncInterval(asyncTask, intervalname, timeout)
|
||||||
|
})
|
||||||
|
}, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopAsyncInterval(intervalname) {
|
||||||
|
currentAsyncIntervals[intervalname] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentIntervals() {
|
||||||
|
return currentAsyncIntervals
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
addNotation,
|
||||||
|
getParsed,
|
||||||
|
getProfit,
|
||||||
|
splitNumber,
|
||||||
|
getRawCraft,
|
||||||
|
asyncInterval,
|
||||||
|
stopAsyncInterval,
|
||||||
|
currentIntervals
|
||||||
|
}
|
|
@ -1,247 +0,0 @@
|
||||||
// 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;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prismarine-nbt parse’s callback has this shape:
|
|
||||||
interface NbtData {
|
|
||||||
// If you know the exact shape of the parsed data, define it here
|
|
||||||
[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,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Auction house fee logic
|
|
||||||
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 haven’t balanced it out, it’ll just return this array
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjust the below types as needed to match your item structure:
|
|
||||||
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;
|
|
||||||
|
|
||||||
// If you have a more specific type for config, use that
|
|
||||||
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 is defined and the degree is within that threshold, skip.
|
|
||||||
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,
|
|
||||||
};
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "NodeNext",
|
|
||||||
"moduleResolution": "nodenext",
|
|
||||||
"target": "ES2024",
|
|
||||||
"jsx": "preserve",
|
|
||||||
"strictNullChecks": true,
|
|
||||||
"strictFunctionTypes": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"outDir": "./dist",
|
|
||||||
},
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
"**/node_modules/*"
|
|
||||||
]
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue