|
/* |
|
Copyright 2025 https://github.com/robere2 |
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated |
|
documentation files (the “Software”), to deal in the Software without restriction, including without limitation |
|
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, |
|
and to permit persons to whom the Software is furnished to do so, subject to the following conditions: |
|
|
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions |
|
of the Software. |
|
|
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED |
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
|
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|
DEALINGS IN THE SOFTWARE. |
|
*/ |
|
|
|
/** |
|
* @license MIT |
|
* @author https://github.com/robere2 |
|
* @see {@link https://gist.github.com/robere2/232691590952b1f1c3de1993b291b6eb} Source |
|
*/ |
|
class AuctionApiError extends Error { |
|
constructor(message, options) { |
|
super(message, options); |
|
this.name = "AuctionApiError"; |
|
} |
|
} |
|
|
|
/** |
|
* Request an auction page from https://api.hypixel.net/v2/skyblock/auctions and verify that the API responded with 200 and "success" equal to true. |
|
* @param {number} pageNum Page to request. Must be an integer greater than or equal to 0. |
|
* @param {AbortSignal|undefined} signal Optional AbortController signal to cancel the request |
|
* @returns {Promise<Object, Error>} Parsed API auction data from the response |
|
* @reject {AuctionApiError} if the response code is not 200, if the body is not JSON, or if the "success" property on the body's JSON is not true. |
|
* @reject {AbortError} if you pass an AbortSignal as the second parameter and fire that signal before the request can finish. |
|
* @reject {TypeError} Network error |
|
* @license MIT |
|
* @author https://github.com/robere2 |
|
* @see {@link https://gist.github.com/robere2/232691590952b1f1c3de1993b291b6eb} Source |
|
* @see {@link https://api.hypixel.net/#tag/SkyBlock/paths/~1v2~1skyblock~1auction/get} Hypixel auction API documentation |
|
*/ |
|
async function fetchAuctionPage(pageNum, signal) { |
|
pageNum = Math.max(0, pageNum); |
|
pageNum = Math.round(pageNum); |
|
|
|
const res = await fetch(`https://api.hypixel.net/v2/skyblock/auctions?page=${pageNum}`, { |
|
signal |
|
}); |
|
|
|
if(res.status !== 200) { |
|
throw new AuctionApiError("Auctions API status code not equal to 200"); |
|
} |
|
|
|
let parsed; |
|
try { |
|
parsed = await res.json(); |
|
} catch (e) { |
|
throw new AuctionApiError("Auction API response body is not valid JSON"); |
|
} |
|
|
|
if(parsed.success !== true) { |
|
throw new AuctionApiError("Auction API status 'success' is not equal to true."); |
|
} |
|
return parsed; |
|
} |
|
|
|
/** |
|
* Robust function to fetch all auction pages from https://api.hypixel.net/v2/skyblock/auctions. If the auctions update during the time that |
|
* this function is executing, then it will restart in order to fetch the latest data. |
|
* @returns {Promise<Object[], Error>} Array of JSON responses from the API, with each entry being a page of auction results. The results are not necessarily in order. |
|
* @reject {TypeError} Network error |
|
* @reject {AuctionApiError} If the function restarts more than five times due to updates or an API response with "success" set to false. |
|
* @license MIT |
|
* @author https://github.com/robere2 |
|
* @see {@link https://gist.github.com/robere2/232691590952b1f1c3de1993b291b6eb} Source |
|
* @see {@link https://api.hypixel.net/#tag/SkyBlock/paths/~1v2~1skyblock~1auction/get} Hypixel auction API documentation |
|
*/ |
|
async function fetchAllAuctionPages() { |
|
let allPages = [] |
|
let tries = 0; |
|
while(allPages.length === 0) { |
|
// After five failed attempts, presumably more attempts aren't going to solve the problem. |
|
if(tries++ >= 5) { |
|
throw new AuctionApiError("Tried to fetch auction results five times, but all attempts failed. This could be a Hypixel API error, " + |
|
"or responses are not coming in fast enough to keep up with the updating results.") |
|
} |
|
|
|
// On failure, give the API a moment to resolve itself and then retry. |
|
let firstPage; |
|
try { |
|
firstPage = await fetchAuctionPage(0); |
|
} catch(e) { |
|
await new Promise(resolve => setTimeout(() => resolve(), 1000)); |
|
continue; |
|
} |
|
|
|
// Last updated timestamp is used to restart the process if results update between now and when we finish all page requests |
|
const lastUpdated = firstPage.lastUpdated; |
|
const abortController = new AbortController(); |
|
|
|
// Request each following page concurrently |
|
const promises = []; |
|
for(let i = 1; i < firstPage.totalPages; i++) { |
|
promises.push((async () => { |
|
try { |
|
const page = await fetchAuctionPage(i, abortController.signal); |
|
if(page.lastUpdated !== lastUpdated) { |
|
throw new AuctionApiError("Auction list has updated while fetching all auctions") |
|
} |
|
return page; |
|
} catch(e) { |
|
if(e.name === "AuctionApiError") { |
|
// Abort and restart if the results have updated or API says request failed. Wait a moment to give the |
|
// error some time to resolve itself. |
|
abortController.abort(); |
|
await new Promise(resolve => setTimeout(() => resolve(), 1000)); |
|
} else if(e.name !== "AbortError") { |
|
throw e; |
|
} |
|
} |
|
})()) |
|
} |
|
|
|
// Await all of the page requests to complete, and add their responses to an array with the first page |
|
allPages = [firstPage, ...await Promise.all(promises)] |
|
|
|
// Aborted due to failure or updated list, restart by deleting the responses |
|
if(abortController.signal.aborted) { |
|
allPages = []; |
|
} |
|
} |
|
|
|
return allPages; |
|
} |