Compare commits

...

5 commits

Author SHA1 Message Date
cd3b9520ea added remote adding scripts 2024-12-28 11:39:31 +01:00
9f7cc8f8e9 added more types 2024-12-28 11:39:30 +01:00
d4275929ac enforcing strict types 2024-12-28 11:39:30 +01:00
sol
d44ad19efc sql: javascript > typescript 2024-12-28 11:39:26 +01:00
sol
c3badac626 setup sql framework 2024-12-28 11:39:16 +01:00
13 changed files with 153 additions and 37 deletions

4
.gitignore vendored
View file

@ -1,4 +1,6 @@
node_modules node_modules
.env .env
pnpm-lock* pnpm-lock*
dist dist
*.db
*.sqlite*

View file

@ -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
View 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
View 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

View file

@ -1,6 +1,6 @@
{ {
"data": { "data": {
"thread_count/speed": 48, "worker_count": 48,
"minSnipeProfit": 900000, "minSnipeProfit": 900000,
"minAvgProfit": 500000, "minAvgProfit": 500000,

View file

@ -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;

View file

@ -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"

View file

@ -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
View 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
}

View file

@ -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;

View file

@ -18,9 +18,7 @@ interface ConfigType {
}; };
} }
// Prismarine-nbt parses 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
View 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
}

View file

@ -11,6 +11,7 @@
"esModuleInterop": true, "esModuleInterop": true,
"skipLibCheck": true, "skipLibCheck": true,
"outDir": "./dist", "outDir": "./dist",
"strict": true,
}, },
"exclude": [ "exclude": [
"node_modules", "node_modules",