Compare commits
5 commits
cd3b9520ea
...
8031a71bc5
Author | SHA1 | Date | |
---|---|---|---|
8031a71bc5 | |||
3d24fea0b2 | |||
7ce3de2229 | |||
6aa5a5a9e8 | |||
325b86f4a7 |
13 changed files with 153 additions and 37 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,4 +1,6 @@
|
||||||
node_modules
|
node_modules
|
||||||
.env
|
.env
|
||||||
pnpm-lock*
|
pnpm-lock*
|
||||||
dist
|
dist
|
||||||
|
*.db
|
||||||
|
*.sqlite*
|
|
@ -2,6 +2,7 @@ import axios from 'axios';
|
||||||
import { getParsed, getProfit, splitNumber, getRawCraft } from './src/helperFunctions';
|
import { getParsed, getProfit, splitNumber, getRawCraft } from './src/helperFunctions';
|
||||||
import { parentPort, workerData, isMainThread } from 'worker_threads';
|
import { parentPort, workerData, isMainThread } from 'worker_threads';
|
||||||
import { Item } from './src/Item';
|
import { Item } from './src/Item';
|
||||||
|
import { AuctionResponse, Auction, Bid } from './src/auctionType';
|
||||||
|
|
||||||
import { loadConfig } from './src/configLoader';
|
import { loadConfig } from './src/configLoader';
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
|
@ -10,8 +11,7 @@ if (isMainThread || !parentPort) {
|
||||||
throw new Error('This module can only be run in a Worker thread.');
|
throw new Error('This module can only be run in a Worker thread.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const threadsToUse: number = config.data['threadsToUse/speed'];
|
const worker_count: number = config.data.worker_count;
|
||||||
|
|
||||||
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: any[] = [];
|
||||||
|
@ -31,10 +31,10 @@ parentPort.on('message', async (message) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function parsePage(i) {
|
async function parsePage(i: number) {
|
||||||
console.log(`[Worker ${workerData.workerNumber}] Parsing page ${i}`);
|
console.log(`[Worker ${workerData.workerNumber}] Parsing page ${i}`);
|
||||||
try {
|
try {
|
||||||
const auctionPage = await axios.get(
|
const auctionPage = await axios.get<AuctionResponse>(
|
||||||
`https://api.hypixel.net/skyblock/auctions?page=${i}`
|
`https://api.hypixel.net/skyblock/auctions?page=${i}`
|
||||||
);
|
);
|
||||||
for (const auction of auctionPage.data.auctions) {
|
for (const auction of auctionPage.data.auctions) {
|
||||||
|
@ -144,12 +144,12 @@ async function parsePage(i) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doTask(totalPages) {
|
async function doTask(totalPages: number) {
|
||||||
console.log(
|
console.log(
|
||||||
`[Worker ${workerData.workerNumber}] Starting task for ${totalPages} pages`
|
`[Worker ${workerData.workerNumber}] Starting task for ${totalPages} pages`
|
||||||
);
|
);
|
||||||
let startingPage = 0;
|
let startingPage = 0;
|
||||||
const pagePerThread = splitNumber(totalPages, threadsToUse);
|
const pagePerThread = splitNumber(totalPages, worker_count);
|
||||||
|
|
||||||
if (workerData.workerNumber !== 0 && startingPage === 0) {
|
if (workerData.workerNumber !== 0 && startingPage === 0) {
|
||||||
const clonedStarting = pagePerThread.slice();
|
const clonedStarting = pagePerThread.slice();
|
||||||
|
|
2
add_remotes.bat
Normal file
2
add_remotes.bat
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
git remote add upstream https://github.com/MashClashXD/Hypixel-Auction-Flipper.git
|
||||||
|
git remote add upstream2 https://github.com/DuckySoLucky/Hypixel-Auction-Flipper.git
|
2
add_remotes.sh
Normal file
2
add_remotes.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
git remote add upstream https://github.com/MashClashXD/Hypixel-Auction-Flipper.git
|
||||||
|
git remote add upstream2 https://github.com/DuckySoLucky/Hypixel-Auction-Flipper.git
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"thread_count/speed": 48,
|
"worker_count": 48,
|
||||||
|
|
||||||
"minSnipeProfit": 900000,
|
"minSnipeProfit": 900000,
|
||||||
"minAvgProfit": 500000,
|
"minAvgProfit": 500000,
|
||||||
|
|
37
index.ts
37
index.ts
|
@ -3,17 +3,31 @@ import { WebhookClient, EmbedBuilder, Embed } from 'discord.js';
|
||||||
import { Worker } from 'worker_threads';
|
import { Worker } from 'worker_threads';
|
||||||
import { asyncInterval, addNotation } from './src/helperFunctions';
|
import { asyncInterval, addNotation } from './src/helperFunctions';
|
||||||
import { string } from 'prismarine-nbt';
|
import { string } from 'prismarine-nbt';
|
||||||
|
import { InitTable } from './src/sqlFunctions';
|
||||||
import { loadConfig } from './src/configLoader';
|
import { loadConfig } from './src/configLoader';
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
|
|
||||||
|
class ItemData {
|
||||||
|
public sales: number;
|
||||||
|
public lbin: number;
|
||||||
|
public cleanPrice: number;
|
||||||
|
public price: number;
|
||||||
|
|
||||||
let thread_count = config.data['thread_count/speed'] ?? 1;
|
constructor(sales: number = 0, lbin: number = 0, cleanPrice: number = 0, price: number = 0) {
|
||||||
|
this.sales = sales;
|
||||||
|
this.lbin = lbin;
|
||||||
|
this.cleanPrice = cleanPrice;
|
||||||
|
this.price = price;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
let worker_count = config.data.worker_count ?? 1;
|
||||||
let lastUpdated = 0;
|
let lastUpdated = 0;
|
||||||
let doneWorkers = 0;
|
let doneWorkers = 0;
|
||||||
let startingTime;
|
let startingTime: number;
|
||||||
let maxPrice = 0;
|
let maxPrice = 0;
|
||||||
let itemDatas = {};
|
let itemDatas: Record<string, ItemData> = {};
|
||||||
const workers: Worker[] = [];
|
const workers: Worker[] = [];
|
||||||
const webhookRegex = /https:\/\/discord.com\/api\/webhooks\/(.+)\/(.+)/;
|
const webhookRegex = /https:\/\/discord.com\/api\/webhooks\/(.+)\/(.+)/;
|
||||||
|
|
||||||
|
@ -24,6 +38,7 @@ const bazaarPrice = {
|
||||||
};
|
};
|
||||||
|
|
||||||
async function initialize() {
|
async function initialize() {
|
||||||
|
await InitTable();
|
||||||
const matches = process.env.WEBHOOK_URL.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] });
|
||||||
|
@ -32,7 +47,7 @@ async function initialize() {
|
||||||
await getMoulberry();
|
await getMoulberry();
|
||||||
await getLBINs();
|
await getLBINs();
|
||||||
|
|
||||||
for (let j = 0; j < thread_count; j++) {
|
for (let j = 0; j < worker_count; j++) {
|
||||||
workers[j] = new Worker('/app/dist/AuctionHandler.worker.js', {
|
workers[j] = new Worker('/app/dist/AuctionHandler.worker.js', {
|
||||||
workerData: {
|
workerData: {
|
||||||
itemDatas: itemDatas,
|
itemDatas: itemDatas,
|
||||||
|
@ -44,11 +59,11 @@ async function initialize() {
|
||||||
|
|
||||||
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: number | null = itemDatas[result.itemData.id]?.cleanPrice || null;
|
||||||
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 || averagePrice! - result.auctionData.price >= config.data.minAvgProfit)
|
||||||
) {
|
) {
|
||||||
let mustBuyMessage = '';
|
let mustBuyMessage = '';
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
|
@ -69,7 +84,7 @@ async function initialize() {
|
||||||
result.auctionData.sales
|
result.auctionData.sales
|
||||||
)}\`
|
)}\`
|
||||||
\nType: \`${result.auctionData.ahType}\`
|
\nType: \`${result.auctionData.ahType}\`
|
||||||
\nAverage Price: \`${addNotation('oneLetters', averagePrice)}\``
|
\nAverage Price: \`${averagePrice ? addNotation('oneLetters', averagePrice) : 'N/A'}\``
|
||||||
);
|
);
|
||||||
|
|
||||||
await webhook.send({
|
await webhook.send({
|
||||||
|
@ -80,7 +95,7 @@ async function initialize() {
|
||||||
}
|
}
|
||||||
} else if (result === 'finished') {
|
} else if (result === 'finished') {
|
||||||
doneWorkers++;
|
doneWorkers++;
|
||||||
if (doneWorkers === thread_count) {
|
if (doneWorkers === worker_count) {
|
||||||
doneWorkers = 0;
|
doneWorkers = 0;
|
||||||
console.log(
|
console.log(
|
||||||
`Completed in ${(Date.now() - startingTime) / 1000} seconds`
|
`Completed in ${(Date.now() - startingTime) / 1000} seconds`
|
||||||
|
@ -145,7 +160,7 @@ async function getLBINs(): Promise<void> {
|
||||||
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] = new ItemData();
|
||||||
itemDatas[item].lbin = lbinData[item];
|
itemDatas[item].lbin = lbinData[item];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,7 +177,7 @@ async function getMoulberry(): Promise<void> {
|
||||||
const cleanPriceData = cleanPriceAvgs.data;
|
const cleanPriceData = cleanPriceAvgs.data;
|
||||||
|
|
||||||
for (const item of Object.keys(avgData)) {
|
for (const item of Object.keys(avgData)) {
|
||||||
if (!itemDatas[item]) itemDatas[item] = {};
|
if (!itemDatas[item]) itemDatas[item] = new ItemData();
|
||||||
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;
|
||||||
|
|
|
@ -17,11 +17,11 @@
|
||||||
"copy-paste": "^1.5.3",
|
"copy-paste": "^1.5.3",
|
||||||
"discord.js": "^14.16.3",
|
"discord.js": "^14.16.3",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
"node": "^23.5.0",
|
|
||||||
"node-notifier": "^10.0.1",
|
"node-notifier": "^10.0.1",
|
||||||
"open": "^8.4.2",
|
"open": "^8.4.2",
|
||||||
"prismarine-nbt": "^2.7.0",
|
"prismarine-nbt": "^2.7.0",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
|
"sqlite3": "^5.1.7",
|
||||||
"toastify-js": "^1.12.0",
|
"toastify-js": "^1.12.0",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.7.2"
|
"typescript": "^5.7.2"
|
||||||
|
|
|
@ -6,7 +6,7 @@ export interface ItemData {
|
||||||
stars: number;
|
stars: number;
|
||||||
rarity: string;
|
rarity: string;
|
||||||
recomb: boolean;
|
recomb: boolean;
|
||||||
enchants: string[];
|
enchants: Record<string, number>;
|
||||||
hpbs: number;
|
hpbs: number;
|
||||||
fpbs: number;
|
fpbs: number;
|
||||||
gemstones: string[];
|
gemstones: string[];
|
||||||
|
@ -34,7 +34,7 @@ export class Item {
|
||||||
auctionID: string,
|
auctionID: string,
|
||||||
price: number,
|
price: number,
|
||||||
rarity: string,
|
rarity: string,
|
||||||
enchants: string[],
|
enchants: Record<string, number>,
|
||||||
hpbs: number,
|
hpbs: number,
|
||||||
fpbs: number,
|
fpbs: number,
|
||||||
recomb: boolean,
|
recomb: boolean,
|
||||||
|
|
39
src/auctionType.ts
Normal file
39
src/auctionType.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
export type AuctionResponse = {
|
||||||
|
success: boolean
|
||||||
|
page: number
|
||||||
|
totalPages: number
|
||||||
|
totalAuctions: number
|
||||||
|
lastUpdated: number
|
||||||
|
auctions: Auction[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Auction = {
|
||||||
|
uuid: string
|
||||||
|
auctioneer: string
|
||||||
|
profile_id: string
|
||||||
|
coop: string[]
|
||||||
|
start: number
|
||||||
|
end: number
|
||||||
|
item_name: string
|
||||||
|
item_lore: string
|
||||||
|
extra: string
|
||||||
|
category: string
|
||||||
|
tier: string
|
||||||
|
starting_bid: number
|
||||||
|
item_bytes: string
|
||||||
|
claimed: boolean
|
||||||
|
claimed_bidders: any[]
|
||||||
|
highest_bid_amount: number
|
||||||
|
last_updated: number
|
||||||
|
bin: boolean
|
||||||
|
bids: Bid[]
|
||||||
|
item_uuid?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Bid = {
|
||||||
|
auction_id: string
|
||||||
|
bidder: string
|
||||||
|
profile_id: string
|
||||||
|
amount: number
|
||||||
|
timestamp: number
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ import * as path from 'path';
|
||||||
|
|
||||||
export interface ConfigType {
|
export interface ConfigType {
|
||||||
data: {
|
data: {
|
||||||
'threadsToUse/speed': number;
|
worker_count: number;
|
||||||
minSnipeProfit: number;
|
minSnipeProfit: number;
|
||||||
minAvgProfit: number;
|
minAvgProfit: number;
|
||||||
minCraftProfit: number;
|
minCraftProfit: number;
|
||||||
|
@ -11,14 +11,7 @@ export interface ConfigType {
|
||||||
rawCraftMaxWeightPP: number;
|
rawCraftMaxWeightPP: number;
|
||||||
minSnipePP: number;
|
minSnipePP: number;
|
||||||
minCraftPP: number;
|
minCraftPP: number;
|
||||||
ignoreCategories: {
|
ignoreCategories: Record<string, boolean>;
|
||||||
weapon: boolean;
|
|
||||||
accessories: boolean;
|
|
||||||
armor: boolean;
|
|
||||||
misc: boolean;
|
|
||||||
blocks: boolean;
|
|
||||||
consumables: boolean;
|
|
||||||
}
|
|
||||||
minSales: number;
|
minSales: number;
|
||||||
includeCraftCost: boolean;
|
includeCraftCost: boolean;
|
||||||
minPriceForRawcraft: number;
|
minPriceForRawcraft: number;
|
||||||
|
|
|
@ -18,9 +18,7 @@ interface ConfigType {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prismarine-nbt parse’s callback has this shape:
|
|
||||||
interface NbtData {
|
interface NbtData {
|
||||||
// If you know the exact shape of the parsed data, define it here
|
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +87,6 @@ function getProfit(price: number, rcCost: number, lbin: number): ProfitItem {
|
||||||
snipePP: 0,
|
snipePP: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Auction house fee logic
|
|
||||||
if (price >= 1_000_000) {
|
if (price >= 1_000_000) {
|
||||||
profitItem.RCProfit = lbin + rcCost - price - (lbin + rcCost) * 0.02;
|
profitItem.RCProfit = lbin + rcCost - price - (lbin + rcCost) * 0.02;
|
||||||
profitItem.RCPP = parseFloat(((profitItem.RCProfit * 100) / lbin).toFixed(1));
|
profitItem.RCPP = parseFloat(((profitItem.RCProfit * 100) / lbin).toFixed(1));
|
||||||
|
@ -126,7 +123,6 @@ function splitNumber(num: number = 1, parts: number = 1): number[] {
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adjust the below types as needed to match your item structure:
|
|
||||||
interface ItemType {
|
interface ItemType {
|
||||||
itemData: {
|
itemData: {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -160,7 +156,6 @@ function getRawCraft(
|
||||||
): number {
|
): number {
|
||||||
let price = 0;
|
let price = 0;
|
||||||
|
|
||||||
// If you have a more specific type for config, use that
|
|
||||||
const typedConfig = config as ConfigType;
|
const typedConfig = config as ConfigType;
|
||||||
|
|
||||||
const ignoreMatch: string =
|
const ignoreMatch: string =
|
||||||
|
@ -183,7 +178,6 @@ function getRawCraft(
|
||||||
if (isInIgnore) {
|
if (isInIgnore) {
|
||||||
const enchantMinValue =
|
const enchantMinValue =
|
||||||
typedConfig.filters.EnchantThresholdConditionalBypass[ignoreMatch]?.[enchant];
|
typedConfig.filters.EnchantThresholdConditionalBypass[ignoreMatch]?.[enchant];
|
||||||
// If enchantMinValue is defined and the degree is within that threshold, skip.
|
|
||||||
if (enchantMinValue && degree <= enchantMinValue) continue;
|
if (enchantMinValue && degree <= enchantMinValue) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
68
src/sqlFunctions.ts
Normal file
68
src/sqlFunctions.ts
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import sqlite3 from 'sqlite3';
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
// MUTEX functions for adding/removing/upsert
|
||||||
|
// basic read function by id
|
||||||
|
// complex read function by value range
|
||||||
|
|
||||||
|
async function InitTable() {
|
||||||
|
const db = new sqlite3.Database('bot_data');
|
||||||
|
try{
|
||||||
|
await runQuery(db,'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)')
|
||||||
|
console.log('Table created successfully.');
|
||||||
|
|
||||||
|
// Insert data
|
||||||
|
await runQuery(db, 'INSERT INTO users (name) VALUES (?)', ['Alice']);
|
||||||
|
console.log('Data inserted successfully.');
|
||||||
|
|
||||||
|
// Retrieve a single row
|
||||||
|
const row = await getQuery(db, 'SELECT * FROM users WHERE name = ?', ['Alice']);
|
||||||
|
console.log('Retrieved row:', row);
|
||||||
|
|
||||||
|
// Retrieve all rows
|
||||||
|
const rows = await allQuery(db, 'SELECT * FROM users');
|
||||||
|
console.log('All rows:', rows);
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('Database error:', err.message);
|
||||||
|
} finally {
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function runQuery(db: sqlite3.Database, query: string, params: string[] = []) {
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
db.run(query, params, function (err) {
|
||||||
|
if(err){
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
resolve(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function getQuery(db: sqlite3.Database, query: string, params: string[] = []) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.get(query, params, (err, row) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(row);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function allQuery(db: sqlite3.Database, query: string, params: string[] = []) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.all(query, params, (err, rows) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(rows);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
InitTable
|
||||||
|
}
|
|
@ -11,6 +11,7 @@
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
|
"strict": true,
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
|
|
Loading…
Add table
Reference in a new issue