You've already forked tf2wikipricing
bump version to 0.5.0
- Adds festive & botkiller pricing - Script retries if API is hit too frequently
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, test, mock } from "bun:test";
|
import { describe, expect, test, mock } from "bun:test";
|
||||||
import { ItemSchema, ItemSlot, getItemIndexByName, getTradableStatusByDefindex, getTradableStatusByName, prepareSchema } from '../src/content/schemaService'
|
import { ItemSchema, ItemSlot, getItemIndexByName, getTradableStatusByDefindex, getTradableStatusByName, linkBotkillerVariants, linkFestiveVariants, prepareSchema } from '../src/content/schemaService'
|
||||||
|
|
||||||
// Mock the storage and log functions
|
// Mock the storage and log functions
|
||||||
mock.module('../src/content/storage', () => ({
|
mock.module('../src/content/storage', () => ({
|
||||||
@@ -19,35 +19,72 @@ const mockSchema: ItemSchema = {
|
|||||||
slot: ItemSlot.Primary,
|
slot: ItemSlot.Primary,
|
||||||
tradable: false,
|
tradable: false,
|
||||||
hasAustraliumVariant: false,
|
hasAustraliumVariant: false,
|
||||||
canKillstreakify: true
|
canKillstreakify: true,
|
||||||
|
festiveVariant: null,
|
||||||
|
botkillerVariants: null
|
||||||
},
|
},
|
||||||
'208': {
|
'208': {
|
||||||
name: 'Flame Thrower',
|
name: 'Flame Thrower',
|
||||||
slot: ItemSlot.Primary,
|
slot: ItemSlot.Primary,
|
||||||
tradable: true,
|
tradable: true,
|
||||||
hasAustraliumVariant: true,
|
hasAustraliumVariant: true,
|
||||||
canKillstreakify: true
|
canKillstreakify: true,
|
||||||
|
festiveVariant: 659,
|
||||||
|
botkillerVariants: [798, 807]
|
||||||
|
},
|
||||||
|
'659': {
|
||||||
|
name: 'Festive Flame Thrower',
|
||||||
|
slot: ItemSlot.Primary,
|
||||||
|
tradable: true,
|
||||||
|
hasAustraliumVariant: false,
|
||||||
|
canKillstreakify: true,
|
||||||
|
festiveVariant: null,
|
||||||
|
botkillerVariants: null
|
||||||
|
},
|
||||||
|
'798': {
|
||||||
|
name: 'Silver Botkiller Flame Thrower Mk.I',
|
||||||
|
slot: ItemSlot.Primary,
|
||||||
|
tradable: true,
|
||||||
|
hasAustraliumVariant: false,
|
||||||
|
canKillstreakify: true,
|
||||||
|
festiveVariant: null,
|
||||||
|
botkillerVariants: null
|
||||||
|
},
|
||||||
|
'807': {
|
||||||
|
name: 'Gold Botkiller Flame Thrower Mk.I',
|
||||||
|
slot: ItemSlot.Primary,
|
||||||
|
tradable: true,
|
||||||
|
hasAustraliumVariant: false,
|
||||||
|
canKillstreakify: true,
|
||||||
|
festiveVariant: null,
|
||||||
|
botkillerVariants: null
|
||||||
},
|
},
|
||||||
'5021': {
|
'5021': {
|
||||||
name: 'Mann Co. Supply Crate Key',
|
name: 'Mann Co. Supply Crate Key',
|
||||||
slot: ItemSlot.Tool,
|
slot: ItemSlot.Tool,
|
||||||
tradable: true,
|
tradable: true,
|
||||||
hasAustraliumVariant: false,
|
hasAustraliumVariant: false,
|
||||||
canKillstreakify: false
|
canKillstreakify: false,
|
||||||
|
festiveVariant: null,
|
||||||
|
botkillerVariants: null
|
||||||
},
|
},
|
||||||
'15141': {
|
'15141': {
|
||||||
name: 'Flame Thrower',
|
name: 'Flame Thrower',
|
||||||
slot: ItemSlot.Primary,
|
slot: ItemSlot.Primary,
|
||||||
tradable: true,
|
tradable: true,
|
||||||
hasAustraliumVariant: false,
|
hasAustraliumVariant: false,
|
||||||
canKillstreakify: true
|
canKillstreakify: true,
|
||||||
|
festiveVariant: null,
|
||||||
|
botkillerVariants: null
|
||||||
},
|
},
|
||||||
'69420': {
|
'69420': {
|
||||||
name: 'Non-Tradable Item',
|
name: 'Non-Tradable Item',
|
||||||
slot: ItemSlot.Misc,
|
slot: ItemSlot.Misc,
|
||||||
tradable: false,
|
tradable: false,
|
||||||
hasAustraliumVariant: false,
|
hasAustraliumVariant: false,
|
||||||
canKillstreakify: false
|
canKillstreakify: false,
|
||||||
|
festiveVariant: null,
|
||||||
|
botkillerVariants: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,4 +154,187 @@ describe('Schema Service', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('linkFestiveVariants', () => {
|
||||||
|
test('should link festive variants to their original counterparts', () => {
|
||||||
|
const testSchema = {
|
||||||
|
'21': { // Stock (should be ignored)
|
||||||
|
name: 'Flame Thrower',
|
||||||
|
slot: ItemSlot.Primary,
|
||||||
|
tradable: false,
|
||||||
|
hasAustraliumVariant: false,
|
||||||
|
canKillstreakify: false,
|
||||||
|
festiveVariant: null,
|
||||||
|
botkillerVariants: null
|
||||||
|
},
|
||||||
|
'208': { // Original Flame Thrower
|
||||||
|
name: 'Flame Thrower',
|
||||||
|
slot: ItemSlot.Primary,
|
||||||
|
tradable: true,
|
||||||
|
hasAustraliumVariant: true,
|
||||||
|
canKillstreakify: true,
|
||||||
|
festiveVariant: null,
|
||||||
|
botkillerVariants: null
|
||||||
|
},
|
||||||
|
'659': { // Festive Flame Thrower (should be detected)
|
||||||
|
name: 'Festive Flame Thrower',
|
||||||
|
slot: ItemSlot.Primary,
|
||||||
|
tradable: true,
|
||||||
|
hasAustraliumVariant: false,
|
||||||
|
canKillstreakify: true,
|
||||||
|
festiveVariant: null,
|
||||||
|
botkillerVariants: null
|
||||||
|
},
|
||||||
|
'15141': { // Decorated (should be ignored)
|
||||||
|
name: 'Flame Thrower',
|
||||||
|
slot: ItemSlot.Primary,
|
||||||
|
tradable: true,
|
||||||
|
hasAustraliumVariant: false,
|
||||||
|
canKillstreakify: true,
|
||||||
|
festiveVariant: null,
|
||||||
|
botkillerVariants: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const mockResponseItems = [
|
||||||
|
{ item_class: 'tf_weapon_flamethrower', defindex: 21, item_name: 'Flame Thrower' }, // Incorrect; stock
|
||||||
|
{ item_class: 'tf_weapon_flamethrower', defindex: 208, item_name: 'Flame Thrower' }, // Original
|
||||||
|
{ item_class: 'tf_weapon_flamethrower', defindex: 659, item_name: 'Festive Flame Thrower' }, // Festive
|
||||||
|
{ item_class: 'tf_weapon_flamethrower', defindex: 15141, item_name: 'Flame Thrower' }, // Incorrect; decorated
|
||||||
|
];
|
||||||
|
linkFestiveVariants(mockResponseItems, testSchema)
|
||||||
|
expect(testSchema['21'].festiveVariant).toBeNull()
|
||||||
|
expect(testSchema['208'].festiveVariant).toBe(659)
|
||||||
|
expect(testSchema['659'].festiveVariant).toBeNull();
|
||||||
|
expect(testSchema['15141'].festiveVariant).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not link if no festive variant exists', () => {
|
||||||
|
const testSchema = {
|
||||||
|
'163': {
|
||||||
|
name: 'Crit-a-Cola',
|
||||||
|
slot: ItemSlot.Secondary,
|
||||||
|
tradable: true,
|
||||||
|
hasAustraliumVariant: false,
|
||||||
|
canKillstreakify: false,
|
||||||
|
festiveVariant: null,
|
||||||
|
botkillerVariants: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockResponseItems = [
|
||||||
|
{ item_class: 'tf_weapon_lunchbox_drink', defindex: 163, item_name: 'Crit-a-Cola' }
|
||||||
|
];
|
||||||
|
|
||||||
|
linkFestiveVariants(mockResponseItems, testSchema);
|
||||||
|
|
||||||
|
expect(testSchema['163'].festiveVariant).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('linkBotkillerVariants', () => {
|
||||||
|
test('should link botkiller variants to their original counterparts', () => {
|
||||||
|
const testSchema = {
|
||||||
|
'21': { // Original Rocket Launcher
|
||||||
|
name: 'Flame Thrower',
|
||||||
|
slot: ItemSlot.Primary,
|
||||||
|
tradable: false,
|
||||||
|
hasAustraliumVariant: false,
|
||||||
|
canKillstreakify: false,
|
||||||
|
festiveVariant: null,
|
||||||
|
botkillerVariants: null
|
||||||
|
},
|
||||||
|
'208': { // Original Flame Thrower
|
||||||
|
name: 'Flame Thrower',
|
||||||
|
slot: ItemSlot.Primary,
|
||||||
|
tradable: true,
|
||||||
|
hasAustraliumVariant: true,
|
||||||
|
canKillstreakify: true,
|
||||||
|
festiveVariant: null,
|
||||||
|
botkillerVariants: null
|
||||||
|
},
|
||||||
|
'798': {
|
||||||
|
name: 'Silver Botkiller Flame Thrower Mk. I',
|
||||||
|
slot: ItemSlot.Primary,
|
||||||
|
tradable: true,
|
||||||
|
hasAustraliumVariant: false,
|
||||||
|
canKillstreakify: true,
|
||||||
|
festiveVariant: null,
|
||||||
|
botkillerVariants: null
|
||||||
|
},
|
||||||
|
'807': {
|
||||||
|
name: 'Gold Botkiller Flame Thrower Mk. I',
|
||||||
|
slot: ItemSlot.Primary,
|
||||||
|
tradable: true,
|
||||||
|
hasAustraliumVariant: false,
|
||||||
|
canKillstreakify: true,
|
||||||
|
festiveVariant: null,
|
||||||
|
botkillerVariants: null
|
||||||
|
},
|
||||||
|
'15141': { // Decorated (should be ignored)
|
||||||
|
name: 'Flame Thrower',
|
||||||
|
slot: ItemSlot.Primary,
|
||||||
|
tradable: true,
|
||||||
|
hasAustraliumVariant: false,
|
||||||
|
canKillstreakify: true,
|
||||||
|
festiveVariant: null,
|
||||||
|
botkillerVariants: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const mockResponseItems = [
|
||||||
|
{ item_class: 'tf_weapon_flamethrower', defindex: 21, item_name: 'Flame Thrower', item_type_name: 'Flame Thrower'
|
||||||
|
}, // Incorrect; stock
|
||||||
|
{
|
||||||
|
item_class: 'tf_weapon_flamethrower',
|
||||||
|
defindex: 208,
|
||||||
|
item_name: 'Flame Thrower',
|
||||||
|
item_type_name: 'Flame Thrower'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
item_class: 'tf_weapon_flamethrower',
|
||||||
|
defindex: 798,
|
||||||
|
item_name: 'Silver Botkiller Flame Thrower Mk.I',
|
||||||
|
item_type_name: 'Flame Thrower'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
item_class: 'tf_weapon_flamethrower',
|
||||||
|
defindex: 807,
|
||||||
|
item_name: 'Gold Botkiller Flame Thrower Mk.I',
|
||||||
|
item_type_name: 'Flame Thrower'
|
||||||
|
},
|
||||||
|
{ item_class: 'tf_weapon_flamethrower', defindex: 15141, item_name: 'Flame Thrower', item_type_name: 'Flame Thrower' }, // Incorrect; decorated
|
||||||
|
];
|
||||||
|
linkBotkillerVariants(mockResponseItems, testSchema);
|
||||||
|
expect(testSchema['21'].botkillerVariants).toBeNull()
|
||||||
|
expect(testSchema['208'].botkillerVariants).toEqual([798, 807]);
|
||||||
|
expect(testSchema['798'].botkillerVariants).toBeNull();
|
||||||
|
expect(testSchema['807'].botkillerVariants).toBeNull();
|
||||||
|
expect(testSchema['15141'].botkillerVariants).toBeNull()
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not link if no botkiller variants exist', () => {
|
||||||
|
const testSchema = {
|
||||||
|
'163': { // Crit-a-Cola
|
||||||
|
name: 'Crit-a-Cola',
|
||||||
|
slot: ItemSlot.Secondary,
|
||||||
|
tradable: true,
|
||||||
|
hasAustraliumVariant: false,
|
||||||
|
festiveVariant: null,
|
||||||
|
canKillstreakify: false,
|
||||||
|
botkillerVariants: null,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockResponseItems = [
|
||||||
|
{
|
||||||
|
item_class: 'tf_weapon_lunchbox_drink',
|
||||||
|
defindex: 163,
|
||||||
|
item_name: 'Crit-a-Cola',
|
||||||
|
item_type_name: 'Lunch Box',
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
linkBotkillerVariants(mockResponseItems, testSchema);
|
||||||
|
expect(testSchema['163'].botkillerVariants).toBeNull();
|
||||||
|
});
|
||||||
|
})
|
||||||
})
|
})
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tf2wikipricing",
|
"name": "tf2wikipricing",
|
||||||
"version": "0.4.1",
|
"version": "0.5.0",
|
||||||
"description": "Adds item pricing to the Team Fortress 2 wiki",
|
"description": "Adds item pricing to the Team Fortress 2 wiki",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@happy-dom/global-registrator": "^17.4.4",
|
"@happy-dom/global-registrator": "^17.4.4",
|
||||||
|
|||||||
@@ -187,9 +187,16 @@ async function inject() {
|
|||||||
|
|
||||||
var updateTime: Date | null = null;
|
var updateTime: Date | null = null;
|
||||||
|
|
||||||
|
enum PriceRowCategory {
|
||||||
|
None,
|
||||||
|
Festive,
|
||||||
|
Botkiller
|
||||||
|
}
|
||||||
|
|
||||||
interface PriceRow {
|
interface PriceRow {
|
||||||
quality: number
|
order: number
|
||||||
row: HTMLTableRowElement
|
row: HTMLTableRowElement
|
||||||
|
category: PriceRowCategory
|
||||||
}
|
}
|
||||||
var priceRows: PriceRow[]= [];
|
var priceRows: PriceRow[]= [];
|
||||||
|
|
||||||
@@ -214,7 +221,7 @@ async function inject() {
|
|||||||
const qualityName = itemQualities[quality as unknown as keyof typeof itemQualities].toString()
|
const qualityName = itemQualities[quality as unknown as keyof typeof itemQualities].toString()
|
||||||
const priceRow = createPriceRow(qualityName, data, keyPrice, locale)
|
const priceRow = createPriceRow(qualityName, data, keyPrice, locale)
|
||||||
|
|
||||||
priceRows.push({quality: quality, row: priceRow})
|
priceRows.push({order: quality == 6 ? -1 : quality, row: priceRow, category: PriceRowCategory.None})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Check item schema for Australium variant of current defindex
|
// Check item schema for Australium variant of current defindex
|
||||||
@@ -231,24 +238,128 @@ async function inject() {
|
|||||||
|
|
||||||
const priceRow = createPriceRow($T("Australium"), data, keyPrice, locale, "https://wiki.teamfortress.com/wiki/Australium_weapons")
|
const priceRow = createPriceRow($T("Australium"), data, keyPrice, locale, "https://wiki.teamfortress.com/wiki/Australium_weapons")
|
||||||
|
|
||||||
priceRows.push({quality: 99, row: priceRow})
|
priceRows.push({order: 99, row: priceRow, category: PriceRowCategory.None})
|
||||||
resolve()
|
resolve()
|
||||||
return
|
return
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var festiveHeadingRow: HTMLTableRowElement | null
|
||||||
|
// Check item schema for Festive variant of current defindex
|
||||||
|
if(itemSchema[itemIndex].festiveVariant != null) {
|
||||||
|
/// Create subheading
|
||||||
|
festiveHeadingRow = document.createElement("tr")
|
||||||
|
const festiveHeading = document.createElement("th")
|
||||||
|
festiveHeading.className = "infobox-subheader"
|
||||||
|
festiveHeading.colSpan = 2
|
||||||
|
festiveHeading.innerText = $T("Festive")
|
||||||
|
festiveHeading.style.fontSize = '1em';
|
||||||
|
festiveHeading.style.backgroundColor = '#F5C087';
|
||||||
|
festiveHeadingRow.appendChild(festiveHeading);
|
||||||
|
|
||||||
|
promises.push(new Promise(async (resolve) => {
|
||||||
|
logDebug(`Fetching price for Festive ${itemName}`)
|
||||||
|
var data: ItemPriceData | null
|
||||||
|
try {
|
||||||
|
data = await fetchPrice(token, `${itemSchema[itemIndex].festiveVariant};6`, currentTime);
|
||||||
|
updateTime = new Date(data.update)
|
||||||
|
} catch {
|
||||||
|
log(`Festive ${itemName} is unpriced or unavailable, skipping...`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const priceRow = createPriceRow($T("Unique"), data, keyPrice, locale)
|
||||||
|
|
||||||
|
priceRows.push({order: -1, row: priceRow, category: PriceRowCategory.Festive})
|
||||||
|
resolve()
|
||||||
|
return
|
||||||
|
}))
|
||||||
|
promises.push(new Promise(async (resolve) => {
|
||||||
|
logDebug(`Fetching price for Strange Festive ${itemName}`)
|
||||||
|
var data: ItemPriceData | null
|
||||||
|
try {
|
||||||
|
data = await fetchPrice(token, `${itemSchema[itemIndex].festiveVariant};11`, currentTime);
|
||||||
|
updateTime = new Date(data.update)
|
||||||
|
} catch {
|
||||||
|
log(`Strange Festive ${itemName} is unpriced or unavailable, skipping...`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const priceRow = createPriceRow($T("Strange"), data, keyPrice, locale)
|
||||||
|
|
||||||
|
priceRows.push({order: 11, row: priceRow, category: PriceRowCategory.Festive})
|
||||||
|
resolve()
|
||||||
|
return
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Silver Mk.I, Gold Mk.II, Rust, Blood, Carbonado, Diamond, Silver Mk.II, Gold Mk.II
|
||||||
|
const botkillerOrder = [
|
||||||
|
"Silver",
|
||||||
|
"Gold",
|
||||||
|
"Rust",
|
||||||
|
"Blood",
|
||||||
|
"Carbonado",
|
||||||
|
"Diamond",
|
||||||
|
"Silver Mk.II",
|
||||||
|
"Gold Mk.II",
|
||||||
|
]
|
||||||
|
var botKillerHeadingRow: HTMLTableRowElement | null
|
||||||
|
if(itemSchema[itemIndex].botkillerVariants != null && itemSchema[itemIndex].botkillerVariants.length > 0) {
|
||||||
|
/// Create subheading
|
||||||
|
botKillerHeadingRow = document.createElement("tr")
|
||||||
|
const festiveHeading = document.createElement("th")
|
||||||
|
festiveHeading.className = "infobox-subheader"
|
||||||
|
festiveHeading.colSpan = 2
|
||||||
|
festiveHeading.innerText = $T("Botkiller")
|
||||||
|
festiveHeading.style.fontSize = '1em';
|
||||||
|
festiveHeading.style.backgroundColor = '#F5C087';
|
||||||
|
botKillerHeadingRow.appendChild(festiveHeading);
|
||||||
|
|
||||||
|
itemSchema[itemIndex].botkillerVariants.map((variantIndex) => {
|
||||||
|
const itemName = itemSchema[variantIndex].name
|
||||||
|
// FIXME: variantName should match wiki display name
|
||||||
|
const variantName = itemName.includes('Mk.II') ? itemName.split(' ')[0] + ' Mk.II' : itemName.split(' ')[0]
|
||||||
|
promises.push(new Promise(async (resolve) => {
|
||||||
|
logDebug(`Fetching price for ${itemName}`)
|
||||||
|
var data: ItemPriceData | null
|
||||||
|
try {
|
||||||
|
data = await fetchPrice(token, `${variantIndex};11`, currentTime);
|
||||||
|
updateTime = new Date(data.update)
|
||||||
|
} catch {
|
||||||
|
log(`${itemName} is unpriced or unavailable, skipping...`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const priceRow = createPriceRow($T(variantName), data, keyPrice, 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})
|
||||||
|
resolve()
|
||||||
|
return
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if(botKillerHeadingRow) priceInfoboxHeadingRow.insertAdjacentElement('afterend', botKillerHeadingRow);
|
||||||
|
if(festiveHeadingRow) priceInfoboxHeadingRow.insertAdjacentElement('afterend', festiveHeadingRow);
|
||||||
|
|
||||||
Promise.all(promises).then(() => {
|
Promise.all(promises).then(() => {
|
||||||
priceRows.sort((a, b) => {
|
priceRows.sort((a, b) => {
|
||||||
// Sort 6 first always, then numerically
|
if (a.category != b.category) {
|
||||||
if (a.quality === 6) {
|
return a.category - b.category;
|
||||||
return -1;
|
|
||||||
} else if (b.quality === 6) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return a.quality == b.quality ? a.quality < b.quality ? -1 : 1 : 0;
|
|
||||||
}
|
}
|
||||||
|
return a.order - b.order;
|
||||||
}).reverse().forEach((element) => {
|
}).reverse().forEach((element) => {
|
||||||
priceInfoboxHeadingRow.insertAdjacentElement('afterend', element.row);
|
switch(element.category) {
|
||||||
|
case PriceRowCategory.None:
|
||||||
|
priceInfoboxHeadingRow.insertAdjacentElement('afterend', element.row);
|
||||||
|
break;
|
||||||
|
case PriceRowCategory.Festive:
|
||||||
|
festiveHeadingRow.insertAdjacentElement('afterend', element.row);
|
||||||
|
break;
|
||||||
|
case PriceRowCategory.Botkiller:
|
||||||
|
botKillerHeadingRow.insertAdjacentElement('afterend', element.row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
if(!updateTime || !(updateTime instanceof Date) || isNaN(+updateTime)) updateTime = new Date()
|
if(!updateTime || !(updateTime instanceof Date) || isNaN(+updateTime)) updateTime = new Date()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
declare function GM_fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
|
declare function GM_fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
|
||||||
import '../GM_fetch'
|
import '../GM_fetch'
|
||||||
|
import { logDebug } from '../utils/log'
|
||||||
|
|
||||||
async function getPricesToken(): Promise<string> {
|
async function getPricesToken(): Promise<string> {
|
||||||
return new Promise<any>((resolve, reject) => {
|
return new Promise<any>((resolve, reject) => {
|
||||||
@@ -37,7 +38,7 @@ class PricesResponse {
|
|||||||
* @returns {Promise<PricesResponse>} Object containing 'keys' and 'metal' prices
|
* @returns {Promise<PricesResponse>} Object containing 'keys' and 'metal' prices
|
||||||
* @throws When authentication fails or API returns non-200 status code
|
* @throws When authentication fails or API returns non-200 status code
|
||||||
*/
|
*/
|
||||||
async function priceUsingPricesTF(token: string, sku: string): Promise<PricesResponse> {
|
async function priceUsingPricesTF(token: string, sku: string, retries: number = 3): Promise<PricesResponse> {
|
||||||
// prices.tf
|
// prices.tf
|
||||||
// https://api2.prices.tf/prices/${sku}
|
// https://api2.prices.tf/prices/${sku}
|
||||||
// Authorization: Bearer ${token}
|
// Authorization: Bearer ${token}
|
||||||
@@ -65,11 +66,37 @@ async function priceUsingPricesTF(token: string, sku: string): Promise<PricesRes
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (response.status === 200) {
|
switch (response.status) {
|
||||||
const json = await response.json()
|
case 200:
|
||||||
resolve({ keys: json['sellKeys'], metal: json['sellHalfScrap'] / 18.0 })
|
const json = await response.json()
|
||||||
} else {
|
resolve({ keys: json['sellKeys'], metal: json['sellHalfScrap'] / 18.0 })
|
||||||
reject(response.status)
|
break;
|
||||||
|
case 404:
|
||||||
|
reject("Not found in prices.tf")
|
||||||
|
break;
|
||||||
|
case 429:
|
||||||
|
case 503:
|
||||||
|
// Happens if we send too many requests in a short period of time
|
||||||
|
// Retry after a few seconds
|
||||||
|
if(retries > 0) {
|
||||||
|
logDebug(`Cloudflare rate limit exceeded, trying again after 2 seconds, ${retries} retries left`)
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
try {
|
||||||
|
const retryResult = await priceUsingPricesTF(token, sku, retries - 1);
|
||||||
|
resolve(retryResult);
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reject("Cloudflare rate limit exceeded, stopping")
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Something went wrong
|
||||||
|
logDebug(`Received ${response.status} error while pricing ${sku}`)
|
||||||
|
logDebug(`${JSON.stringify(response.headers)}`)
|
||||||
|
reject("Unknown error")
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ export class ItemSchema {
|
|||||||
slot: ItemSlot,
|
slot: ItemSlot,
|
||||||
tradable: Boolean,
|
tradable: Boolean,
|
||||||
hasAustraliumVariant: Boolean,
|
hasAustraliumVariant: Boolean,
|
||||||
|
festiveVariant: number | null
|
||||||
|
botkillerVariants: Array<number> | null
|
||||||
canKillstreakify: Boolean
|
canKillstreakify: Boolean
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -70,6 +72,45 @@ export function getTradableStatusByName(schema: ItemSchema, name: string, exclud
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function linkFestiveVariants(items: Array<{item_class: string, defindex: number, item_name: string}>, schema: ItemSchema): void {
|
||||||
|
if(!schema) return
|
||||||
|
items.filter(item =>
|
||||||
|
item.item_class != null &&
|
||||||
|
item.item_class.startsWith('tf_weapon') &&
|
||||||
|
item.item_name.startsWith('Festive ')
|
||||||
|
).forEach(festive => {
|
||||||
|
const originalName = festive.item_name.slice(8); // "Festive " is 8 chars
|
||||||
|
const original = items.find(item => item.item_name === originalName && item.defindex > 30 && (item.defindex < 15000 || item.defindex >= 16000));
|
||||||
|
|
||||||
|
if (original) {
|
||||||
|
if(schema[original.defindex]) {
|
||||||
|
schema[original.defindex].festiveVariant = festive.defindex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function linkBotkillerVariants(items: Array<{item_class: string, defindex: number, item_name: string, item_type_name: string}>, schema: ItemSchema): void {
|
||||||
|
if(!schema) return
|
||||||
|
items.filter(item =>
|
||||||
|
item.item_class != null &&
|
||||||
|
item.item_class.startsWith('tf_weapon') &&
|
||||||
|
item.item_name.includes('Botkiller')
|
||||||
|
).forEach(botkiller => {
|
||||||
|
const originalName = botkiller.item_type_name
|
||||||
|
const original = items.find(item => item.item_name === originalName && item.defindex > 30 && (item.defindex < 15000 || item.defindex >= 16000));
|
||||||
|
if (original) {
|
||||||
|
if(schema[original.defindex]) {
|
||||||
|
if(schema[original.defindex].botkillerVariants == null) {
|
||||||
|
// init array
|
||||||
|
schema[original.defindex].botkillerVariants = new Array<number>()
|
||||||
|
}
|
||||||
|
schema[original.defindex].botkillerVariants.push(botkiller.defindex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function wipeSchema(): Promise<void> {
|
export async function wipeSchema(): Promise<void> {
|
||||||
await setStorageValue(storage_version, __VERSION__)
|
await setStorageValue(storage_version, __VERSION__)
|
||||||
await setStorageValue(storage_schema, null)
|
await setStorageValue(storage_schema, null)
|
||||||
@@ -149,6 +190,9 @@ export async function prepareSchema(): Promise<ItemSchema> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
linkFestiveVariants(responseItems, cacheItems)
|
||||||
|
linkBotkillerVariants(responseItems, cacheItems)
|
||||||
|
|
||||||
await setStorageValue(storage_schema, (cacheItems));
|
await setStorageValue(storage_schema, (cacheItems));
|
||||||
itemSchema = cacheItems
|
itemSchema = cacheItems
|
||||||
await setStorageValue(storage_version, __VERSION__);
|
await setStorageValue(storage_version, __VERSION__);
|
||||||
|
|||||||
@@ -22,5 +22,17 @@ module.exports = {
|
|||||||
"Strange": "Strange",
|
"Strange": "Strange",
|
||||||
"Collector's": "Collector's",
|
"Collector's": "Collector's",
|
||||||
"Haunted": "Haunted",
|
"Haunted": "Haunted",
|
||||||
"Australium": "Australium"
|
"Australium": "Australium",
|
||||||
|
"Festive": "Festive",
|
||||||
|
|
||||||
|
// Botkiller names, all sourced from TF2 wiki
|
||||||
|
"Botkiller": "Botkiller",
|
||||||
|
"Silver": "Silver",
|
||||||
|
"Gold": "Gold",
|
||||||
|
"Rust": "Rust",
|
||||||
|
"Blood": "Blood",
|
||||||
|
"Carbonado": "Carbonado",
|
||||||
|
"Diamond": "Diamond",
|
||||||
|
"Silver Mk.II": "Silver Mk.II",
|
||||||
|
"Gold Mk.II": "Gold Mk.II",
|
||||||
}
|
}
|
||||||
@@ -22,5 +22,17 @@ module.exports = {
|
|||||||
"Strange": "de Calidad Rara",
|
"Strange": "de Calidad Rara",
|
||||||
"Collector's": "de Coleccionista",
|
"Collector's": "de Coleccionista",
|
||||||
"Haunted": "de Calidad Embrujada",
|
"Haunted": "de Calidad Embrujada",
|
||||||
"Australium": "de Australium"
|
"Australium": "de Australium",
|
||||||
|
"Festive": "Festiva",
|
||||||
|
|
||||||
|
// Botkiller names, all sourced from TF2 wiki
|
||||||
|
"Botkiller": "Matabots",
|
||||||
|
"Silver": "Plata",
|
||||||
|
"Gold": "Oro",
|
||||||
|
"Rust": "Oxidado",
|
||||||
|
"Blood": "Sangriento",
|
||||||
|
"Carbonado": "Carbonado",
|
||||||
|
"Diamond": "Diamante",
|
||||||
|
"Silver Mk.II": "Plata Mk.II",
|
||||||
|
"Gold Mk.II": "Oro Mk.II",
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user