You've already forked tf2wikipricing
refactor: rewrite async promises as chain
This commit is contained in:
@@ -69,7 +69,7 @@ describe('Price Service', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('fetchPrice rejects with 401 when no token provided', async () => {
|
test('fetchPrice rejects with 401 when no token provided', async () => {
|
||||||
await expect(fetchPrice('', mockDefIndex + ";" + mockQuality)).rejects.toBe(401)
|
await expect(fetchPrice('', mockDefIndex + ";" + mockQuality)).rejects.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('fetchPrice handles pricing API errors', async () => {
|
test('fetchPrice handles pricing API errors', async () => {
|
||||||
@@ -77,7 +77,8 @@ describe('Price Service', () => {
|
|||||||
(priceUsingPricesTF as jest.Mock).mockRejectedValue(testError);
|
(priceUsingPricesTF as jest.Mock).mockRejectedValue(testError);
|
||||||
(getStorageValue as jest.Mock).mockResolvedValue(null)
|
(getStorageValue as jest.Mock).mockResolvedValue(null)
|
||||||
|
|
||||||
await expect(fetchPrice(mockToken, mockDefIndex + ";" + mockQuality)).rejects.toBe(testError)
|
await expect(fetchPrice(mockToken, mockDefIndex + ";" + mockQuality)).rejects.toThrow()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('fetchKeyPrice uses correct parameters', async () => {
|
test('fetchKeyPrice uses correct parameters', async () => {
|
||||||
|
|||||||
@@ -227,21 +227,17 @@ async function inject() {
|
|||||||
|
|
||||||
// Check item schema for Australium variant of current defindex
|
// Check item schema for Australium variant of current defindex
|
||||||
if(itemSchema[itemIndex].hasAustraliumVariant) {
|
if(itemSchema[itemIndex].hasAustraliumVariant) {
|
||||||
promises.push(new Promise(async (resolve) => {
|
promises.push(fetchPrice(token, `${itemIndex};11;australium`, currentTime).then(data => {
|
||||||
logDebug(`Fetching price for Australium ${itemName}`)
|
|
||||||
let data: ItemPriceData | null
|
|
||||||
try {
|
|
||||||
data = await fetchPrice(token, `${itemIndex};11;australium`, currentTime);
|
|
||||||
updateTime = new Date(data.update)
|
updateTime = new Date(data.update)
|
||||||
} catch {
|
log(`Saving price for Australium ${itemName}`)
|
||||||
log(`Australium ${itemName} is unpriced or unavailable, skipping...`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const priceRow = createPriceRow($T("Australium"), data, keyPrice, exchangeRates, locale, "https://wiki.teamfortress.com/wiki/Australium_weapons")
|
const priceRow = createPriceRow($T("Australium"), data, keyPrice, exchangeRates, locale, "https://wiki.teamfortress.com/wiki/Australium_weapons")
|
||||||
|
|
||||||
priceRows.push({order: 99, row: priceRow, category: PriceRowCategory.None})
|
priceRows.push({order: 99, row: priceRow, category: PriceRowCategory.None})
|
||||||
resolve()
|
})
|
||||||
return
|
.catch((error) => {
|
||||||
|
logError(error)
|
||||||
|
log(`Australium ${itemName} is unpriced or unavailable, skipping...`)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,37 +255,29 @@ async function inject() {
|
|||||||
festiveHeadingRow.style.display = 'none';
|
festiveHeadingRow.style.display = 'none';
|
||||||
festiveHeadingRow.appendChild(festiveHeading);
|
festiveHeadingRow.appendChild(festiveHeading);
|
||||||
|
|
||||||
promises.push(new Promise(async (resolve) => {
|
promises.push(fetchPrice(token, `${itemSchema[itemIndex].festiveVariant};6`, currentTime).then(data => {
|
||||||
logDebug(`Fetching price for Festive ${itemName}`)
|
|
||||||
let data: ItemPriceData | null
|
|
||||||
try {
|
|
||||||
data = await fetchPrice(token, `${itemSchema[itemIndex].festiveVariant};6`, currentTime);
|
|
||||||
updateTime = new Date(data.update)
|
updateTime = new Date(data.update)
|
||||||
} catch {
|
log(`updateTime price for Festive ${itemName}`)
|
||||||
log(`Festive ${itemName} is unpriced or unavailable, skipping...`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const priceRow = createPriceRow($T("Unique"), data, keyPrice, exchangeRates, locale)
|
const priceRow = createPriceRow($T("Unique"), data, keyPrice, exchangeRates, locale)
|
||||||
|
|
||||||
priceRows.push({order: -1, row: priceRow, category: PriceRowCategory.Festive})
|
priceRows.push({order: -1, row: priceRow, category: PriceRowCategory.Festive})
|
||||||
resolve()
|
})
|
||||||
return
|
.catch((error) => {
|
||||||
|
logError(error)
|
||||||
|
log(`Festive ${itemName} is unpriced or unavailable, skipping...`)
|
||||||
}))
|
}))
|
||||||
promises.push(new Promise(async (resolve) => {
|
promises.push(fetchPrice(token, `${itemSchema[itemIndex].festiveVariant};11`, currentTime).then(data => {
|
||||||
logDebug(`Fetching price for Strange Festive ${itemName}`)
|
|
||||||
let data: ItemPriceData | null
|
|
||||||
try {
|
|
||||||
data = await fetchPrice(token, `${itemSchema[itemIndex].festiveVariant};11`, currentTime);
|
|
||||||
updateTime = new Date(data.update)
|
updateTime = new Date(data.update)
|
||||||
} catch {
|
log(`Saving price for Strange Festive ${itemName}`)
|
||||||
log(`Strange Festive ${itemName} is unpriced or unavailable, skipping...`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const priceRow = createPriceRow($T("Strange"), data, keyPrice, exchangeRates, locale)
|
const priceRow = createPriceRow($T("Strange"), data, keyPrice, exchangeRates, locale)
|
||||||
|
|
||||||
priceRows.push({order: 11, row: priceRow, category: PriceRowCategory.Festive})
|
priceRows.push({order: 11, row: priceRow, category: PriceRowCategory.Festive})
|
||||||
resolve()
|
})
|
||||||
return
|
.catch((error) => {
|
||||||
|
logError(error)
|
||||||
|
log(`Strange Festive ${itemName} is unpriced or unavailable, skipping...`)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,10 +298,6 @@ async function inject() {
|
|||||||
killstreakKitHeadingRow.style.display = 'none';
|
killstreakKitHeadingRow.style.display = 'none';
|
||||||
killstreakKitHeadingRow.appendChild(heading);
|
killstreakKitHeadingRow.appendChild(heading);
|
||||||
[1,2,3].map((tier) => {
|
[1,2,3].map((tier) => {
|
||||||
promises.push(new Promise(async (resolve) => {
|
|
||||||
logDebug(`Fetching price for ${itemName} Killstreak Kit Tier ${tier}`)
|
|
||||||
let data: ItemPriceData | null
|
|
||||||
try {
|
|
||||||
let kitIndex: number
|
let kitIndex: number
|
||||||
switch (tier) {
|
switch (tier) {
|
||||||
default:
|
default:
|
||||||
@@ -321,19 +305,16 @@ async function inject() {
|
|||||||
case 2: kitIndex = 6523; break;
|
case 2: kitIndex = 6523; break;
|
||||||
case 3: kitIndex = 6526; break;
|
case 3: kitIndex = 6526; break;
|
||||||
}
|
}
|
||||||
data = await fetchPrice(token, `${kitIndex};6;uncraftable;kt-${tier};td-${itemIndex}`, currentTime);
|
promises.push(fetchPrice(token, `${kitIndex};6;uncraftable;kt-${tier};td-${itemIndex}`, currentTime).then(data => {
|
||||||
updateTime = new Date(data.update)
|
updateTime = new Date(data.update)
|
||||||
} catch {
|
logDebug(`Saving price for ${itemName} Killstreak Kit Tier ${tier}`)
|
||||||
log(`${itemName} Killstreak Kit Tier ${tier} is unpriced or unavailable, skipping...`)
|
|
||||||
resolve()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const priceRow = createPriceRow($T(`kt-${tier}`), data, keyPrice, exchangeRates, locale, "https://wiki.teamfortress.com/wiki/Killstreak_Kit")
|
const priceRow = createPriceRow($T(`kt-${tier}`), data, keyPrice, exchangeRates, locale, "https://wiki.teamfortress.com/wiki/Killstreak_Kit")
|
||||||
|
|
||||||
priceRows.push({order: tier, row: priceRow, category: PriceRowCategory.KillstreakKit})
|
priceRows.push({order: tier, row: priceRow, category: PriceRowCategory.KillstreakKit})
|
||||||
resolve()
|
})
|
||||||
return
|
.catch((error) => {
|
||||||
|
logError(`Failed to fetch price for ${itemName} Killstreak Kit Tier ${tier}`, error)
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -366,23 +347,17 @@ async function inject() {
|
|||||||
const itemName = itemSchema[variantIndex].name
|
const itemName = itemSchema[variantIndex].name
|
||||||
// FIXME: variantName should match wiki display name
|
// FIXME: variantName should match wiki display name
|
||||||
const variantName = itemName.includes('Mk.II') ? itemName.split(' ')[0] + ' Mk.II' : itemName.split(' ')[0]
|
const variantName = itemName.includes('Mk.II') ? itemName.split(' ')[0] + ' Mk.II' : itemName.split(' ')[0]
|
||||||
promises.push(new Promise(async (resolve) => {
|
promises.push(fetchPrice(token, `${variantIndex};11`, currentTime).then(data => {
|
||||||
logDebug(`Fetching price for ${itemName}`)
|
logDebug(`Saving price for ${itemName}`)
|
||||||
let data: ItemPriceData | null
|
|
||||||
try {
|
|
||||||
data = await fetchPrice(token, `${variantIndex};11`, currentTime);
|
|
||||||
updateTime = new Date(data.update)
|
updateTime = new Date(data.update)
|
||||||
} catch {
|
|
||||||
log(`${itemName} is unpriced or unavailable, skipping...`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const priceRow = createPriceRow($T(variantName), data, keyPrice, exchangeRates, locale, "https://wiki.teamfortress.com/wiki/Botkiller_weapons")
|
const priceRow = createPriceRow($T(variantName), data, keyPrice, exchangeRates, locale, "https://wiki.teamfortress.com/wiki/Botkiller_weapons")
|
||||||
|
|
||||||
// FIXME: order should be by release
|
|
||||||
// Silver Mk.I, Gold Mk.II, Rust, Blood, Carbonado, Diamond, Silver Mk.II, Gold Mk.II
|
|
||||||
priceRows.push({order: botkillerOrder.indexOf(variantName), row: priceRow, category: PriceRowCategory.Botkiller})
|
priceRows.push({order: botkillerOrder.indexOf(variantName), row: priceRow, category: PriceRowCategory.Botkiller})
|
||||||
resolve()
|
})
|
||||||
return
|
.catch((error) => {
|
||||||
|
logError(error)
|
||||||
|
log(`Strange Festive ${itemName} is unpriced or unavailable, skipping...`)
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { defindex_key, storage_priceprefix } from "./config"
|
import { defindex_key, storage_priceprefix } from "./config"
|
||||||
import { priceUsingPricesTF } from "./pricing/pricestf"
|
import { priceUsingPricesTF } from "./pricing/pricestf"
|
||||||
import { getStorageValue, setStorageValue } from "./storage"
|
import { getStorageValue, setStorageValue } from "./storage"
|
||||||
import { logDebug, log } from "./utils/log"
|
import { logDebug } from "./utils/log"
|
||||||
|
|
||||||
/** Pricing data for a given TF2 item. */
|
/** Pricing data for a given TF2 item. */
|
||||||
export class ItemPriceData {
|
export class ItemPriceData {
|
||||||
@@ -40,7 +40,6 @@ export async function fetchKeyPrice(token: string) {
|
|||||||
* @param ttl Time to cache results in milliseconds. 30 minutes by default.
|
* @param ttl Time to cache results in milliseconds. 30 minutes by default.
|
||||||
*/
|
*/
|
||||||
export async function fetchPrice(token: string, sku: string, update: Date = new Date(), ttl: number = 30 * 60 * 1000): Promise<ItemPriceData> {
|
export async function fetchPrice(token: string, sku: string, update: Date = new Date(), ttl: number = 30 * 60 * 1000): Promise<ItemPriceData> {
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
let data: ItemPriceData | null
|
let data: ItemPriceData | null
|
||||||
|
|
||||||
const cached: ItemPriceData = await getStorageValue(storage_priceprefix + sku, null)
|
const cached: ItemPriceData = await getStorageValue(storage_priceprefix + sku, null)
|
||||||
@@ -51,7 +50,7 @@ export async function fetchPrice(token: string, sku: string, update: Date = new
|
|||||||
if (!data || data.sku != sku || 'update' in data && 'ttl' in data && Date.now() > (new Date(data.update).getTime() + data.ttl)) {
|
if (!data || data.sku != sku || 'update' in data && 'ttl' in data && Date.now() > (new Date(data.update).getTime() + data.ttl)) {
|
||||||
logDebug(`Fetching price data for ${sku}`)
|
logDebug(`Fetching price data for ${sku}`)
|
||||||
if(!token) {
|
if(!token) {
|
||||||
reject(401)
|
throw new Error('No token provided')
|
||||||
}
|
}
|
||||||
data = new ItemPriceData()
|
data = new ItemPriceData()
|
||||||
data.sku = sku
|
data.sku = sku
|
||||||
@@ -65,8 +64,7 @@ export async function fetchPrice(token: string, sku: string, update: Date = new
|
|||||||
data.metal = response.metal
|
data.metal = response.metal
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log(`Received ${error} error while pricing ${sku} using prices.tf`)
|
throw new Error(`Received ${error} error while pricing ${sku} using prices.tf`)
|
||||||
reject(error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('metal' in data && 'keys' in data) {
|
if ('metal' in data && 'keys' in data) {
|
||||||
@@ -75,6 +73,5 @@ export async function fetchPrice(token: string, sku: string, update: Date = new
|
|||||||
} else {
|
} else {
|
||||||
logDebug(`Using cached price data for ${sku}`)
|
logDebug(`Using cached price data for ${sku}`)
|
||||||
}
|
}
|
||||||
resolve(data)
|
return data
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,49 +42,45 @@ async function priceUsingPricesTF(token: string, sku: string, retries: number =
|
|||||||
// prices.tf
|
// prices.tf
|
||||||
// https://api2.prices.tf/prices/${sku}
|
// https://api2.prices.tf/prices/${sku}
|
||||||
// Authorization: Bearer ${token}
|
// Authorization: Bearer ${token}
|
||||||
return GM_fetch(`https://api2.prices.tf/prices/${encodeURIComponent(sku)}`, {
|
try {
|
||||||
|
const response = await GM_fetch(`https://api2.prices.tf/prices/${encodeURIComponent(sku)}`, {
|
||||||
method: 'get',
|
method: 'get',
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
||||||
}
|
|
||||||
if (response.status === 404 && sku.includes(';') && !sku.includes(';uncraftable')) {
|
if (response.status === 404 && sku.includes(';') && !sku.includes(';uncraftable')) {
|
||||||
const quality: number = parseInt(sku.split(';')[1], 10);
|
const quality: number = parseInt(sku.split(';')[1], 10);
|
||||||
if(quality === 6) {
|
if(quality === 6) {
|
||||||
// Try uncraftable variant if unique weapon
|
// Try uncraftable variant if unique weapon
|
||||||
return priceUsingPricesTF(token, sku + ';uncraftable', retries);
|
return priceUsingPricesTF(token, sku + ';uncraftable');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(response.status === 404) {
|
|
||||||
throw new Error(`Item not found: ${sku}`);
|
|
||||||
}
|
|
||||||
if(response.status === 503) {
|
if(response.status === 503) {
|
||||||
// Happens if we send too many requests in a short period of time
|
// Happens if we send too many requests in a short period of time
|
||||||
// Retry after a few seconds
|
// Retry after a few seconds
|
||||||
if(retries > 0) {
|
if(retries >= 0) {
|
||||||
logDebug(`Cloudflare rate limit exceeded, trying again after 2 seconds, ${retries} retries left`)
|
logDebug(`Cloudflare rate limit exceeded, trying again after 1 second, ${retries} retries left`)
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
return priceUsingPricesTF(token, sku, retries - 1);
|
return priceUsingPricesTF(token, sku, retries - 1);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Cloudflare rate limit exceeded, stopping`)
|
throw new Error(`Cloudflare rate limit exceeded, stopping`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return response.json();
|
if (!response.ok) {
|
||||||
})
|
throw new Error(`Pricing request for ${sku} failed with status code: ${response.status}`);
|
||||||
.then(data => {
|
}
|
||||||
|
const data = await response.json();
|
||||||
const prices = new PricesResponse();
|
const prices = new PricesResponse();
|
||||||
prices.keys = data['sellKeys']
|
prices.keys = data['sellKeys']
|
||||||
prices.metal = data['sellHalfScrap'] / 18.0;
|
prices.metal = data['sellHalfScrap'] / 18.0;
|
||||||
return prices;
|
return prices;
|
||||||
})
|
}
|
||||||
.catch(error => {
|
catch(error) {
|
||||||
logError(`Failed to fetch prices from prices.tf for item ${sku}`)
|
logError(`Failed to fetch prices from prices.tf for item ${sku}`)
|
||||||
throw error;
|
throw error;
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getPricesToken, priceUsingPricesTF, PricesResponse }
|
export { getPricesToken, priceUsingPricesTF, PricesResponse }
|
||||||
Reference in New Issue
Block a user