import { getStorageValue, setStorageValue } from './storage' import { logDebug, log, logError } from './utils/log' import './config' declare function GM_fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise import './GM_fetch' import { storage_version, storage_schema, storage_lastUpdateTime } from './config' import Australiums from '../resources/australiums.json' const semver = require('semver') export function checkAustraliumVariant(defindex: number): boolean { return Object.prototype.hasOwnProperty.call(Australiums, defindex.toString()); } export declare const __VERSION__: string; function isDateAfterOneDay(date1: Date, date2: Date): boolean { const diff = date2.getTime() - date1.getTime(); const diffDays = Math.round(diff / (1000 * 3600 * 24)); return diffDays > 1; } export enum ItemSlot { Primary = "primary", Secondary = "secondary", Melee = "melee", PDA = "pda", PDA2 = "pda2", Building = "building", Misc = "misc", Special = "special", Taunt = "taunt", Tool = "tool", } export class ItemSchema { [key: string]: { name: string, slot: ItemSlot, tradable: Boolean, hasAustraliumVariant: Boolean, festiveVariant: number | null botkillerVariants: Array | null canKillstreakify: Boolean }; } export function getItemIndexByName(schema: ItemSchema, name: string, excludeStock: Boolean = true, excludeDecorated: Boolean = true) { for (const [defindex, value] of Object.entries(schema)) { if (value['name'] == name) { const index = parseInt(defindex) if(excludeStock && index <= 30) continue if(excludeDecorated && (index >= 15000 && index < 16000)) continue return index } } return null } export function getTradableStatusByDefindex(schema: ItemSchema, defindex: number) { return schema[defindex.toString()].tradable } export function getTradableStatusByName(schema: ItemSchema, name: string, excludeStock: Boolean = true, excludeDecorated = true,) { for (const [defindex, value] of Object.entries(schema)) { if (value['name'] == name) { const index = parseInt(defindex) if(excludeStock && index <= 30) continue if(excludeDecorated && (index >= 15000 && index < 16000)) continue return value.tradable } } 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() } schema[original.defindex].botkillerVariants.push(botkiller.defindex) } } }); } export async function wipeSchema(): Promise { await setStorageValue(storage_version, __VERSION__) await setStorageValue(storage_schema, null) await setStorageValue(storage_lastUpdateTime, new Date().toISOString()) logDebug(`Schema wiped`) } export async function prepareSchema(): Promise { let needsUpdate: boolean = false let itemSchema: ItemSchema | null = null const storedVersion: string | null = await getStorageValue(storage_version, null) if(!storedVersion || !semver.valid(storedVersion)) { log(`Cache is from an unknown version of the extension. Updating for version ${__VERSION__}`); needsUpdate = true } else if(semver.valid(storedVersion) && semver.lt(storedVersion, __VERSION__)) { log(`Cache is from a previous version (${storedVersion}) of the extension. Updating for version ${__VERSION__}`); needsUpdate = true } else { log(`Cache is from current version (${storedVersion}) of the extension.`); itemSchema = await getStorageValue(storage_schema, null); } const update = await getStorageValue(storage_lastUpdateTime, null) if (update) { const lastUpdateTime = new Date(update); log(`Item schema updated at ${lastUpdateTime}`); if (itemSchema == null || Object.keys(itemSchema).length === 0 || isDateAfterOneDay(lastUpdateTime, new Date())) { needsUpdate = true } } if(needsUpdate) { log("Item Schema out of Date. Rebuilding..."); const url = "https://raw.githubusercontent.com/danocmx/node-tf2-static-schema/master/static/items.json" const response = await GM_fetch(url); if (response.ok) { await setStorageValue(storage_lastUpdateTime, new Date().getTime()); const cacheItems = {} const responseItems: any[] = await response.json() // We want to keep the keys `defindex`, `item_name`, and `attributes` responseItems.forEach((item: any) => { const defindex: number = item['defindex'] let tradable: boolean = true try { if(item['attributes'] != null) { if(item['attributes'].find((attribute: {}) => (attribute as any)['class'] == "cannot_trade")) { tradable = false } } } catch(error) { logError(error) log(item) } let canKillstreakify: boolean = false try { if(item['capabilities'] != null) { if(item['capabilities']['can_killstreakify'] != null && item['capabilities']['can_killstreakify'] == true) { canKillstreakify = true } } } catch(error) { logError(error) log(item) } (cacheItems as any)[defindex.toString()] = { "name": item['item_name'], "slot": item['item_slot'], "tradable": tradable, "canKillstreakify": canKillstreakify, "hasAustraliumVariant": checkAustraliumVariant(defindex) } }); linkFestiveVariants(responseItems, cacheItems) linkBotkillerVariants(responseItems, cacheItems) await setStorageValue(storage_schema, (cacheItems)); itemSchema = cacheItems await setStorageValue(storage_version, __VERSION__); logDebug(`Item schema updated at ${new Date()}`) } else { logError("Could not fetch item schema."); } } return itemSchema }