added docker, environment variables, renamed vars
This commit is contained in:
parent
46b0fe21cb
commit
02337bdb2d
12 changed files with 1567 additions and 209 deletions
7
.dockerignore
Normal file
7
.dockerignore
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
config*
|
||||||
|
docker*
|
||||||
|
Docker*
|
||||||
|
README*
|
||||||
|
node_modules/
|
||||||
|
.*
|
||||||
|
pnpm-lock*
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules
|
||||||
|
.env
|
8
.prettierrc
Normal file
8
.prettierrc
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"parser": "babel-flow",
|
||||||
|
"bracketSameLine": true
|
||||||
|
}
|
|
@ -1,21 +1,28 @@
|
||||||
const { default: axios } = require("axios");
|
const { default: axios } = require('axios');
|
||||||
const { getParsed, getProfit, splitNumber, getRawCraft } = require("./src/helperFunctions");
|
const {
|
||||||
const { parentPort, workerData } = require("worker_threads");
|
getParsed,
|
||||||
const config = require("./config.json");
|
getProfit,
|
||||||
|
splitNumber,
|
||||||
|
getRawCraft,
|
||||||
|
} = require('./src/helperFunctions');
|
||||||
|
const { parentPort, workerData } = require('worker_threads');
|
||||||
|
const config = require('./config.json');
|
||||||
let minProfit = config.data.minSnipeProfit;
|
let minProfit = config.data.minSnipeProfit;
|
||||||
let minPercentProfit = config.data.minSnipePP;
|
let minPercentProfit = config.data.minSnipePP;
|
||||||
let ignoredAuctions = [];
|
let ignoredAuctions = [];
|
||||||
const { Item } = require("./src/Item");
|
const { Item } = require('./src/Item');
|
||||||
const threadsToUse = require("./config.json").data["threadsToUse/speed"];
|
const threadsToUse = require('./config.json').data['threadsToUse/speed'];
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
console.log(`[Worker ${workerData.workerNumber}] Worker started`);
|
console.log(`[Worker ${workerData.workerNumber}] Worker started`);
|
||||||
|
|
||||||
parentPort.on("message", async (message) => {
|
parentPort.on('message', async (message) => {
|
||||||
console.log(`[Worker ${workerData.workerNumber}] Received message: ${message.type}`);
|
console.log(
|
||||||
if (message.type === "pageCount") {
|
`[Worker ${workerData.workerNumber}] Received message: ${message.type}`
|
||||||
|
);
|
||||||
|
if (message.type === 'pageCount') {
|
||||||
await doTask(message.data);
|
await doTask(message.data);
|
||||||
} else if (message.type === "moulberry") {
|
} else if (message.type === 'moulberry') {
|
||||||
workerData.itemDatas = message.data;
|
workerData.itemDatas = message.data;
|
||||||
console.log(`[Worker ${workerData.workerNumber}] Updated item data`);
|
console.log(`[Worker ${workerData.workerNumber}] Updated item data`);
|
||||||
}
|
}
|
||||||
|
@ -24,53 +31,100 @@ parentPort.on("message", async (message) => {
|
||||||
async function parsePage(i) {
|
async function parsePage(i) {
|
||||||
console.log(`[Worker ${workerData.workerNumber}] Parsing page ${i}`);
|
console.log(`[Worker ${workerData.workerNumber}] Parsing page ${i}`);
|
||||||
try {
|
try {
|
||||||
const auctionPage = await axios.get(`https://api.hypixel.net/skyblock/auctions?page=${i}`);
|
const auctionPage = await axios.get(
|
||||||
|
`https://api.hypixel.net/skyblock/auctions?page=${i}`
|
||||||
|
);
|
||||||
for (const auction of auctionPage.data.auctions) {
|
for (const auction of auctionPage.data.auctions) {
|
||||||
if (!auction.bin) continue;
|
if (!auction.bin) continue;
|
||||||
const uuid = auction.uuid;
|
const uuid = auction.uuid;
|
||||||
if (ignoredAuctions.includes(uuid) || config.data.ignoreCategories[auction.category]) continue;
|
if (
|
||||||
|
ignoredAuctions.includes(uuid) ||
|
||||||
|
config.data.ignoreCategories[auction.category]
|
||||||
|
)
|
||||||
|
continue;
|
||||||
const item = await getParsed(auction.item_bytes);
|
const item = await getParsed(auction.item_bytes);
|
||||||
const extraAtt = item["i"][0].tag.ExtraAttributes;
|
const extraAtt = item['i'][0].tag.ExtraAttributes;
|
||||||
const itemID = extraAtt.id;
|
const itemID = extraAtt.id;
|
||||||
let startingBid = auction.starting_bid;
|
let startingBid = auction.starting_bid;
|
||||||
const itemData = workerData.itemDatas[itemID];
|
const itemData = workerData.itemDatas[itemID];
|
||||||
if (!itemData) continue;
|
if (!itemData) continue;
|
||||||
const lbin = itemData.lbin;
|
const lbin = itemData.lbin;
|
||||||
const sales = itemData.sales;
|
const sales = itemData.sales;
|
||||||
const prettyItem = new Item(item.i[0].tag.display.Name, uuid, startingBid, auction.tier, extraAtt.enchantments,
|
const prettyItem = new Item(
|
||||||
extraAtt.hot_potato_count > 10 ? 10 : extraAtt.hot_potato_count, extraAtt.hot_potato_count > 10 ?
|
item.i[0].tag.display.Name,
|
||||||
extraAtt.hot_potato_count - 10 : 0, extraAtt.rarity_upgrades === 1,
|
uuid,
|
||||||
extraAtt.art_of_war_count === 1, extraAtt.dungeon_item_level,
|
startingBid,
|
||||||
extraAtt.gems, itemID, auction.category, 0, 0, lbin, sales, auction.item_lore);
|
auction.tier,
|
||||||
const unstableOrMarketManipulated = Math.abs((lbin - itemData.cleanPrice) / lbin) > config.data.maxAvgLbinDiff;
|
extraAtt.enchantments,
|
||||||
|
extraAtt.hot_potato_count > 10 ? 10 : extraAtt.hot_potato_count,
|
||||||
|
extraAtt.hot_potato_count > 10 ? extraAtt.hot_potato_count - 10 : 0,
|
||||||
|
extraAtt.rarity_upgrades === 1,
|
||||||
|
extraAtt.art_of_war_count === 1,
|
||||||
|
extraAtt.dungeon_item_level,
|
||||||
|
extraAtt.gems,
|
||||||
|
itemID,
|
||||||
|
auction.category,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
lbin,
|
||||||
|
sales,
|
||||||
|
auction.item_lore
|
||||||
|
);
|
||||||
|
const unstableOrMarketManipulated =
|
||||||
|
Math.abs((lbin - itemData.cleanPrice) / lbin) >
|
||||||
|
config.data.maxAvgLbinDiff;
|
||||||
ignoredAuctions.push(uuid);
|
ignoredAuctions.push(uuid);
|
||||||
const rcCost = config.data.includeCraftCost ? getRawCraft(prettyItem, workerData.bazaarData, workerData.itemDatas) : 0;
|
const rcCost = config.data.includeCraftCost
|
||||||
|
? getRawCraft(prettyItem, workerData.bazaarData, workerData.itemDatas)
|
||||||
|
: 0;
|
||||||
const carriedByRC = rcCost >= config.data.rawCraftMaxWeightPP * lbin;
|
const carriedByRC = rcCost >= config.data.rawCraftMaxWeightPP * lbin;
|
||||||
|
|
||||||
if (carriedByRC || unstableOrMarketManipulated || sales <= config.data.minSales || !sales) continue;
|
if (
|
||||||
|
carriedByRC ||
|
||||||
|
unstableOrMarketManipulated ||
|
||||||
|
sales <= config.data.minSales ||
|
||||||
|
!sales
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (config.filters.nameFilter.find((name) => itemID.includes(name)) === undefined) {
|
if (
|
||||||
if ((lbin + rcCost) - startingBid > minProfit) {
|
config.filters.itemIDExclusions.find((name) =>
|
||||||
|
itemID.includes(name)
|
||||||
|
) === undefined
|
||||||
|
) {
|
||||||
|
if (lbin + rcCost - startingBid > minProfit) {
|
||||||
const profitData = getProfit(startingBid, rcCost, lbin);
|
const profitData = getProfit(startingBid, rcCost, lbin);
|
||||||
let auctionType = null;
|
let auctionType = null;
|
||||||
if (rcCost > (lbin - startingBid) && profitData.snipeProfit < minProfit) {
|
if (
|
||||||
auctionType = "VALUE";
|
rcCost > lbin - startingBid &&
|
||||||
} else if (profitData.snipeProfit >= minProfit && rcCost < (lbin - startingBid)) {
|
profitData.snipeProfit < minProfit
|
||||||
auctionType = "SNIPE";
|
) {
|
||||||
|
auctionType = 'VALUE';
|
||||||
|
} else if (
|
||||||
|
profitData.snipeProfit >= minProfit &&
|
||||||
|
rcCost < lbin - startingBid
|
||||||
|
) {
|
||||||
|
auctionType = 'SNIPE';
|
||||||
} else if (profitData.snipeProfit >= minProfit && rcCost > 0) {
|
} else if (profitData.snipeProfit >= minProfit && rcCost > 0) {
|
||||||
auctionType = "BOTH";
|
auctionType = 'BOTH';
|
||||||
}
|
}
|
||||||
|
|
||||||
prettyItem.auctionData.ahType = auctionType;
|
prettyItem.auctionData.ahType = auctionType;
|
||||||
|
|
||||||
if (auctionType === "VALUE" || auctionType === "BOTH") {
|
if (auctionType === 'VALUE' || auctionType === 'BOTH') {
|
||||||
if (profitData.RCProfit > config.data.minCraftProfit && profitData.RCPP > config.data.minCraftPP) {
|
if (
|
||||||
|
profitData.RCProfit > config.data.minCraftProfit &&
|
||||||
|
profitData.RCPP > config.data.minCraftPP
|
||||||
|
) {
|
||||||
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 (profitData.snipeProfit > minProfit && profitData.snipePP > minPercentProfit) {
|
if (
|
||||||
|
profitData.snipeProfit > minProfit &&
|
||||||
|
profitData.snipePP > minPercentProfit
|
||||||
|
) {
|
||||||
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);
|
||||||
|
@ -80,12 +134,17 @@ async function parsePage(i) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Worker ${workerData.workerNumber}] Error parsing page ${i}:`, error);
|
console.error(
|
||||||
|
`[Worker ${workerData.workerNumber}] Error parsing page ${i}:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doTask(totalPages) {
|
async function doTask(totalPages) {
|
||||||
console.log(`[Worker ${workerData.workerNumber}] Starting task for ${totalPages} pages`);
|
console.log(
|
||||||
|
`[Worker ${workerData.workerNumber}] Starting task for ${totalPages} pages`
|
||||||
|
);
|
||||||
let startingPage = 0;
|
let startingPage = 0;
|
||||||
const pagePerThread = splitNumber(totalPages, threadsToUse);
|
const pagePerThread = splitNumber(totalPages, threadsToUse);
|
||||||
|
|
||||||
|
@ -97,18 +156,21 @@ async function doTask(totalPages) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let pageToStop = parseInt(startingPage) + parseInt(pagePerThread[workerData.workerNumber]);
|
let pageToStop =
|
||||||
|
parseInt(startingPage) + parseInt(pagePerThread[workerData.workerNumber]);
|
||||||
|
|
||||||
if (pageToStop !== totalPages) {
|
if (pageToStop !== totalPages) {
|
||||||
pageToStop -= 1;
|
pageToStop -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[Worker ${workerData.workerNumber}] Processing pages from ${startingPage} to ${pageToStop}`);
|
console.log(
|
||||||
|
`[Worker ${workerData.workerNumber}] Processing pages from ${startingPage} to ${pageToStop}`
|
||||||
|
);
|
||||||
|
|
||||||
for (let i = startingPage; i < pageToStop; i++) {
|
for (let i = startingPage; i < pageToStop; i++) {
|
||||||
promises.push(parsePage(i));
|
promises.push(parsePage(i));
|
||||||
}
|
}
|
||||||
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');
|
||||||
}
|
}
|
||||||
|
|
9
Dockerfile
Normal file
9
Dockerfile
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
FROM node:23-alpine3.20
|
||||||
|
|
||||||
|
COPY . /app
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
CMD ["node", "index.js"]
|
17
README.md
17
README.md
|
@ -19,3 +19,20 @@
|
||||||
- Ability to make some high tier enchantments worthless (like Looking 4, Luck 6 etc..)
|
- Ability to make some high tier enchantments worthless (like Looking 4, Luck 6 etc..)
|
||||||
- Bad Enchantment filter
|
- Bad Enchantment filter
|
||||||
- And more..
|
- And more..
|
||||||
|
|
||||||
|
# Setup
|
||||||
|
|
||||||
|
## .env
|
||||||
|
`.env` file is needed due to docker compose. Even if you are not using docker and docker compose, **.ENV IS NEEDED**
|
||||||
|
the example .env is
|
||||||
|
```
|
||||||
|
webhook_url=<url_of_webhook>
|
||||||
|
webhook_name=Flipper
|
||||||
|
webhook_profile_picture_link=https://cdn.discordapp.com/avatars/486155512568741900/164084b936b4461fe9505398f7383a0e.png?size=4096
|
||||||
|
```
|
||||||
|
You can also add these as environment variables into your system when running Node or run the app via `node --env-file=.env index.js`
|
||||||
|
|
||||||
|
## config.json
|
||||||
|
### filters
|
||||||
|
- itemIDExclusions: exclusion based on itemID (contains, doesnt have to be the full itemID name)
|
||||||
|
- EnchantThreshold and EnchantThresholdConditionalBypass are used for **Raw Crafting**. I suggest leaving them as they are.
|
||||||
|
|
19
config.json
19
config.json
|
@ -23,13 +23,8 @@
|
||||||
"includeCraftCost": true,
|
"includeCraftCost": true,
|
||||||
"minPriceForRawcraft": 5000000
|
"minPriceForRawcraft": 5000000
|
||||||
},
|
},
|
||||||
"webhook": {
|
|
||||||
"discordWebhookUrl": "WEBHOOK_URL",
|
|
||||||
"webhookName": "Flipper",
|
|
||||||
"webhookPFP": "https://cdn.discordapp.com/avatars/486155512568741900/164084b936b4461fe9505398f7383a0e.png?size=4096"
|
|
||||||
},
|
|
||||||
"filters": {
|
"filters": {
|
||||||
"rawCraftIgnoreEnchants": {
|
"EnchantThresholdConditionalBypass": {
|
||||||
"WITHER_": {
|
"WITHER_": {
|
||||||
"growth": 6,
|
"growth": 6,
|
||||||
"protection": 6
|
"protection": 6
|
||||||
|
@ -39,7 +34,7 @@
|
||||||
"protection": 6
|
"protection": 6
|
||||||
},
|
},
|
||||||
"NECROMANCER": {
|
"NECROMANCER": {
|
||||||
"growth": 6,
|
"growth": 4,
|
||||||
"protection": 6
|
"protection": 6
|
||||||
},
|
},
|
||||||
"SHREDDED": {
|
"SHREDDED": {
|
||||||
|
@ -50,7 +45,7 @@
|
||||||
"protection": 6
|
"protection": 6
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"badEnchants": {
|
"EnchantThreshold": {
|
||||||
"giant_killer": 6,
|
"giant_killer": 6,
|
||||||
"growth": 6,
|
"growth": 6,
|
||||||
"power": 6,
|
"power": 6,
|
||||||
|
@ -63,12 +58,12 @@
|
||||||
"vampirism": 6,
|
"vampirism": 6,
|
||||||
"luck": 6,
|
"luck": 6,
|
||||||
"syphon": 4,
|
"syphon": 4,
|
||||||
"ultimate_soul_eater": 3,
|
"ultimate_soul_eater": 6,
|
||||||
"ultimate_wise": 4,
|
"ultimate_wise": 5,
|
||||||
"ultimate_wisdom": 4,
|
"ultimate_wisdom": 5,
|
||||||
"ultimate_legion": 3
|
"ultimate_legion": 3
|
||||||
},
|
},
|
||||||
"nameFilter": [
|
"itemIDExclusions": [
|
||||||
"SALMON",
|
"SALMON",
|
||||||
"PERFECT",
|
"PERFECT",
|
||||||
"BEASTMASTER",
|
"BEASTMASTER",
|
||||||
|
|
12
docker-compose.yml
Normal file
12
docker-compose.yml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
services:
|
||||||
|
hypixel-flipper:
|
||||||
|
container_name: hypixel-flipper
|
||||||
|
build: .
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
volumes:
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
- ./config.json:/app/config.json
|
138
index.js
138
index.js
|
@ -1,10 +1,11 @@
|
||||||
const { default: axios } = require("axios");
|
const { default: axios } = require('axios');
|
||||||
const config = require("./config.json");
|
const config = require('./config.json');
|
||||||
const { WebhookClient, MessageEmbed } = require('discord.js');
|
const { WebhookClient, EmbedBuilder, Embed } = require('discord.js');
|
||||||
const { Worker } = require("worker_threads");
|
const { Worker } = require('worker_threads');
|
||||||
const { asyncInterval, addNotation } = require("./src/helperFunctions");
|
const { asyncInterval, addNotation } = require('./src/helperFunctions');
|
||||||
|
const { string } = require('prismarine-nbt');
|
||||||
|
|
||||||
let threadsToUse = config.data["threadsToUse/speed"] ?? 1;
|
let threadsToUse = config.data['threadsToUse/speed'] ?? 1;
|
||||||
let lastUpdated = 0;
|
let lastUpdated = 0;
|
||||||
let doneWorkers = 0;
|
let doneWorkers = 0;
|
||||||
let startingTime;
|
let startingTime;
|
||||||
|
@ -14,13 +15,13 @@ const workers = [];
|
||||||
const webhookRegex = /https:\/\/discord.com\/api\/webhooks\/(.+)\/(.+)/;
|
const webhookRegex = /https:\/\/discord.com\/api\/webhooks\/(.+)\/(.+)/;
|
||||||
|
|
||||||
const bazaarPrice = {
|
const bazaarPrice = {
|
||||||
"RECOMBOBULATOR_3000": 0,
|
RECOMBOBULATOR_3000: 0,
|
||||||
"HOT_POTATO_BOOK": 0,
|
HOT_POTATO_BOOK: 0,
|
||||||
"FUMING_POTATO_BOOK": 0
|
FUMING_POTATO_BOOK: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
async function initialize() {
|
async function initialize() {
|
||||||
const matches = config.webhook.discordWebhookUrl.match(webhookRegex);
|
const matches = process.env.webhook_url.match(webhookRegex);
|
||||||
if (!matches) return console.log(`[Main thread] Couldn't parse Webhook URL`);
|
if (!matches) return console.log(`[Main thread] Couldn't parse Webhook URL`);
|
||||||
const webhook = new WebhookClient({ id: matches[1], token: matches[2] });
|
const webhook = new WebhookClient({ id: matches[1], token: matches[2] });
|
||||||
|
|
||||||
|
@ -34,76 +35,111 @@ async function initialize() {
|
||||||
itemDatas: itemDatas,
|
itemDatas: itemDatas,
|
||||||
bazaarData: bazaarPrice,
|
bazaarData: bazaarPrice,
|
||||||
workerNumber: j,
|
workerNumber: j,
|
||||||
maxPrice: maxPrice
|
maxPrice: maxPrice,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
workers[j].on("message", async (result) => {
|
workers[j].on('message', async (result) => {
|
||||||
if (result.itemData !== undefined) {
|
if (result.itemData !== undefined) {
|
||||||
let averagePrice = itemDatas[result.itemData.id]?.cleanPrice || "N/A";
|
let averagePrice = itemDatas[result.itemData.id]?.cleanPrice || 'N/A';
|
||||||
if (result.auctionData.lbin - result.auctionData.price >= config.data.minSnipeProfit && averagePrice - result.auctionData.price >= config.data.minAvgProfit) {
|
if (
|
||||||
|
result.auctionData.lbin - result.auctionData.price >=
|
||||||
|
config.data.minSnipeProfit &&
|
||||||
|
averagePrice - result.auctionData.price >= config.data.minAvgProfit
|
||||||
|
) {
|
||||||
let mustBuyMessage = '';
|
let mustBuyMessage = '';
|
||||||
const embed = new MessageEmbed()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle(`**${(result.itemData.name).replace(/§./g, '')}**`)
|
.setTitle(`**${result.itemData.name.replace(/§./g, '')}**`)
|
||||||
.setColor("#2e3137")
|
.setColor('#2e3137')
|
||||||
.setThumbnail(`https://sky.shiiyu.moe/item/${result.itemData.id}`)
|
.setThumbnail(`https://sky.shiiyu.moe/item/${result.itemData.id}`)
|
||||||
.setDescription(`${mustBuyMessage}\nAuction: \`\`\`/viewauction ${result.auctionData.auctionID}\`\`\`\nProfit: \`${addNotation("oneLetters", (result.auctionData.profit))} (${result.auctionData.percentProfit}%)\`\nCost: \`${addNotation("oneLetters", (result.auctionData.price))}\`\nLBIN: \`${addNotation("oneLetters", (result.auctionData.lbin))}\`\nSales/Day: \`${addNotation("oneLetters", result.auctionData.sales)}\`\nType: \`${result.auctionData.ahType}\`\nAverage Price: \`${addNotation("oneLetters", averagePrice)}\``);
|
.setDescription(
|
||||||
|
`${mustBuyMessage}\nAuction:
|
||||||
|
\`\`\`/viewauction ${result.auctionData.auctionID}\`\`\`
|
||||||
|
\nProfit: \`${addNotation(
|
||||||
|
'oneLetters',
|
||||||
|
result.auctionData.profit
|
||||||
|
)} (${result.auctionData.percentProfit}%)\`
|
||||||
|
\nCost: \`${addNotation('oneLetters', result.auctionData.price)}\`
|
||||||
|
\nLBIN: \`${addNotation('oneLetters', result.auctionData.lbin)}\`
|
||||||
|
\nSales/Day: \`${addNotation(
|
||||||
|
'oneLetters',
|
||||||
|
result.auctionData.sales
|
||||||
|
)}\`
|
||||||
|
\nType: \`${result.auctionData.ahType}\`
|
||||||
|
\nAverage Price: \`${addNotation('oneLetters', averagePrice)}\``
|
||||||
|
);
|
||||||
|
|
||||||
await webhook.send({
|
await webhook.send({
|
||||||
username: config.webhook.webhookName,
|
username: process.env.webhook_name,
|
||||||
avatarURL: config.webhook.webhookPFP,
|
avatarURL: process.env.webhook_profile_picture_link,
|
||||||
embeds: [embed]
|
embeds: [embed],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (result === "finished") {
|
} else if (result === 'finished') {
|
||||||
doneWorkers++;
|
doneWorkers++;
|
||||||
if (doneWorkers === threadsToUse) {
|
if (doneWorkers === threadsToUse) {
|
||||||
doneWorkers = 0;
|
doneWorkers = 0;
|
||||||
console.log(`Completed in ${(Date.now() - startingTime) / 1000} seconds`);
|
console.log(
|
||||||
|
`Completed in ${(Date.now() - startingTime) / 1000} seconds`
|
||||||
|
);
|
||||||
startingTime = 0;
|
startingTime = 0;
|
||||||
workers[0].emit("done");
|
workers[0].emit('done');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
asyncInterval(async () => {
|
asyncInterval(
|
||||||
|
async () => {
|
||||||
await getLBINs();
|
await getLBINs();
|
||||||
workers.forEach((worker) => {
|
workers.forEach((worker) => {
|
||||||
worker.postMessage({ type: "moulberry", data: itemDatas });
|
worker.postMessage({ type: 'moulberry', data: itemDatas });
|
||||||
});
|
});
|
||||||
}, "lbin", 60000);
|
},
|
||||||
|
'lbin',
|
||||||
|
60000
|
||||||
|
);
|
||||||
|
|
||||||
asyncInterval(async () => {
|
asyncInterval(
|
||||||
|
async () => {
|
||||||
await getMoulberry();
|
await getMoulberry();
|
||||||
workers.forEach((worker) => {
|
workers.forEach((worker) => {
|
||||||
worker.postMessage({ type: "moulberry", data: itemDatas });
|
worker.postMessage({ type: 'moulberry', data: itemDatas });
|
||||||
});
|
});
|
||||||
}, "avg", 60e5);
|
},
|
||||||
|
'avg',
|
||||||
|
60e5
|
||||||
|
);
|
||||||
|
|
||||||
asyncInterval(async () => {
|
asyncInterval(
|
||||||
|
async () => {
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
const ahFirstPage = await axios.get("https://api.hypixel.net/skyblock/auctions?page=0");
|
const ahFirstPage = await axios.get(
|
||||||
|
'https://api.hypixel.net/skyblock/auctions?page=0'
|
||||||
|
);
|
||||||
const totalPages = ahFirstPage.data.totalPages;
|
const totalPages = ahFirstPage.data.totalPages;
|
||||||
if (ahFirstPage.data.lastUpdated === lastUpdated) {
|
if (ahFirstPage.data.lastUpdated === lastUpdated) {
|
||||||
resolve();
|
resolve();
|
||||||
} else {
|
} else {
|
||||||
lastUpdated = ahFirstPage.data.lastUpdated;
|
lastUpdated = ahFirstPage.data.lastUpdated;
|
||||||
startingTime = Date.now();
|
startingTime = Date.now();
|
||||||
console.log("Getting auctions..");
|
console.log('Getting auctions..');
|
||||||
workers.forEach((worker) => {
|
workers.forEach((worker) => {
|
||||||
worker.postMessage({ type: "pageCount", data: totalPages });
|
worker.postMessage({ type: 'pageCount', data: totalPages });
|
||||||
});
|
});
|
||||||
workers[0].once("done", () => {
|
workers[0].once('done', () => {
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, "check", 0);
|
},
|
||||||
|
'check',
|
||||||
|
0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getLBINs() {
|
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)) {
|
||||||
if (!itemDatas[item]) itemDatas[item] = {};
|
if (!itemDatas[item]) itemDatas[item] = {};
|
||||||
|
@ -112,10 +148,14 @@ async function getLBINs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMoulberry() {
|
async function getMoulberry() {
|
||||||
const moulberryAvgs = await axios.get("https://moulberry.codes/auction_averages/3day.json");
|
const moulberryAvgs = await axios.get(
|
||||||
|
'https://moulberry.codes/auction_averages/3day.json'
|
||||||
|
);
|
||||||
const avgData = moulberryAvgs.data;
|
const avgData = moulberryAvgs.data;
|
||||||
|
|
||||||
const cleanPriceAvgs = await axios.get("https://moulberry.codes/auction_averages_lbin/1day.json");
|
const cleanPriceAvgs = await axios.get(
|
||||||
|
'https://moulberry.codes/auction_averages_lbin/1day.json'
|
||||||
|
);
|
||||||
const cleanPriceData = cleanPriceAvgs.data;
|
const cleanPriceData = cleanPriceAvgs.data;
|
||||||
|
|
||||||
for (const item of Object.keys(avgData)) {
|
for (const item of Object.keys(avgData)) {
|
||||||
|
@ -123,15 +163,23 @@ async function getMoulberry() {
|
||||||
const itemInfo = avgData[item];
|
const itemInfo = avgData[item];
|
||||||
|
|
||||||
itemDatas[item].sales = itemInfo.sales !== undefined ? itemInfo.sales : 0;
|
itemDatas[item].sales = itemInfo.sales !== undefined ? itemInfo.sales : 0;
|
||||||
itemDatas[item].cleanPrice = cleanPriceData[item] !== undefined ? Math.round(cleanPriceData[item]) : (itemInfo.clean_price !== undefined ? itemInfo.clean_price : itemInfo.price);
|
itemDatas[item].cleanPrice =
|
||||||
|
cleanPriceData[item] !== undefined
|
||||||
|
? Math.round(cleanPriceData[item])
|
||||||
|
: itemInfo.clean_price !== undefined
|
||||||
|
? itemInfo.clean_price
|
||||||
|
: itemInfo.price;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getBzData() {
|
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"] = bzData.data.products.RECOMBOBULATOR_3000.quick_status.buyPrice;
|
bazaarPrice['RECOMBOBULATOR_3000'] =
|
||||||
bazaarPrice["HOT_POTATO_BOOK"] = bzData.data.products.HOT_POTATO_BOOK.quick_status.buyPrice;
|
bzData.data.products.RECOMBOBULATOR_3000.quick_status.buyPrice;
|
||||||
bazaarPrice["FUMING_POTATO_BOOK"] = bzData.data.products.FUMING_POTATO_BOOK.quick_status.buyPrice;
|
bazaarPrice['HOT_POTATO_BOOK'] =
|
||||||
|
bzData.data.products.HOT_POTATO_BOOK.quick_status.buyPrice;
|
||||||
|
bazaarPrice['FUMING_POTATO_BOOK'] =
|
||||||
|
bzData.data.products.FUMING_POTATO_BOOK.quick_status.buyPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize();
|
initialize();
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
"copy-paste": "^1.3.0",
|
"copy-paste": "^1.3.0",
|
||||||
"discord.js": "^12.5.3",
|
"discord.js": "^14.16.3",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"node-notifier": "^10.0.0",
|
"node-notifier": "^10.0.0",
|
||||||
"open": "^8.4.0",
|
"open": "^8.4.0",
|
||||||
|
@ -14,7 +14,6 @@
|
||||||
},
|
},
|
||||||
"description": "Hypixel Skyblock Auction House Flip Notifier",
|
"description": "Hypixel Skyblock Auction House Flip Notifier",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"devDependencies": {},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ."
|
"start": "node ."
|
||||||
},
|
},
|
||||||
|
|
1191
pnpm-lock.yaml
Normal file
1191
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
|
@ -90,17 +90,25 @@ function splitNumber (num = 1, parts = 1) {
|
||||||
|
|
||||||
function getRawCraft(item, bazaarPrice, lbins) {
|
function getRawCraft(item, bazaarPrice, lbins) {
|
||||||
let price = 0
|
let price = 0
|
||||||
const ignoreMatch = Object.keys(config.filters.rawCraftIgnoreEnchants).find((key) => {
|
const ignoreMatch = Object.keys(
|
||||||
if (item.itemData.id.includes(key)) return true
|
config.filters.EnchantThresholdConditionalBypass
|
||||||
})
|
).find((key) => {
|
||||||
|
if (item.itemData.id.includes(key)) return true;
|
||||||
|
});
|
||||||
if (item.auctionData.lbin < config.data.minPriceForRawcraft) return 0
|
if (item.auctionData.lbin < config.data.minPriceForRawcraft) return 0
|
||||||
let isInIgnore = ignoreMatch ? ignoreMatch : false
|
let isInIgnore = ignoreMatch ? ignoreMatch : false
|
||||||
if (item.itemData.enchants && !item.itemData.id.includes(';')) {
|
if (item.itemData.enchants && !item.itemData.id.includes(';')) {
|
||||||
for (const enchant of Object.keys(item.itemData.enchants)) {
|
for (const enchant of Object.keys(item.itemData.enchants)) {
|
||||||
const degree = item.itemData.enchants[enchant]
|
const degree = item.itemData.enchants[enchant]
|
||||||
const badEnchant = typeof config.filters.badEnchants[enchant] === 'number' ? degree >= config.filters.badEnchants[enchant] : false
|
const badEnchant =
|
||||||
|
typeof config.filters.EnchantThreshold[enchant] === 'number'
|
||||||
|
? degree >= config.filters.EnchantThreshold[enchant]
|
||||||
|
: false;
|
||||||
if (isInIgnore) {
|
if (isInIgnore) {
|
||||||
const enchantMinValue = config.filters.rawCraftIgnoreEnchants[ignoreMatch][enchant]
|
const enchantMinValue =
|
||||||
|
config.filters.EnchantThresholdConditionalBypass[ignoreMatch][
|
||||||
|
enchant
|
||||||
|
];
|
||||||
if (enchantMinValue >= degree) continue
|
if (enchantMinValue >= degree) continue
|
||||||
}
|
}
|
||||||
if (badEnchant) {
|
if (badEnchant) {
|
||||||
|
|
Loading…
Reference in a new issue