feat: fetch currency exchange rate

exchange rates are fetched at script startup, and at most once daily, then cached. attribution is added as per ExchangeRate-API's requirements
This commit is contained in:
xenticore
2025-04-16 18:23:07 -04:00
parent 84daf5b2d6
commit fcf077c877
3 changed files with 73 additions and 5 deletions

View File

@@ -3,6 +3,9 @@ export const storage_lastUpdateTime = 'tf2wikipricing_lastUpdate';
export const storage_schema = 'tf2wikipricing_schema';
export const storage_version = 'tf2wikipricing_version';
export const storage_priceprefix = 'tf2wikipricing_sku_';
export const storage_exchangerates = 'tf2wikipricing_exchangerates';
export const storage_exchangerates_update = 'tf2wikipricing_exchangerates_update';
export const storage_exchangerates_next = 'tf2wikipricing_exchangerates_next';
export const conversion_ref_usd = 0.0265;
export const defindex_key = 5021;
export const defindex_metal_refined = 5002;

View File

@@ -9,7 +9,9 @@ import { fetchPrice, fetchKeyPrice, ItemPriceData } from './priceService'
import { createPriceRow, createStoreButton } from './uiRenderer'
import { findFirstElement, findFirstChildElement } from './utils/dom'
import { extractPageTitleFromURL } from './utils/url';
import { ExchangeRates, prepareExchangeRates } from './exchangeRateService';
var itemSchema: ItemSchema | null;
var exchangeRates: ExchangeRates | null;
var locale: string = 'en'
@@ -425,7 +427,8 @@ async function inject() {
label.style.fontSize = "85%";
const updateText = $T("Updated %@.", locale).replace('%@', updateTime.toLocaleString(locale, { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', timeZoneName: 'short' }))
const attributionText = $T("Trade prices sourced from %@. Currency conversions are approximate.", locale).replace('%@', '<a rel="nofollow" class="external text" href="https://prices.tf">prices.tf</a>');
label.innerHTML = `${updateText}<br>${attributionText}`;
const exchangeRateAttribution = `<a rel="nofollow" class="external text" href="https://www.exchangerate-api.com">Rates By Exchange Rate API</a>.`;
label.innerHTML = `${updateText}<br>${attributionText}<br>${exchangeRateAttribution}`;
row.appendChild(label);
priceProgressRow.insertAdjacentElement('afterend', row);
@@ -440,15 +443,22 @@ function addStyles() {
style.innerHTML = styleCss;
}
prepareSchema().then(function (schema) {
prepareSchema()
.then(schema => {
itemSchema = schema;
if (!itemSchema) {
logError("No item schema ready, exiting.");
wipeSchema(); // FIXME: ugly hack. requires additional page reload. if prepareSchema returns null, we should handle it properly
return;
throw new Error("No item schema ready");
}
})
.then(prepareExchangeRates)
.then(rates => exchangeRates = rates)
.then(() => {
locale = extractLocaleFromURL(document.URL)
addStyles();
inject();
// TODO: Purge expired price data
});
})
.catch((error) => {
logError(error);
})

View File

@@ -0,0 +1,55 @@
import { getStorageValue, setStorageValue } from './storage'
import { logDebug, log, logError } from './utils/log'
import { storage_exchangerates, storage_exchangerates_next, storage_exchangerates_update } from './config'
declare function GM_fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
import './GM_fetch'
export interface ExchangeRates {
[key: string]: number;
}
export async function wipeExchangeRates(): Promise<void> {
await setStorageValue(storage_exchangerates, null)
await setStorageValue(storage_exchangerates_update, new Date().toISOString())
await setStorageValue(storage_exchangerates_next, new Date().toISOString())
logDebug(`Exchange rates wiped`)
}
export async function prepareExchangeRates(): Promise<ExchangeRates> {
var needsUpdate: Boolean = false
var rates: ExchangeRates | null = null
rates = await getStorageValue(storage_exchangerates, null);
const update = await getStorageValue(storage_exchangerates_update, null)
const nextUpdate = await getStorageValue(storage_exchangerates_next, null)
if (update && nextUpdate) {
const lastUpdateTime = new Date(update);
const nextUpdateTime = new Date(nextUpdate);
log(`Exchange rates updated at ${lastUpdateTime}`);
if (rates == null || Object.keys(rates).length === 0 || lastUpdateTime.getTime() > nextUpdateTime.getTime()) {
needsUpdate = true
}
} else {
needsUpdate = true
}
if(needsUpdate) {
log("Exchange rates out of Date. Rebuilding...");
const url = "https://open.er-api.com/v6/latest/USD"
const response = await GM_fetch(url);
if (response.ok) {
await setStorageValue(storage_exchangerates_update, new Date().toISOString())
var json = await response.json()
if(json != null){
rates = json['rates']
await setStorageValue(storage_exchangerates, rates)
await setStorageValue(storage_exchangerates_next, json['time_next_update_utc'])
}
logDebug(`Exchange rates updated at ${new Date()}`)
} else {
logError(`Failed to fetch exchange rates. Status code: ${response.status}`, response)
}
}
return rates
}