diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index efa0685..a351a5d 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -13,11 +13,14 @@ on: jobs: build: - runs-on: debian-latest + runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@v4.1.2 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest - name: Read package version uses: tyankatsu0105/read-package-version-actions@v1 id: version diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index c15a765..f7ef4f2 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -7,13 +7,16 @@ on: jobs: build: - runs-on: debian-latest + runs-on: ubuntu-latest outputs: version: ${{ steps.version.outputs.version }} steps: - name: Check out repository uses: actions/checkout@v4.1.2 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest - name: Read package version uses: tyankatsu0105/read-package-version-actions@v1 id: version @@ -31,7 +34,7 @@ jobs: name: tf2wikipricing path: dist/ deploy: - runs-on: debian-latest + runs-on: ubuntu-latest needs: build steps: - name: Download release artifacts diff --git a/bun.lockb b/bun.lockb index c86c55f..9f97e78 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index d042b39..a9c1fdc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "tf2wikipricing", "displayName": "TF2 Wiki Pricing", - "version": "0.8.1", + "version": "0.9.0", "description": "Adds item pricing to the Team Fortress 2 wiki", "author": "rapture.party", "devDependencies": { diff --git a/src/background/background.ts b/src/background/background.ts index 1798bd1..047e58b 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -27,61 +27,44 @@ chrome.runtime.onMessage.addListener( } ); -chrome.runtime.onMessage.addListener( - function(request, sender, sendResponse) { - if (request.contentScriptQuery == "getPricesTFToken") { - fetch('https://api2.prices.tf/auth/access', { - method: 'post', - headers: new Headers({ - 'Accept': 'application/json' - }) - }) - .then(response => response.json()) - .then(json => sendResponse(json['accessToken'])) - .catch(error => { - console.error("Failed to get access token", error); - }) - return true; - } - } -) - class PricesResponse { keys: number metal: number } -async function priceUsingPricesTF(token: string, sku: string, retries: number = 3): Promise { - const url = `https://api2.prices.tf/prices/${encodeURIComponent(sku)}`; +async function priceUsingPricedb(sku: string, retries: number = 3): Promise { + const url = `https://pricedb.io/api/item/${encodeURIComponent(sku)}`; const response = await fetch(url, { method: 'get', headers: { 'Accept': 'application/json', - 'Authorization': `Bearer ${token}`, } }) if (response.status === 404 && sku.includes(';') && !sku.includes(';uncraftable')) { const quality: number = parseInt(sku.split(';')[1], 10); if(quality === 6) { // Try uncraftable variant if unique weapon - return priceUsingPricesTF(token, sku + ';uncraftable'); + return priceUsingPricedb(sku + ';uncraftable'); } } - if(response.status === 503) { - // Happens if we send too many requests in a short period of time + if(response.status === 429) { + // Happens if we send too many requests (rate limit: 180 req/min) // Retry after a few seconds if(retries >= 0) { - console.log(`Cloudflare rate limit exceeded, trying again after 1 second, ${retries} retries left`) + console.log(`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 priceUsingPricedb(sku, retries - 1); } else { - throw new Error(`Cloudflare rate limit exceeded, stopping`) + throw new Error(`Rate limit exceeded, stopping`) } } + if (!response.ok) { + throw new Error(`Pricing request for ${sku} failed with status code: ${response.status}`); + } const data = await response.json(); const prices = new PricesResponse(); - prices.keys = data['sellKeys'] - prices.metal = data['sellHalfScrap'] / 18.0; + prices.keys = data.sell.keys + prices.metal = data.sell.metal return prices; } @@ -89,23 +72,14 @@ chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) { if (request.contentScriptQuery == "priceSKU") { const sku: string = request.sku - const service: string = request.service - const token: string = request.token - if(token === "" || !token) { - sendResponse(new Error("No token provided")) + priceUsingPricedb(sku) + .then((response) => sendResponse(response)) + .catch(error => { + console.error(`Received "${error}" error while pricing ${sku} using pricedb.io`) + sendResponse(null); return false; - } - switch (service) { - case "prices.tf": { - priceUsingPricesTF(token, sku) - .then((response) => sendResponse(response)) - .catch(error => { - sendResponse(error); - return false; - }) - } - } - return true; + }) } + return true; } -); \ No newline at end of file +); diff --git a/src/content/content.ts b/src/content/content.ts index 9d0afae..0c1f6ad 100644 --- a/src/content/content.ts +++ b/src/content/content.ts @@ -2,7 +2,6 @@ declare const __ENV_WEBEXTENSION: boolean; declare const __ENV_USERSCRIPT: boolean; import { logDebug, log, logError } from './utils/log' -import { getPricesToken } from './pricing/pricestf' import itemQualities from 'tf2-static-schema/static/qualities.json'; import { getItemIndexByName, getTradableStatusByDefindex, ItemSchema, ItemSlot, prepareSchema, wipeSchema } from './schemaService' import { $T, extractLocaleFromURL } from './utils/localization' @@ -16,7 +15,7 @@ let exchangeRates: ExchangeRates | null; let locale: string = 'en' -/** Exclude these from the pricelist. */ +/** Exclude these from pricelist. */ const excludedQualities = new Set([ 15, // Decorated 5, // Unusual @@ -118,7 +117,7 @@ async function inject() { /// Create buttons // Item infobox is a table with the following layout: - // + // // th.infobox-header (Item Name) // tr (3D item viewer/2D preview image) // tr (buy buttons if applicable) @@ -175,22 +174,11 @@ async function inject() { priceProgressRow.appendChild(priceProgressData); priceInfoboxHeadingRow.insertAdjacentElement('afterend', priceProgressRow); - let token: string | null; - // Steam Community Market // TODO: Change this to lazy-load, so that it doesn't make network requests when we have cached data. // var steamMarketResults = await getSteamResults(itemName) // logDebug(JSON.stringify(steamMarketResults)) - // Fetch prices.tf access token - // https://api2.prices.tf/auth/access - try { - // throw new Error('dont wanna') - token = await getPricesToken(); - } catch (err) { - log('Failed to get an access token for prices.tf: ' + err); - } - let updateTime: Date | null = null; enum PriceRowCategory { @@ -208,7 +196,24 @@ async function inject() { const priceRows: PriceRow[]= []; // Get current key price - const keyPrice = await fetchKeyPrice(token); + let keyPrice: ItemPriceData + try { + keyPrice = await fetchKeyPrice(); + } catch (error) { + logError('Failed to get a key price from pricedb.io: ' + error); + // Footer row + const row = document.createElement("tr"); + + const label = document.createElement("td"); + label.colSpan = 2; + label.style.fontSize = "85%"; + label.style.textAlign = "center"; + label.innerHTML = `Failed to get prices from pricedb.io. Service may be down.`; + row.appendChild(label); + priceProgressRow.insertAdjacentElement('afterend', row); + priceProgressRow.remove(); + return + } const currentTime = new Date() @@ -219,7 +224,7 @@ async function inject() { let data: ItemPriceData | null try { - data = await fetchPrice(token, itemIndex + ";" + quality, currentTime); + data = await fetchPrice(itemIndex + ";" + quality, currentTime); updateTime = new Date(data.update) } catch { log(`${qualifiedName} is unpriced or unavailable, skipping...`) @@ -233,7 +238,7 @@ async function inject() { // Check item schema for Australium variant of current defindex if(itemSchema[itemIndex].hasAustraliumVariant) { - promises.push(fetchPrice(token, `${itemIndex};11;australium`, currentTime).then(data => { + promises.push(fetchPrice(`${itemIndex};11;australium`, currentTime).then(data => { updateTime = new Date(data.update) logDebug(`Saving price for Australium ${itemName}`) @@ -261,7 +266,7 @@ async function inject() { festiveHeadingRow.style.display = 'none'; festiveHeadingRow.appendChild(festiveHeading); - promises.push(fetchPrice(token, `${itemSchema[itemIndex].festiveVariant};6`, currentTime).then(data => { + promises.push(fetchPrice(`${itemSchema[itemIndex].festiveVariant};6`, currentTime).then(data => { updateTime = new Date(data.update) logDebug(`Saving price for Festive ${itemName}`) @@ -273,7 +278,7 @@ async function inject() { logError(error) log(`Festive ${itemName} is unpriced or unavailable, skipping...`) })) - promises.push(fetchPrice(token, `${itemSchema[itemIndex].festiveVariant};11`, currentTime).then(data => { + promises.push(fetchPrice(`${itemSchema[itemIndex].festiveVariant};11`, currentTime).then(data => { updateTime = new Date(data.update) logDebug(`Saving price for Strange Festive ${itemName}`) @@ -293,36 +298,36 @@ async function inject() { itemSchema[itemIndex].slot == ItemSlot.Secondary || itemSchema[itemIndex].slot == ItemSlot.Melee) { - /// Create subheading - killstreakKitHeadingRow = document.createElement("tr") - const heading = document.createElement("th") - heading.className = "infobox-subheader" - heading.colSpan = 2 - heading.innerText = $T("Killstreak Kit") - heading.style.fontSize = '1em'; - heading.style.backgroundColor = '#F5C087'; - killstreakKitHeadingRow.style.display = 'none'; - killstreakKitHeadingRow.appendChild(heading); - [1,2,3].map((tier) => { - let kitIndex: number - switch (tier) { - default: - case 1: kitIndex = 6527; break; - case 2: kitIndex = 6523; break; - case 3: kitIndex = 6526; break; - } - promises.push(fetchPrice(token, `${kitIndex};6;uncraftable;kt-${tier};td-${itemIndex}`, currentTime).then(data => { - updateTime = new Date(data.update) - logDebug(`Saving price for ${itemName} Killstreak Kit Tier ${tier}`) + /// Create subheading + killstreakKitHeadingRow = document.createElement("tr") + const heading = document.createElement("th") + heading.className = "infobox-subheader" + heading.colSpan = 2 + heading.innerText = $T("Killstreak Kit") + heading.style.fontSize = '1em'; + heading.style.backgroundColor = '#F5C087'; + killstreakKitHeadingRow.style.display = 'none'; + killstreakKitHeadingRow.appendChild(heading); + [1,2,3].map((tier) => { + let kitIndex: number + switch (tier) { + default: + case 1: kitIndex = 6527; break; + case 2: kitIndex = 6523; break; + case 3: kitIndex = 6526; break; + } + promises.push(fetchPrice(`${kitIndex};6;uncraftable;kt-${tier};td-${itemIndex}`, currentTime).then(data => { + updateTime = new Date(data.update) + logDebug(`Saving price for ${itemName} Killstreak Kit Tier ${tier}`) - 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}) + }) + .catch((error) => { + logError(`Failed to fetch price for ${itemName} Killstreak Kit Tier ${tier}`, error) + })) }) - .catch((error) => { - logError(`Failed to fetch price for ${itemName} Killstreak Kit Tier ${tier}`, error) - })) - }) } // Silver Mk.I, Gold Mk.II, Rust, Blood, Carbonado, Diamond, Silver Mk.II, Gold Mk.II @@ -352,7 +357,7 @@ async function inject() { itemSchema[itemIndex].botkillerVariants.map((variantIndex) => { const itemName = itemSchema[variantIndex].name const variantName = itemName.includes('Mk.II') ? itemName.split(' ')[0] + ' Mk.II' : itemName.split(' ')[0] - promises.push(fetchPrice(token, `${variantIndex};11`, currentTime).then(data => { + promises.push(fetchPrice(`${variantIndex};11`, currentTime).then(data => { logDebug(`Saving price for ${itemName}`) updateTime = new Date(data.update) @@ -404,9 +409,9 @@ async function inject() { label.colSpan = 2; label.style.fontSize = "85%"; label.style.textAlign = "center"; - const updateText = $T("Updated %@.", locale).replace('%@', updateTime.toLocaleString(locale, { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', timeZoneName: 'short' })) + const updateText = $T("Updated %@", locale).replace('%@', updateTime.toLocaleString(locale, { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', timeZoneName: 'short' })) const attributionHeader = $T("Acknowledgements"); - const pricesAttribution = `prices.tf`; + const pricesAttribution = `pricedb.io`; const exchangeRateAttribution = `Rates By Exchange Rate API`; label.innerHTML = `${updateText}
${attributionHeader}
${pricesAttribution}
${exchangeRateAttribution}`; row.appendChild(label); @@ -443,4 +448,4 @@ prepareSchema() }) .catch((error) => { logError(error); -}) \ No newline at end of file +}) diff --git a/src/content/priceService.ts b/src/content/priceService.ts index fac3b1a..8b6901f 100644 --- a/src/content/priceService.ts +++ b/src/content/priceService.ts @@ -1,5 +1,5 @@ import { defindex_key, storage_priceprefix } from "./config" -import { priceUsingPricesTF } from "./pricing/pricestf" +import { priceUsingPricedb } from "./pricing/pricedb" import { getStorageValue, setStorageValue } from "./storage" import { logDebug } from "./utils/log" declare const __ENV_WEBEXTENSION: boolean; @@ -21,7 +21,7 @@ export class ItemPriceData { scmPrice: number toString(): string { - return `Price for ${this.sku}, fetched ${new Date(this.update)} (expires ${new Date(this.update + this.ttl)})\n` + + return `Price for ${this.sku}, fetched ${new Date(this.update)} (expires ${new Date(this.update + this.ttl)})\n` + JSON.stringify({ keys: this.keys, metal: this.metal, @@ -31,17 +31,16 @@ export class ItemPriceData { } -export async function fetchKeyPrice(token: string) { - return fetchPrice(token, `${defindex_key};6`, new Date(), 86400000) +export async function fetchKeyPrice() { + return fetchPrice(`${defindex_key};6`, new Date(), 86400000) } /** * Fetch a price for a given SKU, using cached values if available. - * @param token prices.tf access token. * @param update Date retrieved. * @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 { +export async function fetchPrice(sku: string, update: Date = new Date(), ttl: number = 30 * 60 * 1000): Promise { let data: ItemPriceData | null const cached: ItemPriceData = await getStorageValue(storage_priceprefix + sku, null) @@ -51,9 +50,6 @@ 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)) { logDebug(`Fetching price data for ${sku}`) - if(!token || token === '') { - throw new Error('No token provided') - } data = new ItemPriceData() data.sku = sku data.update = update.getTime() @@ -62,9 +58,9 @@ export async function fetchPrice(token: string, sku: string, update: Date = new try { let response: PricesResponse if(__ENV_USERSCRIPT) { - response = await priceUsingPricesTF(token, sku) + response = await priceUsingPricedb(sku) } else { - response = await chrome.runtime.sendMessage({contentScriptQuery: "priceSKU", service: "prices.tf", sku: sku, token: token}); + response = await chrome.runtime.sendMessage({contentScriptQuery: "priceSKU", sku: sku}); } if (!response || response instanceof Error) { throw new Error(`Bad response: ${response}`) @@ -72,7 +68,7 @@ export async function fetchPrice(token: string, sku: string, update: Date = new data.keys = response.keys data.metal = response.metal } catch (error) { - throw new Error(`Received "${error}" error while pricing ${sku} using prices.tf`) + throw new Error(`Received "${error}" error while pricing ${sku} using pricedb.io`) } if ('metal' in data && 'keys' in data) { @@ -82,4 +78,4 @@ export async function fetchPrice(token: string, sku: string, update: Date = new logDebug(`Using cached price data for ${sku}`) } return data -} + } diff --git a/src/content/pricing/pricedb.ts b/src/content/pricing/pricedb.ts new file mode 100644 index 0000000..d7419f0 --- /dev/null +++ b/src/content/pricing/pricedb.ts @@ -0,0 +1,67 @@ +import { fetchWrap } from '../fetchWrap' +import { logDebug, logError } from '../utils/log' +declare const __ENV_WEBEXTENSION: boolean; +declare const __ENV_USERSCRIPT: boolean; + +class PricesResponse { + keys: number + metal: number +} + +/** + * Fetches current price data for Team Fortress 2 items from pricedb.io. + * + * This function uses the pricedb.io API to fetch latest pricing data for a given item in keys and metal. + * No authentication is required. + * + * @example + * const price = await priceUsingPricedb('105;11'); + * console.log("Strange Brigade Helm price: ${price.keys} keys ${price.metal} metal") + * + * @returns {Promise} Object containing 'keys' and 'metal' prices + * @throws When API returns non-200 status code + */ +async function priceUsingPricedb(sku: string, retries: number = 3): Promise { + // pricedb.io + // https://pricedb.io/api/item/${sku} + try { + const response = await fetchWrap(`https://pricedb.io/api/item/${encodeURIComponent(sku)}`, { + method: 'get', + headers: { + 'Accept': 'application/json', + } + }) + if (response.status === 404 && sku.includes(';') && !sku.includes(';uncraftable')) { + const quality: number = parseInt(sku.split(';')[1], 10); + if(quality === 6) { + // Try uncraftable variant if unique weapon + return priceUsingPricedb(sku + ';uncraftable'); + } + } + if(response.status === 429) { + // Happens if we send too many requests (rate limit: 180 req/min) + // Retry after a few seconds + if(retries >= 0) { + logDebug(`Rate limit exceeded, trying again after 1 second, ${retries} retries left`) + await new Promise(resolve => setTimeout(resolve, 1000)); + return priceUsingPricedb(sku, retries - 1); + } else { + throw new Error(`Rate limit exceeded, stopping`) + } + } + if (!response.ok) { + throw new Error(`Pricing request for ${sku} failed with status code: ${response.status}`); + } + const data = await response.json(); + const prices = new PricesResponse(); + prices.keys = data.sell.keys + prices.metal = data.sell.metal + return prices; + } + catch(error) { + logError(`Failed to fetch prices from pricedb.io for item ${sku}`) + throw error; + } +} + +export { priceUsingPricedb, PricesResponse } diff --git a/src/content/pricing/pricestf.ts b/src/content/pricing/pricestf.ts deleted file mode 100644 index abeff72..0000000 --- a/src/content/pricing/pricestf.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { fetchWrap } from '../fetchWrap' -import { logDebug, logError } from '../utils/log' -declare const __ENV_WEBEXTENSION: boolean; -declare const __ENV_USERSCRIPT: boolean; - -async function getPricesToken(): Promise { - if(__ENV_USERSCRIPT) { - return new Promise((resolve, reject) => { - fetchWrap('https://api2.prices.tf/auth/access', { - method: 'post', - headers: new Headers({ - 'Accept': 'application/json' - }) - }) - .then((response) => { - if (response.status != 200) { - reject(response.status) - } - return response.json() - }) - .then((responseData) => resolve(responseData['accessToken'])) - }) - } else { - return chrome.runtime.sendMessage({contentScriptQuery: 'getPricesTFToken'}) - } -} - -class PricesResponse { - keys: number - metal: number -} - -/** - * Fetches the current price data for Team Fortress 2 items from prices.tf. - * - * This function authenticates with the prices.tf API using the provided token, - * and uses it to fetch the latest pricing data for the given item in keys and metal. - * - * @example - * const price = await priceUsingPricesTF(token, '105;11'); - * console.log("Strange Brigade Helm price: ${price.keys} keys ${price.metal} metal") - * - * @returns {Promise} Object containing 'keys' and 'metal' prices - * @throws When authentication fails or API returns non-200 status code - */ -async function priceUsingPricesTF(token: string, sku: string, retries: number = 3): Promise { - // prices.tf - // https://api2.prices.tf/prices/${sku} - // Authorization: Bearer ${token} - try { - const response = await fetchWrap(`https://api2.prices.tf/prices/${encodeURIComponent(sku)}`, { - method: 'get', - headers: { - 'Accept': 'application/json', - 'Authorization': `Bearer ${token}`, - } - }) - if (response.status === 404 && sku.includes(';') && !sku.includes(';uncraftable')) { - const quality: number = parseInt(sku.split(';')[1], 10); - if(quality === 6) { - // Try uncraftable variant if unique weapon - return priceUsingPricesTF(token, sku + ';uncraftable'); - } - } - if(response.status === 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 1 second, ${retries} retries left`) - await new Promise(resolve => setTimeout(resolve, 1000)); - return priceUsingPricesTF(token, sku, retries - 1); - } else { - throw new Error(`Cloudflare rate limit exceeded, stopping`) - } - } - if (!response.ok) { - throw new Error(`Pricing request for ${sku} failed with status code: ${response.status}`); - } - const data = await response.json(); - const prices = new PricesResponse(); - prices.keys = data['sellKeys'] - prices.metal = data['sellHalfScrap'] / 18.0; - return prices; - } - catch(error) { - logError(`Failed to fetch prices from prices.tf for item ${sku}`) - throw error; - } -} - -export { getPricesToken, priceUsingPricesTF, PricesResponse } \ No newline at end of file diff --git a/src/content/uiRenderer.ts b/src/content/uiRenderer.ts index 3f60e3d..829d6da 100644 --- a/src/content/uiRenderer.ts +++ b/src/content/uiRenderer.ts @@ -24,7 +24,7 @@ export function createPriceRow(qualityName: string, data: ItemPriceData, keyPric const priceLink = document.createElement("span"); let priceString: string = '' - if(data) { + if(data && data.keys != null && data.metal != null) { const gamePrice = formatPrice(data.keys, data.metal, keyPrice.metal, locale).trim() const realPriceUSD = convertTF2PriceToUSD(data.keys, data.metal, keyPrice.metal) diff --git a/src/content/utils/formatting.ts b/src/content/utils/formatting.ts index 0418a18..d1ba4a3 100644 --- a/src/content/utils/formatting.ts +++ b/src/content/utils/formatting.ts @@ -6,6 +6,8 @@ function toFixed(num: number, fixed: number) { } export function formatPrice(keys: number, metal: number, keyPrice: number, locale: string = 'en') { + if(keys == null) keys = 0 + if(metal == null) metal = 0 const formattedKeys = +(keys + (metal / keyPrice)).toFixed(2) let output: string = '' diff --git a/src/content/utils/localization.ts b/src/content/utils/localization.ts index 4706b8b..d3701b3 100644 --- a/src/content/utils/localization.ts +++ b/src/content/utils/localization.ts @@ -1,40 +1,40 @@ -import { logDebug } from "./log"; +import { logDebug, logError } from "./log"; const localizations: Record = { - 'en': require('../../strings/en'), // English - 'es': require('../../strings/es'), // Spanish - 'ja': require('../../strings/ja'), // Japanese - 'it': require('../../strings/it'), // Italian 'ar': require('../../strings/ar'), // Arabic 'cs': require('../../strings/cs'), // Czech 'da': require('../../strings/da'), // Danish - 'de': require('../../strings/de'), // German + 'nl': require('../../strings/nl'), // Dutch 'fi': require('../../strings/fi'), // Finnish 'fr': require('../../strings/fr'), // French + 'en': require('../../strings/en'), // English 'hu': require('../../strings/hu'), // Hungarian + 'it': require('../../strings/it'), // Italian + 'ja': require('../../strings/ja'), // Japanese + 'de': require('../../strings/de'), // German 'ko': require('../../strings/ko'), // Korean - 'nl': require('../../strings/nl'), // Dutch 'no': require('../../strings/no'), // Norwegian Bokmål 'pl': require('../../strings/pl'), // Polish 'pt': require('../../strings/pt'), // Portuguese - 'pt-BR': require('../../strings/pt-BR'), // Brazilian Portuguese + 'pt-br': require('../../strings/pt-BR'), // Brazilian Portuguese 'ro': require('../../strings/ro'), // Romanian + 'zh-hans': require('../../strings/zh-Hans'), // Simplified Chinese 'ru': require('../../strings/ru'), // Russian + 'es': require('../../strings/es'), // Spanish 'sv': require('../../strings/sv'), // Swedish + 'zh-hant': require('../../strings/zh-Hant'), // Traditional Chinese 'tr': require('../../strings/tr'), // Turkish - 'zh-Hans': require('../../strings/zh-Hans'), // Simplified Chinese - 'zh-Hant': require('../../strings/zh-Hant'), // Traditional Chinese } export function $T(s: string, locale?: Intl.LocalesArgument): string { const code = locale ? locale.toString() : extractLocaleFromURL(document.URL) if (code in localizations) { - const translation = localizations[code] as Record; + const translation = localizations[code.toLowerCase()] as Record; const result = translation[s] ?? s; logDebug(`Translating "${s}" to locale "${code}": ${result}`); return result; } - logDebug(`Untranslated string "${s}" in locale "${code}`); + logError(`Untranslated string "${s}" in locale "${code}"`); return s; } diff --git a/src/manifest.json b/src/manifest.json index 0d5fd6e..62ddfaf 100755 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,39 +1,39 @@ -{ - "name": EXTENSION_NAME, - "description": EXTENSION_DESCRIPTION, - "author": EXTENSION_AUTHOR, - "manifest_version": 3, - "version": EXTENSION_VERSION, - "permissions": [ - "storage", - "scripting" - ], - "host_permissions": [ - "https://wiki.teamfortress.com/wiki/*", - "https://*.prices.tf/*", - "https://open.er-api.com/*" - ], - "web_accessible_resources": [ - { - "resources": ["lib/style.css", "resources/*"], - "matches": ["https://wiki.teamfortress.com/*"] - } - ], - "content_scripts": [ - { - "matches": ["*://wiki.teamfortress.com/wiki/*"], - "run_at": "document_start", - "all_frames": true, - "css": ["lib/style.css"], - "js": ["content/content.js"] - } - ], - "background": { - "service_worker": "background/background.js", - "type": "module" - }, - "icons": { - "48": "icons/icon-48.png", - "96": "icons/icon-96.png" - } -} +{ + "name": EXTENSION_NAME, + "description": EXTENSION_DESCRIPTION, + "author": EXTENSION_AUTHOR, + "manifest_version": 3, + "version": EXTENSION_VERSION, + "permissions": [ + "storage", + "scripting" + ], + "host_permissions": [ + "https://wiki.teamfortress.com/wiki/*", + "https://*.pricedb.io/*", + "https://open.er-api.com/*" + ], + "web_accessible_resources": [ + { + "resources": ["lib/style.css", "resources/*"], + "matches": ["https://wiki.teamfortress.com/*"] + } + ], + "content_scripts": [ + { + "matches": ["*://wiki.teamfortress.com/wiki/*"], + "run_at": "document_start", + "all_frames": true, + "css": ["lib/style.css"], + "js": ["content/content.js"] + } + ], + "background": { + "service_worker": "background/background.js", + "type": "module" + }, + "icons": { + "48": "icons/icon-48.png", + "96": "icons/icon-96.png" + } +} diff --git a/src/strings/ar.js b/src/strings/ar.js index dc30ea5..ef17217 100644 --- a/src/strings/ar.js +++ b/src/strings/ar.js @@ -5,11 +5,11 @@ module.exports = { // Itembox header "Community Pricing": "Community Pricing", // Itembox footer - "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "تم التحديث يوم %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings - "Data unavailable": "Data unavailable", // sourced from AppleGlot + "Data unavailable": "البيانات غير متوفرة", // sourced from AppleGlot "%@ ref": "%@ ref", "%@ key": "%@ key", "%@ keys": "%@ keys", diff --git a/src/strings/cs.js b/src/strings/cs.js index dc30ea5..e63b053 100644 --- a/src/strings/cs.js +++ b/src/strings/cs.js @@ -5,11 +5,11 @@ module.exports = { // Itembox header "Community Pricing": "Community Pricing", // Itembox footer - "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Aktualizováno %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings - "Data unavailable": "Data unavailable", // sourced from AppleGlot + "Data unavailable": "Data nejsou k dispozici", // sourced from AppleGlot "%@ ref": "%@ ref", "%@ key": "%@ key", "%@ keys": "%@ keys", diff --git a/src/strings/da.js b/src/strings/da.js index dc30ea5..581f524 100644 --- a/src/strings/da.js +++ b/src/strings/da.js @@ -5,11 +5,11 @@ module.exports = { // Itembox header "Community Pricing": "Community Pricing", // Itembox footer - "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Opdateret %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings - "Data unavailable": "Data unavailable", // sourced from AppleGlot + "Data unavailable": "Data utilgængelige", // sourced from AppleGlot "%@ ref": "%@ ref", "%@ key": "%@ key", "%@ keys": "%@ keys", diff --git a/src/strings/de.js b/src/strings/de.js index dc30ea5..6126e08 100644 --- a/src/strings/de.js +++ b/src/strings/de.js @@ -5,11 +5,11 @@ module.exports = { // Itembox header "Community Pricing": "Community Pricing", // Itembox footer - "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Updated %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings - "Data unavailable": "Data unavailable", // sourced from AppleGlot + "Data unavailable": "Daten nicht verfügbar", // sourced from AppleGlot "%@ ref": "%@ ref", "%@ key": "%@ key", "%@ keys": "%@ keys", diff --git a/src/strings/en.js b/src/strings/en.js index dc30ea5..f4caea0 100644 --- a/src/strings/en.js +++ b/src/strings/en.js @@ -5,7 +5,7 @@ module.exports = { // Itembox header "Community Pricing": "Community Pricing", // Itembox footer - "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Updated %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings diff --git a/src/strings/es.js b/src/strings/es.js index d8ed300..da4cf09 100644 --- a/src/strings/es.js +++ b/src/strings/es.js @@ -5,7 +5,7 @@ module.exports = { // Itembox header "Community Pricing": "Precios de la comunidad", // Itembox footer - "Updated %@.": "Actualizado %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Actualizado %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Agradecimientos", // sourced from AppleGlot // Price strings diff --git a/src/strings/fi.js b/src/strings/fi.js index dc30ea5..d61a5f1 100644 --- a/src/strings/fi.js +++ b/src/strings/fi.js @@ -5,11 +5,11 @@ module.exports = { // Itembox header "Community Pricing": "Community Pricing", // Itembox footer - "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Updated %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings - "Data unavailable": "Data unavailable", // sourced from AppleGlot + "Data unavailable": "Dataa ei saatavilla", // sourced from AppleGlot "%@ ref": "%@ ref", "%@ key": "%@ key", "%@ keys": "%@ keys", diff --git a/src/strings/fr.js b/src/strings/fr.js index dc30ea5..f79bde6 100644 --- a/src/strings/fr.js +++ b/src/strings/fr.js @@ -5,11 +5,11 @@ module.exports = { // Itembox header "Community Pricing": "Community Pricing", // Itembox footer - "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Updated %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings - "Data unavailable": "Data unavailable", // sourced from AppleGlot + "Data unavailable": "Données non disponibles", // sourced from AppleGlot "%@ ref": "%@ ref", "%@ key": "%@ key", "%@ keys": "%@ keys", diff --git a/src/strings/hu.js b/src/strings/hu.js index dc30ea5..b4193ed 100644 --- a/src/strings/hu.js +++ b/src/strings/hu.js @@ -5,11 +5,11 @@ module.exports = { // Itembox header "Community Pricing": "Community Pricing", // Itembox footer - "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Updated %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings - "Data unavailable": "Data unavailable", // sourced from AppleGlot + "Data unavailable": "Adat nem érhető el", // sourced from AppleGlot "%@ ref": "%@ ref", "%@ key": "%@ key", "%@ keys": "%@ keys", diff --git a/src/strings/it.js b/src/strings/it.js index 734302e..1ff28cc 100644 --- a/src/strings/it.js +++ b/src/strings/it.js @@ -5,11 +5,11 @@ module.exports = { // Itembox header "Community Pricing": "Prezzo Comunitario", // Itembox footer - "Updated %@.": "Aggiornato il %@.", + "Updated %@": "Aggiornato il %@", "Acknowledgements": "Note legali", // sourced from AppleGlot // Price strings - "Data unavailable": "Data unavailable", // sourced from AppleGlot + "Data unavailable": "Dati non disponibili", // sourced from AppleGlot "%@ ref": "%@ raf", "%@ key": "%@ chiave", "%@ keys": "%@ chiavi", diff --git a/src/strings/ja.js b/src/strings/ja.js index ffcc18d..87314ed 100644 --- a/src/strings/ja.js +++ b/src/strings/ja.js @@ -5,7 +5,7 @@ module.exports = { // Itembox header "Community Pricing": "共同体価格", // Itembox footer - "Updated %@.": "アップデート: %@。", // %@ is a date string, sourced from AppleGlot + "Updated %@": "アップデート: %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "謝辞", // sourced from AppleGlot // Price strings diff --git a/src/strings/ko.js b/src/strings/ko.js index dc30ea5..c9f4851 100644 --- a/src/strings/ko.js +++ b/src/strings/ko.js @@ -5,11 +5,11 @@ module.exports = { // Itembox header "Community Pricing": "Community Pricing", // Itembox footer - "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Updated %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings - "Data unavailable": "Data unavailable", // sourced from AppleGlot + "Data unavailable": "데이터를 사용할 수 없음", // sourced from AppleGlot "%@ ref": "%@ ref", "%@ key": "%@ key", "%@ keys": "%@ keys", diff --git a/src/strings/nl.js b/src/strings/nl.js index dc30ea5..12f9f1a 100644 --- a/src/strings/nl.js +++ b/src/strings/nl.js @@ -5,11 +5,11 @@ module.exports = { // Itembox header "Community Pricing": "Community Pricing", // Itembox footer - "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Bijgewerkt %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings - "Data unavailable": "Data unavailable", // sourced from AppleGlot + "Data unavailable": "Gegevens niet beschikbaar", // sourced from AppleGlot "%@ ref": "%@ ref", "%@ key": "%@ key", "%@ keys": "%@ keys", diff --git a/src/strings/no.js b/src/strings/no.js index dc30ea5..0c72348 100644 --- a/src/strings/no.js +++ b/src/strings/no.js @@ -5,11 +5,11 @@ module.exports = { // Itembox header "Community Pricing": "Community Pricing", // Itembox footer - "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Updated %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings - "Data unavailable": "Data unavailable", // sourced from AppleGlot + "Data unavailable": "Data ikke tilgjengelig", // sourced from AppleGlot "%@ ref": "%@ ref", "%@ key": "%@ key", "%@ keys": "%@ keys", diff --git a/src/strings/pl.js b/src/strings/pl.js index dc30ea5..7f9f0f9 100644 --- a/src/strings/pl.js +++ b/src/strings/pl.js @@ -5,11 +5,11 @@ module.exports = { // Itembox header "Community Pricing": "Community Pricing", // Itembox footer - "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Updated %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings - "Data unavailable": "Data unavailable", // sourced from AppleGlot + "Data unavailable": "Dane niedostępne", // sourced from AppleGlot "%@ ref": "%@ ref", "%@ key": "%@ key", "%@ keys": "%@ keys", diff --git a/src/strings/pt-BR.js b/src/strings/pt-BR.js index dc30ea5..cc1483f 100644 --- a/src/strings/pt-BR.js +++ b/src/strings/pt-BR.js @@ -5,11 +5,11 @@ module.exports = { // Itembox header "Community Pricing": "Community Pricing", // Itembox footer - "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Atualizado: %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings - "Data unavailable": "Data unavailable", // sourced from AppleGlot + "Data unavailable": "Dados indisponíveis", // sourced from AppleGlot "%@ ref": "%@ ref", "%@ key": "%@ key", "%@ keys": "%@ keys", diff --git a/src/strings/pt.js b/src/strings/pt.js index dc30ea5..afdc497 100644 --- a/src/strings/pt.js +++ b/src/strings/pt.js @@ -5,11 +5,11 @@ module.exports = { // Itembox header "Community Pricing": "Community Pricing", // Itembox footer - "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Updated %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings - "Data unavailable": "Data unavailable", // sourced from AppleGlot + "Data unavailable": "Dados indisponíveis", // sourced from AppleGlot "%@ ref": "%@ ref", "%@ key": "%@ key", "%@ keys": "%@ keys", diff --git a/src/strings/ro.js b/src/strings/ro.js index dc30ea5..bc0de7d 100644 --- a/src/strings/ro.js +++ b/src/strings/ro.js @@ -5,11 +5,11 @@ module.exports = { // Itembox header "Community Pricing": "Community Pricing", // Itembox footer - "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Updated %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings - "Data unavailable": "Data unavailable", // sourced from AppleGlot + "Data unavailable": "Date indisponibile", // sourced from AppleGlot "%@ ref": "%@ ref", "%@ key": "%@ key", "%@ keys": "%@ keys", diff --git a/src/strings/ru.js b/src/strings/ru.js index 245f637..dd7f631 100644 --- a/src/strings/ru.js +++ b/src/strings/ru.js @@ -5,7 +5,7 @@ module.exports = { // Itembox header "Community Pricing": "Цены сообщества", // Itembox footer - "Updated %@.": "Обновлено %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Обновлено %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Уведомления", // sourced from AppleGlot // Price strings diff --git a/src/strings/sv.js b/src/strings/sv.js index dc30ea5..6d1054c 100644 --- a/src/strings/sv.js +++ b/src/strings/sv.js @@ -5,11 +5,11 @@ module.exports = { // Itembox header "Community Pricing": "Community Pricing", // Itembox footer - "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Updated %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings - "Data unavailable": "Data unavailable", // sourced from AppleGlot + "Data unavailable": "Data ej tillgängliga", // sourced from AppleGlot "%@ ref": "%@ ref", "%@ key": "%@ key", "%@ keys": "%@ keys", diff --git a/src/strings/tr.js b/src/strings/tr.js index dc30ea5..e67aab3 100644 --- a/src/strings/tr.js +++ b/src/strings/tr.js @@ -5,11 +5,11 @@ module.exports = { // Itembox header "Community Pricing": "Community Pricing", // Itembox footer - "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Updated %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings - "Data unavailable": "Data unavailable", // sourced from AppleGlot + "Data unavailable": "Veri yok", // sourced from AppleGlot "%@ ref": "%@ ref", "%@ key": "%@ key", "%@ keys": "%@ keys", diff --git a/src/strings/zh-Hans.js b/src/strings/zh-Hans.js index dc30ea5..fc33cd7 100644 --- a/src/strings/zh-Hans.js +++ b/src/strings/zh-Hans.js @@ -5,11 +5,11 @@ module.exports = { // Itembox header "Community Pricing": "Community Pricing", // Itembox footer - "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Updated %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings - "Data unavailable": "Data unavailable", // sourced from AppleGlot + "Data unavailable": "数据不可用", // sourced from AppleGlot "%@ ref": "%@ ref", "%@ key": "%@ key", "%@ keys": "%@ keys", diff --git a/src/strings/zh-Hant.js b/src/strings/zh-Hant.js index dc30ea5..10cbe33 100644 --- a/src/strings/zh-Hant.js +++ b/src/strings/zh-Hant.js @@ -5,11 +5,11 @@ module.exports = { // Itembox header "Community Pricing": "Community Pricing", // Itembox footer - "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Updated %@": "Updated %@", // %@ is a date string, sourced from AppleGlot "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings - "Data unavailable": "Data unavailable", // sourced from AppleGlot + "Data unavailable": "無法使用資料", // sourced from AppleGlot "%@ ref": "%@ ref", "%@ key": "%@ key", "%@ keys": "%@ keys", diff --git a/src/userscript_header.js b/src/userscript_header.js index 11d0f31..2702446 100644 --- a/src/userscript_header.js +++ b/src/userscript_header.js @@ -8,8 +8,8 @@ // @inject-into content // @connect steamcommunity.com // @domain steamcommunity.com -// @connect prices.tf -// @domain prices.tf +// @connect pricedb.io +// @domain pricedb.io // @connect open.er-api.com // @domain open.er-api.com // @grant GM.setValue @@ -18,4 +18,4 @@ // @grant GM_getValue // @grant GM.xmlhttpRequest // @grant GM_xmlhttpRequest -// ==/UserScript== \ No newline at end of file +// ==/UserScript== diff --git a/webpack.config.js b/webpack.config.js index 151bb28..8117b8d 100755 --- a/webpack.config.js +++ b/webpack.config.js @@ -22,9 +22,10 @@ const defines = { __VERSION__: JSON.stringify(package.version), } -module.exports = [ +module.exports = (env, argv) => [ // WebExtension { + devtool: argv.mode === 'production' ? false : 'source-map', entry: { content: './src/content/content.ts', background: './src/background/background.ts',