You've already forked tf2wikipricing
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
493176c9d5
|
|||
|
dcbde81741
|
|||
|
874b3a643e
|
@@ -13,11 +13,14 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: debian-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository
|
- name: Check out repository
|
||||||
uses: actions/checkout@v4.1.2
|
uses: actions/checkout@v4.1.2
|
||||||
|
- uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version: latest
|
||||||
- name: Read package version
|
- name: Read package version
|
||||||
uses: tyankatsu0105/read-package-version-actions@v1
|
uses: tyankatsu0105/read-package-version-actions@v1
|
||||||
id: version
|
id: version
|
||||||
|
|||||||
@@ -7,13 +7,16 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: debian-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ steps.version.outputs.version }}
|
version: ${{ steps.version.outputs.version }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository
|
- name: Check out repository
|
||||||
uses: actions/checkout@v4.1.2
|
uses: actions/checkout@v4.1.2
|
||||||
|
- uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version: latest
|
||||||
- name: Read package version
|
- name: Read package version
|
||||||
uses: tyankatsu0105/read-package-version-actions@v1
|
uses: tyankatsu0105/read-package-version-actions@v1
|
||||||
id: version
|
id: version
|
||||||
@@ -31,9 +34,12 @@ jobs:
|
|||||||
name: tf2wikipricing
|
name: tf2wikipricing
|
||||||
path: dist/
|
path: dist/
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: debian-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build
|
needs: build
|
||||||
steps:
|
steps:
|
||||||
|
- uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version: latest
|
||||||
- name: Download release artifacts
|
- name: Download release artifacts
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -1,22 +1,21 @@
|
|||||||
import { describe, expect, test, jest, mock, beforeEach } from "bun:test";
|
import { describe, expect, test, jest, mock, beforeEach } from "bun:test";
|
||||||
import { ItemPriceData, fetchPrice, fetchKeyPrice } from '../src/content/priceService'
|
import { ItemPriceData, fetchPrice, fetchKeyPrice } from '../src/content/priceService'
|
||||||
import { PricesResponse, priceUsingPricesTF } from '../src/content/pricing/pricestf'
|
import { PricesResponse, priceUsingPricedb } from '../src/content/pricing/pricedb'
|
||||||
import { getStorageValue, setStorageValue } from '../src/content/storage'
|
import { getStorageValue, setStorageValue } from '../src/content/storage'
|
||||||
import { defindex_key } from "../src/content/config";
|
import { defindex_key } from "../src/content/config";
|
||||||
|
|
||||||
// Mock the storage module
|
// Mock storage module
|
||||||
mock.module('../src/content/storage', () => ({
|
mock.module('../src/content/storage', () => ({
|
||||||
getStorageValue: jest.fn(),
|
getStorageValue: jest.fn(),
|
||||||
setStorageValue: jest.fn()
|
setStorageValue: jest.fn()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Mock the pricing module
|
// Mock pricing module
|
||||||
mock.module('../src/content/pricing/pricestf', () => ({
|
mock.module('../src/content/pricing/pricedb', () => ({
|
||||||
priceUsingPricesTF: jest.fn()
|
priceUsingPricedb: jest.fn()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
describe('Price Service', () => {
|
describe('Price Service', () => {
|
||||||
const mockToken = 'test-token'
|
|
||||||
const mockDefIndex = 105 // Brigade Helm
|
const mockDefIndex = 105 // Brigade Helm
|
||||||
const mockQuality = 11 // Strange
|
const mockQuality = 11 // Strange
|
||||||
const mockSku = `${mockDefIndex};${mockQuality}`
|
const mockSku = `${mockDefIndex};${mockQuality}`
|
||||||
@@ -49,7 +48,7 @@ describe('Price Service', () => {
|
|||||||
test('fetchPrice returns cached data if available and not expired', async () => {
|
test('fetchPrice returns cached data if available and not expired', async () => {
|
||||||
(getStorageValue as jest.Mock).mockResolvedValue(mockCachedData)
|
(getStorageValue as jest.Mock).mockResolvedValue(mockCachedData)
|
||||||
|
|
||||||
const result = await fetchPrice(mockToken, mockDefIndex + ";" + mockQuality)
|
const result = await fetchPrice(mockDefIndex + ";" + mockQuality)
|
||||||
|
|
||||||
expect(getStorageValue).toHaveBeenCalledWith(expect.stringContaining(mockSku), null)
|
expect(getStorageValue).toHaveBeenCalledWith(expect.stringContaining(mockSku), null)
|
||||||
expect(result).toEqual(mockCachedData)
|
expect(result).toEqual(mockCachedData)
|
||||||
@@ -58,34 +57,30 @@ describe('Price Service', () => {
|
|||||||
test('fetchPrice fetches new data when cache is expired', async () => {
|
test('fetchPrice fetches new data when cache is expired', async () => {
|
||||||
const expiredCache: ItemPriceData = { ...mockCachedData, update: new Date(Date.now() - 2 * mockTtl).getTime() };
|
const expiredCache: ItemPriceData = { ...mockCachedData, update: new Date(Date.now() - 2 * mockTtl).getTime() };
|
||||||
(getStorageValue as jest.Mock).mockResolvedValue(expiredCache);
|
(getStorageValue as jest.Mock).mockResolvedValue(expiredCache);
|
||||||
(priceUsingPricesTF as jest.Mock).mockResolvedValue(mockPriceResponse);
|
(priceUsingPricedb as jest.Mock).mockResolvedValue(mockPriceResponse);
|
||||||
(chrome.runtime.sendMessage as jest.Fn).mockImplementation(() => mockPriceResponse);
|
(chrome.runtime.sendMessage as jest.Fn).mockImplementation(() => mockPriceResponse);
|
||||||
|
|
||||||
const result = await fetchPrice(mockToken, mockDefIndex + ";" + mockQuality)
|
const result = await fetchPrice(mockDefIndex + ";" + mockQuality)
|
||||||
|
|
||||||
expect(setStorageValue).toHaveBeenCalled()
|
expect(setStorageValue).toHaveBeenCalled()
|
||||||
expect(result.metal).not.toBe(mockCachedData.metal)
|
expect(result.metal).not.toBe(mockCachedData.metal)
|
||||||
expect(result.metal).toBe(mockPriceResponse.metal)
|
expect(result.metal).toBe(mockPriceResponse.metal)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('fetchPrice rejects with 401 when no token provided', async () => {
|
|
||||||
await expect(fetchPrice('', mockDefIndex + ";" + mockQuality)).rejects.toThrow()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('fetchPrice handles pricing API errors', async () => {
|
test('fetchPrice handles pricing API errors', async () => {
|
||||||
(chrome.runtime.sendMessage as jest.Fn).mockResolvedValue(null);
|
(chrome.runtime.sendMessage as jest.Fn).mockResolvedValue(null);
|
||||||
(priceUsingPricesTF as jest.Mock).mockImplementation(() => Promise.reject(new Error('500 Internal Server Error')));
|
(priceUsingPricedb as jest.Mock).mockImplementation(() => Promise.reject(new Error('500 Internal Server Error')));
|
||||||
(getStorageValue as jest.Mock).mockResolvedValue(null)
|
(getStorageValue as jest.Mock).mockResolvedValue(null)
|
||||||
|
|
||||||
await expect(fetchPrice(mockToken, mockDefIndex + ";" + mockQuality)).rejects.toThrow()
|
await expect(fetchPrice(mockDefIndex + ";" + mockQuality)).rejects.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('fetchKeyPrice uses correct parameters', async () => {
|
test('fetchKeyPrice uses correct parameters', async () => {
|
||||||
(getStorageValue as jest.Mock).mockResolvedValue(null);
|
(getStorageValue as jest.Mock).mockResolvedValue(null);
|
||||||
(priceUsingPricesTF as jest.Mock).mockResolvedValue(mockKeyPriceResponse);
|
(priceUsingPricedb as jest.Mock).mockResolvedValue(mockKeyPriceResponse);
|
||||||
(chrome.runtime.sendMessage as jest.Fn).mockImplementation(() => mockKeyPriceResponse);
|
(chrome.runtime.sendMessage as jest.Fn).mockImplementation(() => mockKeyPriceResponse);
|
||||||
|
|
||||||
const result = await fetchKeyPrice(mockToken)
|
const result = await fetchKeyPrice()
|
||||||
|
|
||||||
expect(result.keys).toBe(0) // A key cannot cost a key :P
|
expect(result.keys).toBe(0) // A key cannot cost a key :P
|
||||||
expect(result.metal).toBe(mockKeyPriceResponse.metal)
|
expect(result.metal).toBe(mockKeyPriceResponse.metal)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "tf2wikipricing",
|
"name": "tf2wikipricing",
|
||||||
"displayName": "TF2 Wiki Pricing",
|
"displayName": "TF2 Wiki Pricing",
|
||||||
"version": "0.8.1",
|
"version": "0.9.0",
|
||||||
"description": "Adds item pricing to the Team Fortress 2 wiki",
|
"description": "Adds item pricing to the Team Fortress 2 wiki",
|
||||||
"author": "rapture.party",
|
"author": "rapture.party",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -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 {
|
class PricesResponse {
|
||||||
keys: number
|
keys: number
|
||||||
metal: number
|
metal: number
|
||||||
}
|
}
|
||||||
|
|
||||||
async function priceUsingPricesTF(token: string, sku: string, retries: number = 3): Promise<PricesResponse> {
|
async function priceUsingPricedb(sku: string, retries: number = 3): Promise<PricesResponse> {
|
||||||
const url = `https://api2.prices.tf/prices/${encodeURIComponent(sku)}`;
|
const url = `https://pricedb.io/api/item/${encodeURIComponent(sku)}`;
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'get',
|
method: 'get',
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
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');
|
return priceUsingPricedb(sku + ';uncraftable');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(response.status === 503) {
|
if(response.status === 429) {
|
||||||
// Happens if we send too many requests in a short period of time
|
// Happens if we send too many requests (rate limit: 180 req/min)
|
||||||
// Retry after a few seconds
|
// Retry after a few seconds
|
||||||
if(retries >= 0) {
|
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));
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
return priceUsingPricesTF(token, sku, retries - 1);
|
return priceUsingPricedb(sku, retries - 1);
|
||||||
} else {
|
} 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 data = await response.json();
|
||||||
const prices = new PricesResponse();
|
const prices = new PricesResponse();
|
||||||
prices.keys = data['sellKeys']
|
prices.keys = data.sell.keys
|
||||||
prices.metal = data['sellHalfScrap'] / 18.0;
|
prices.metal = data.sell.metal
|
||||||
return prices;
|
return prices;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,23 +72,14 @@ chrome.runtime.onMessage.addListener(
|
|||||||
function(request, sender, sendResponse) {
|
function(request, sender, sendResponse) {
|
||||||
if (request.contentScriptQuery == "priceSKU") {
|
if (request.contentScriptQuery == "priceSKU") {
|
||||||
const sku: string = request.sku
|
const sku: string = request.sku
|
||||||
const service: string = request.service
|
priceUsingPricedb(sku)
|
||||||
const token: string = request.token
|
.then((response) => sendResponse(response))
|
||||||
if(token === "" || !token) {
|
.catch(error => {
|
||||||
sendResponse(new Error("No token provided"))
|
console.error(`Received "${error}" error while pricing ${sku} using pricedb.io`)
|
||||||
|
sendResponse(null);
|
||||||
return false;
|
return false;
|
||||||
}
|
})
|
||||||
switch (service) {
|
|
||||||
case "prices.tf": {
|
|
||||||
priceUsingPricesTF(token, sku)
|
|
||||||
.then((response) => sendResponse(response))
|
|
||||||
.catch(error => {
|
|
||||||
sendResponse(error);
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -2,7 +2,6 @@ declare const __ENV_WEBEXTENSION: boolean;
|
|||||||
declare const __ENV_USERSCRIPT: boolean;
|
declare const __ENV_USERSCRIPT: boolean;
|
||||||
|
|
||||||
import { logDebug, log, logError } from './utils/log'
|
import { logDebug, log, logError } from './utils/log'
|
||||||
import { getPricesToken } from './pricing/pricestf'
|
|
||||||
import itemQualities from 'tf2-static-schema/static/qualities.json';
|
import itemQualities from 'tf2-static-schema/static/qualities.json';
|
||||||
import { getItemIndexByName, getTradableStatusByDefindex, ItemSchema, ItemSlot, prepareSchema, wipeSchema } from './schemaService'
|
import { getItemIndexByName, getTradableStatusByDefindex, ItemSchema, ItemSlot, prepareSchema, wipeSchema } from './schemaService'
|
||||||
import { $T, extractLocaleFromURL } from './utils/localization'
|
import { $T, extractLocaleFromURL } from './utils/localization'
|
||||||
@@ -16,7 +15,7 @@ let exchangeRates: ExchangeRates | null;
|
|||||||
|
|
||||||
let locale: string = 'en'
|
let locale: string = 'en'
|
||||||
|
|
||||||
/** Exclude these from the pricelist. */
|
/** Exclude these from pricelist. */
|
||||||
const excludedQualities = new Set([
|
const excludedQualities = new Set([
|
||||||
15, // Decorated
|
15, // Decorated
|
||||||
5, // Unusual
|
5, // Unusual
|
||||||
@@ -175,22 +174,11 @@ async function inject() {
|
|||||||
priceProgressRow.appendChild(priceProgressData);
|
priceProgressRow.appendChild(priceProgressData);
|
||||||
priceInfoboxHeadingRow.insertAdjacentElement('afterend', priceProgressRow);
|
priceInfoboxHeadingRow.insertAdjacentElement('afterend', priceProgressRow);
|
||||||
|
|
||||||
let token: string | null;
|
|
||||||
|
|
||||||
// Steam Community Market
|
// Steam Community Market
|
||||||
// TODO: Change this to lazy-load, so that it doesn't make network requests when we have cached data.
|
// TODO: Change this to lazy-load, so that it doesn't make network requests when we have cached data.
|
||||||
// var steamMarketResults = await getSteamResults(itemName)
|
// var steamMarketResults = await getSteamResults(itemName)
|
||||||
// logDebug(JSON.stringify(steamMarketResults))
|
// 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;
|
let updateTime: Date | null = null;
|
||||||
|
|
||||||
enum PriceRowCategory {
|
enum PriceRowCategory {
|
||||||
@@ -208,7 +196,24 @@ async function inject() {
|
|||||||
const priceRows: PriceRow[]= [];
|
const priceRows: PriceRow[]= [];
|
||||||
|
|
||||||
// Get current key price
|
// 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()
|
const currentTime = new Date()
|
||||||
|
|
||||||
@@ -219,7 +224,7 @@ async function inject() {
|
|||||||
|
|
||||||
let data: ItemPriceData | null
|
let data: ItemPriceData | null
|
||||||
try {
|
try {
|
||||||
data = await fetchPrice(token, itemIndex + ";" + quality, currentTime);
|
data = await fetchPrice(itemIndex + ";" + quality, currentTime);
|
||||||
updateTime = new Date(data.update)
|
updateTime = new Date(data.update)
|
||||||
} catch {
|
} catch {
|
||||||
log(`${qualifiedName} is unpriced or unavailable, skipping...`)
|
log(`${qualifiedName} is unpriced or unavailable, skipping...`)
|
||||||
@@ -233,7 +238,7 @@ 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(fetchPrice(token, `${itemIndex};11;australium`, currentTime).then(data => {
|
promises.push(fetchPrice(`${itemIndex};11;australium`, currentTime).then(data => {
|
||||||
updateTime = new Date(data.update)
|
updateTime = new Date(data.update)
|
||||||
logDebug(`Saving price for Australium ${itemName}`)
|
logDebug(`Saving price for Australium ${itemName}`)
|
||||||
|
|
||||||
@@ -261,7 +266,7 @@ async function inject() {
|
|||||||
festiveHeadingRow.style.display = 'none';
|
festiveHeadingRow.style.display = 'none';
|
||||||
festiveHeadingRow.appendChild(festiveHeading);
|
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)
|
updateTime = new Date(data.update)
|
||||||
logDebug(`Saving price for Festive ${itemName}`)
|
logDebug(`Saving price for Festive ${itemName}`)
|
||||||
|
|
||||||
@@ -273,7 +278,7 @@ async function inject() {
|
|||||||
logError(error)
|
logError(error)
|
||||||
log(`Festive ${itemName} is unpriced or unavailable, skipping...`)
|
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)
|
updateTime = new Date(data.update)
|
||||||
logDebug(`Saving price for Strange Festive ${itemName}`)
|
logDebug(`Saving price for Strange Festive ${itemName}`)
|
||||||
|
|
||||||
@@ -293,36 +298,36 @@ async function inject() {
|
|||||||
itemSchema[itemIndex].slot == ItemSlot.Secondary ||
|
itemSchema[itemIndex].slot == ItemSlot.Secondary ||
|
||||||
itemSchema[itemIndex].slot == ItemSlot.Melee)
|
itemSchema[itemIndex].slot == ItemSlot.Melee)
|
||||||
{
|
{
|
||||||
/// Create subheading
|
/// Create subheading
|
||||||
killstreakKitHeadingRow = document.createElement("tr")
|
killstreakKitHeadingRow = document.createElement("tr")
|
||||||
const heading = document.createElement("th")
|
const heading = document.createElement("th")
|
||||||
heading.className = "infobox-subheader"
|
heading.className = "infobox-subheader"
|
||||||
heading.colSpan = 2
|
heading.colSpan = 2
|
||||||
heading.innerText = $T("Killstreak Kit")
|
heading.innerText = $T("Killstreak Kit")
|
||||||
heading.style.fontSize = '1em';
|
heading.style.fontSize = '1em';
|
||||||
heading.style.backgroundColor = '#F5C087';
|
heading.style.backgroundColor = '#F5C087';
|
||||||
killstreakKitHeadingRow.style.display = 'none';
|
killstreakKitHeadingRow.style.display = 'none';
|
||||||
killstreakKitHeadingRow.appendChild(heading);
|
killstreakKitHeadingRow.appendChild(heading);
|
||||||
[1,2,3].map((tier) => {
|
[1,2,3].map((tier) => {
|
||||||
let kitIndex: number
|
let kitIndex: number
|
||||||
switch (tier) {
|
switch (tier) {
|
||||||
default:
|
default:
|
||||||
case 1: kitIndex = 6527; break;
|
case 1: kitIndex = 6527; break;
|
||||||
case 2: kitIndex = 6523; break;
|
case 2: kitIndex = 6523; break;
|
||||||
case 3: kitIndex = 6526; break;
|
case 3: kitIndex = 6526; break;
|
||||||
}
|
}
|
||||||
promises.push(fetchPrice(token, `${kitIndex};6;uncraftable;kt-${tier};td-${itemIndex}`, currentTime).then(data => {
|
promises.push(fetchPrice(`${kitIndex};6;uncraftable;kt-${tier};td-${itemIndex}`, currentTime).then(data => {
|
||||||
updateTime = new Date(data.update)
|
updateTime = new Date(data.update)
|
||||||
logDebug(`Saving price for ${itemName} Killstreak Kit Tier ${tier}`)
|
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
|
// 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) => {
|
itemSchema[itemIndex].botkillerVariants.map((variantIndex) => {
|
||||||
const itemName = itemSchema[variantIndex].name
|
const itemName = itemSchema[variantIndex].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(fetchPrice(token, `${variantIndex};11`, currentTime).then(data => {
|
promises.push(fetchPrice(`${variantIndex};11`, currentTime).then(data => {
|
||||||
logDebug(`Saving price for ${itemName}`)
|
logDebug(`Saving price for ${itemName}`)
|
||||||
updateTime = new Date(data.update)
|
updateTime = new Date(data.update)
|
||||||
|
|
||||||
@@ -404,9 +409,9 @@ async function inject() {
|
|||||||
label.colSpan = 2;
|
label.colSpan = 2;
|
||||||
label.style.fontSize = "85%";
|
label.style.fontSize = "85%";
|
||||||
label.style.textAlign = "center";
|
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 attributionHeader = $T("Acknowledgements");
|
||||||
const pricesAttribution = `<a rel="nofollow" class="external text" href="https://prices.tf">prices.tf</a>`;
|
const pricesAttribution = `<a rel="nofollow" class="external text" href="https://pricedb.io">pricedb.io</a>`;
|
||||||
const exchangeRateAttribution = `<a rel="nofollow" class="external text" href="https://www.exchangerate-api.com">Rates By Exchange Rate API</a>`;
|
const exchangeRateAttribution = `<a rel="nofollow" class="external text" href="https://www.exchangerate-api.com">Rates By Exchange Rate API</a>`;
|
||||||
label.innerHTML = `${updateText}<br><b>${attributionHeader}</b><br>${pricesAttribution}<br>${exchangeRateAttribution}`;
|
label.innerHTML = `${updateText}<br><b>${attributionHeader}</b><br>${pricesAttribution}<br>${exchangeRateAttribution}`;
|
||||||
row.appendChild(label);
|
row.appendChild(label);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { defindex_key, storage_priceprefix } from "./config"
|
import { defindex_key, storage_priceprefix } from "./config"
|
||||||
import { priceUsingPricesTF } from "./pricing/pricestf"
|
import { priceUsingPricedb } from "./pricing/pricedb"
|
||||||
import { getStorageValue, setStorageValue } from "./storage"
|
import { getStorageValue, setStorageValue } from "./storage"
|
||||||
import { logDebug } from "./utils/log"
|
import { logDebug } from "./utils/log"
|
||||||
declare const __ENV_WEBEXTENSION: boolean;
|
declare const __ENV_WEBEXTENSION: boolean;
|
||||||
@@ -31,17 +31,16 @@ export class ItemPriceData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function fetchKeyPrice(token: string) {
|
export async function fetchKeyPrice() {
|
||||||
return fetchPrice(token, `${defindex_key};6`, new Date(), 86400000)
|
return fetchPrice(`${defindex_key};6`, new Date(), 86400000)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch a price for a given SKU, using cached values if available.
|
* Fetch a price for a given SKU, using cached values if available.
|
||||||
* @param token prices.tf access token.
|
|
||||||
* @param update Date retrieved.
|
* @param update Date retrieved.
|
||||||
* @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(sku: string, update: Date = new Date(), ttl: number = 30 * 60 * 1000): Promise<ItemPriceData> {
|
||||||
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,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)) {
|
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 || token === '') {
|
|
||||||
throw new Error('No token provided')
|
|
||||||
}
|
|
||||||
data = new ItemPriceData()
|
data = new ItemPriceData()
|
||||||
data.sku = sku
|
data.sku = sku
|
||||||
data.update = update.getTime()
|
data.update = update.getTime()
|
||||||
@@ -62,9 +58,9 @@ export async function fetchPrice(token: string, sku: string, update: Date = new
|
|||||||
try {
|
try {
|
||||||
let response: PricesResponse
|
let response: PricesResponse
|
||||||
if(__ENV_USERSCRIPT) {
|
if(__ENV_USERSCRIPT) {
|
||||||
response = await priceUsingPricesTF(token, sku)
|
response = await priceUsingPricedb(sku)
|
||||||
} else {
|
} 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) {
|
if (!response || response instanceof Error) {
|
||||||
throw new Error(`Bad response: ${response}`)
|
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.keys = response.keys
|
||||||
data.metal = response.metal
|
data.metal = response.metal
|
||||||
} catch (error) {
|
} 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) {
|
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}`)
|
logDebug(`Using cached price data for ${sku}`)
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|||||||
67
src/content/pricing/pricedb.ts
Normal file
67
src/content/pricing/pricedb.ts
Normal file
@@ -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<PricesResponse>} Object containing 'keys' and 'metal' prices
|
||||||
|
* @throws When API returns non-200 status code
|
||||||
|
*/
|
||||||
|
async function priceUsingPricedb(sku: string, retries: number = 3): Promise<PricesResponse> {
|
||||||
|
// 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 }
|
||||||
@@ -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<string> {
|
|
||||||
if(__ENV_USERSCRIPT) {
|
|
||||||
return new Promise<string>((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<PricesResponse>} 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<PricesResponse> {
|
|
||||||
// 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 }
|
|
||||||
@@ -24,7 +24,7 @@ export function createPriceRow(qualityName: string, data: ItemPriceData, keyPric
|
|||||||
const priceLink = document.createElement("span");
|
const priceLink = document.createElement("span");
|
||||||
let priceString: string = ''
|
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 gamePrice = formatPrice(data.keys, data.metal, keyPrice.metal, locale).trim()
|
||||||
const realPriceUSD = convertTF2PriceToUSD(data.keys, data.metal, keyPrice.metal)
|
const realPriceUSD = convertTF2PriceToUSD(data.keys, data.metal, keyPrice.metal)
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ function toFixed(num: number, fixed: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function formatPrice(keys: number, metal: number, keyPrice: number, locale: string = 'en') {
|
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)
|
const formattedKeys = +(keys + (metal / keyPrice)).toFixed(2)
|
||||||
|
|
||||||
let output: string = ''
|
let output: string = ''
|
||||||
|
|||||||
@@ -1,40 +1,40 @@
|
|||||||
import { logDebug } from "./log";
|
import { logDebug, logError } from "./log";
|
||||||
|
|
||||||
const localizations: Record<string, object> = {
|
const localizations: Record<string, object> = {
|
||||||
'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
|
'ar': require('../../strings/ar'), // Arabic
|
||||||
'cs': require('../../strings/cs'), // Czech
|
'cs': require('../../strings/cs'), // Czech
|
||||||
'da': require('../../strings/da'), // Danish
|
'da': require('../../strings/da'), // Danish
|
||||||
'de': require('../../strings/de'), // German
|
'nl': require('../../strings/nl'), // Dutch
|
||||||
'fi': require('../../strings/fi'), // Finnish
|
'fi': require('../../strings/fi'), // Finnish
|
||||||
'fr': require('../../strings/fr'), // French
|
'fr': require('../../strings/fr'), // French
|
||||||
|
'en': require('../../strings/en'), // English
|
||||||
'hu': require('../../strings/hu'), // Hungarian
|
'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
|
'ko': require('../../strings/ko'), // Korean
|
||||||
'nl': require('../../strings/nl'), // Dutch
|
|
||||||
'no': require('../../strings/no'), // Norwegian Bokmål
|
'no': require('../../strings/no'), // Norwegian Bokmål
|
||||||
'pl': require('../../strings/pl'), // Polish
|
'pl': require('../../strings/pl'), // Polish
|
||||||
'pt': require('../../strings/pt'), // Portuguese
|
'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
|
'ro': require('../../strings/ro'), // Romanian
|
||||||
|
'zh-hans': require('../../strings/zh-Hans'), // Simplified Chinese
|
||||||
'ru': require('../../strings/ru'), // Russian
|
'ru': require('../../strings/ru'), // Russian
|
||||||
|
'es': require('../../strings/es'), // Spanish
|
||||||
'sv': require('../../strings/sv'), // Swedish
|
'sv': require('../../strings/sv'), // Swedish
|
||||||
|
'zh-hant': require('../../strings/zh-Hant'), // Traditional Chinese
|
||||||
'tr': require('../../strings/tr'), // Turkish
|
'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 {
|
export function $T(s: string, locale?: Intl.LocalesArgument): string {
|
||||||
const code = locale ? locale.toString() : extractLocaleFromURL(document.URL)
|
const code = locale ? locale.toString() : extractLocaleFromURL(document.URL)
|
||||||
if (code in localizations) {
|
if (code in localizations) {
|
||||||
const translation = localizations[code] as Record<string, string>;
|
const translation = localizations[code.toLowerCase()] as Record<string, string>;
|
||||||
const result = translation[s] ?? s;
|
const result = translation[s] ?? s;
|
||||||
logDebug(`Translating "${s}" to locale "${code}": ${result}`);
|
logDebug(`Translating "${s}" to locale "${code}": ${result}`);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
logDebug(`Untranslated string "${s}" in locale "${code}`);
|
logError(`Untranslated string "${s}" in locale "${code}"`);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
],
|
],
|
||||||
"host_permissions": [
|
"host_permissions": [
|
||||||
"https://wiki.teamfortress.com/wiki/*",
|
"https://wiki.teamfortress.com/wiki/*",
|
||||||
"https://*.prices.tf/*",
|
"https://*.pricedb.io/*",
|
||||||
"https://open.er-api.com/*"
|
"https://open.er-api.com/*"
|
||||||
],
|
],
|
||||||
"web_accessible_resources": [
|
"web_accessible_resources": [
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Community Pricing",
|
"Community Pricing": "Community Pricing",
|
||||||
// Itembox footer
|
// Itembox footer
|
||||||
"Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot
|
"Updated %@": "تم التحديث يوم %@", // %@ is a date string, sourced from AppleGlot
|
||||||
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
"Data unavailable": "Data unavailable", // sourced from AppleGlot
|
"Data unavailable": "البيانات غير متوفرة", // sourced from AppleGlot
|
||||||
"%@ ref": "%@ ref",
|
"%@ ref": "%@ ref",
|
||||||
"%@ key": "%@ key",
|
"%@ key": "%@ key",
|
||||||
"%@ keys": "%@ keys",
|
"%@ keys": "%@ keys",
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Community Pricing",
|
"Community Pricing": "Community Pricing",
|
||||||
// Itembox footer
|
// 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
|
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
"Data unavailable": "Data unavailable", // sourced from AppleGlot
|
"Data unavailable": "Data nejsou k dispozici", // sourced from AppleGlot
|
||||||
"%@ ref": "%@ ref",
|
"%@ ref": "%@ ref",
|
||||||
"%@ key": "%@ key",
|
"%@ key": "%@ key",
|
||||||
"%@ keys": "%@ keys",
|
"%@ keys": "%@ keys",
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Community Pricing",
|
"Community Pricing": "Community Pricing",
|
||||||
// Itembox footer
|
// 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
|
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
"Data unavailable": "Data unavailable", // sourced from AppleGlot
|
"Data unavailable": "Data utilgængelige", // sourced from AppleGlot
|
||||||
"%@ ref": "%@ ref",
|
"%@ ref": "%@ ref",
|
||||||
"%@ key": "%@ key",
|
"%@ key": "%@ key",
|
||||||
"%@ keys": "%@ keys",
|
"%@ keys": "%@ keys",
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Community Pricing",
|
"Community Pricing": "Community Pricing",
|
||||||
// Itembox footer
|
// 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
|
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
"Data unavailable": "Data unavailable", // sourced from AppleGlot
|
"Data unavailable": "Daten nicht verfügbar", // sourced from AppleGlot
|
||||||
"%@ ref": "%@ ref",
|
"%@ ref": "%@ ref",
|
||||||
"%@ key": "%@ key",
|
"%@ key": "%@ key",
|
||||||
"%@ keys": "%@ keys",
|
"%@ keys": "%@ keys",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Community Pricing",
|
"Community Pricing": "Community Pricing",
|
||||||
// Itembox footer
|
// 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
|
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Precios de la comunidad",
|
"Community Pricing": "Precios de la comunidad",
|
||||||
// Itembox footer
|
// 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
|
"Acknowledgements": "Agradecimientos", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Community Pricing",
|
"Community Pricing": "Community Pricing",
|
||||||
// Itembox footer
|
// 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
|
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
"Data unavailable": "Data unavailable", // sourced from AppleGlot
|
"Data unavailable": "Dataa ei saatavilla", // sourced from AppleGlot
|
||||||
"%@ ref": "%@ ref",
|
"%@ ref": "%@ ref",
|
||||||
"%@ key": "%@ key",
|
"%@ key": "%@ key",
|
||||||
"%@ keys": "%@ keys",
|
"%@ keys": "%@ keys",
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Community Pricing",
|
"Community Pricing": "Community Pricing",
|
||||||
// Itembox footer
|
// 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
|
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
"Data unavailable": "Data unavailable", // sourced from AppleGlot
|
"Data unavailable": "Données non disponibles", // sourced from AppleGlot
|
||||||
"%@ ref": "%@ ref",
|
"%@ ref": "%@ ref",
|
||||||
"%@ key": "%@ key",
|
"%@ key": "%@ key",
|
||||||
"%@ keys": "%@ keys",
|
"%@ keys": "%@ keys",
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Community Pricing",
|
"Community Pricing": "Community Pricing",
|
||||||
// Itembox footer
|
// 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
|
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
"Data unavailable": "Data unavailable", // sourced from AppleGlot
|
"Data unavailable": "Adat nem érhető el", // sourced from AppleGlot
|
||||||
"%@ ref": "%@ ref",
|
"%@ ref": "%@ ref",
|
||||||
"%@ key": "%@ key",
|
"%@ key": "%@ key",
|
||||||
"%@ keys": "%@ keys",
|
"%@ keys": "%@ keys",
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Prezzo Comunitario",
|
"Community Pricing": "Prezzo Comunitario",
|
||||||
// Itembox footer
|
// Itembox footer
|
||||||
"Updated %@.": "Aggiornato il %@.",
|
"Updated %@": "Aggiornato il %@",
|
||||||
"Acknowledgements": "Note legali", // sourced from AppleGlot
|
"Acknowledgements": "Note legali", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
"Data unavailable": "Data unavailable", // sourced from AppleGlot
|
"Data unavailable": "Dati non disponibili", // sourced from AppleGlot
|
||||||
"%@ ref": "%@ raf",
|
"%@ ref": "%@ raf",
|
||||||
"%@ key": "%@ chiave",
|
"%@ key": "%@ chiave",
|
||||||
"%@ keys": "%@ chiavi",
|
"%@ keys": "%@ chiavi",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "共同体価格",
|
"Community Pricing": "共同体価格",
|
||||||
// Itembox footer
|
// Itembox footer
|
||||||
"Updated %@.": "アップデート: %@。", // %@ is a date string, sourced from AppleGlot
|
"Updated %@": "アップデート: %@", // %@ is a date string, sourced from AppleGlot
|
||||||
"Acknowledgements": "謝辞", // sourced from AppleGlot
|
"Acknowledgements": "謝辞", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Community Pricing",
|
"Community Pricing": "Community Pricing",
|
||||||
// Itembox footer
|
// 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
|
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
"Data unavailable": "Data unavailable", // sourced from AppleGlot
|
"Data unavailable": "데이터를 사용할 수 없음", // sourced from AppleGlot
|
||||||
"%@ ref": "%@ ref",
|
"%@ ref": "%@ ref",
|
||||||
"%@ key": "%@ key",
|
"%@ key": "%@ key",
|
||||||
"%@ keys": "%@ keys",
|
"%@ keys": "%@ keys",
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Community Pricing",
|
"Community Pricing": "Community Pricing",
|
||||||
// Itembox footer
|
// 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
|
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
"Data unavailable": "Data unavailable", // sourced from AppleGlot
|
"Data unavailable": "Gegevens niet beschikbaar", // sourced from AppleGlot
|
||||||
"%@ ref": "%@ ref",
|
"%@ ref": "%@ ref",
|
||||||
"%@ key": "%@ key",
|
"%@ key": "%@ key",
|
||||||
"%@ keys": "%@ keys",
|
"%@ keys": "%@ keys",
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Community Pricing",
|
"Community Pricing": "Community Pricing",
|
||||||
// Itembox footer
|
// 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
|
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
"Data unavailable": "Data unavailable", // sourced from AppleGlot
|
"Data unavailable": "Data ikke tilgjengelig", // sourced from AppleGlot
|
||||||
"%@ ref": "%@ ref",
|
"%@ ref": "%@ ref",
|
||||||
"%@ key": "%@ key",
|
"%@ key": "%@ key",
|
||||||
"%@ keys": "%@ keys",
|
"%@ keys": "%@ keys",
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Community Pricing",
|
"Community Pricing": "Community Pricing",
|
||||||
// Itembox footer
|
// 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
|
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
"Data unavailable": "Data unavailable", // sourced from AppleGlot
|
"Data unavailable": "Dane niedostępne", // sourced from AppleGlot
|
||||||
"%@ ref": "%@ ref",
|
"%@ ref": "%@ ref",
|
||||||
"%@ key": "%@ key",
|
"%@ key": "%@ key",
|
||||||
"%@ keys": "%@ keys",
|
"%@ keys": "%@ keys",
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Community Pricing",
|
"Community Pricing": "Community Pricing",
|
||||||
// Itembox footer
|
// 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
|
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
"Data unavailable": "Data unavailable", // sourced from AppleGlot
|
"Data unavailable": "Dados indisponíveis", // sourced from AppleGlot
|
||||||
"%@ ref": "%@ ref",
|
"%@ ref": "%@ ref",
|
||||||
"%@ key": "%@ key",
|
"%@ key": "%@ key",
|
||||||
"%@ keys": "%@ keys",
|
"%@ keys": "%@ keys",
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Community Pricing",
|
"Community Pricing": "Community Pricing",
|
||||||
// Itembox footer
|
// 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
|
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
"Data unavailable": "Data unavailable", // sourced from AppleGlot
|
"Data unavailable": "Dados indisponíveis", // sourced from AppleGlot
|
||||||
"%@ ref": "%@ ref",
|
"%@ ref": "%@ ref",
|
||||||
"%@ key": "%@ key",
|
"%@ key": "%@ key",
|
||||||
"%@ keys": "%@ keys",
|
"%@ keys": "%@ keys",
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Community Pricing",
|
"Community Pricing": "Community Pricing",
|
||||||
// Itembox footer
|
// 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
|
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
"Data unavailable": "Data unavailable", // sourced from AppleGlot
|
"Data unavailable": "Date indisponibile", // sourced from AppleGlot
|
||||||
"%@ ref": "%@ ref",
|
"%@ ref": "%@ ref",
|
||||||
"%@ key": "%@ key",
|
"%@ key": "%@ key",
|
||||||
"%@ keys": "%@ keys",
|
"%@ keys": "%@ keys",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Цены сообщества",
|
"Community Pricing": "Цены сообщества",
|
||||||
// Itembox footer
|
// Itembox footer
|
||||||
"Updated %@.": "Обновлено %@.", // %@ is a date string, sourced from AppleGlot
|
"Updated %@": "Обновлено %@", // %@ is a date string, sourced from AppleGlot
|
||||||
"Acknowledgements": "Уведомления", // sourced from AppleGlot
|
"Acknowledgements": "Уведомления", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Community Pricing",
|
"Community Pricing": "Community Pricing",
|
||||||
// Itembox footer
|
// 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
|
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
"Data unavailable": "Data unavailable", // sourced from AppleGlot
|
"Data unavailable": "Data ej tillgängliga", // sourced from AppleGlot
|
||||||
"%@ ref": "%@ ref",
|
"%@ ref": "%@ ref",
|
||||||
"%@ key": "%@ key",
|
"%@ key": "%@ key",
|
||||||
"%@ keys": "%@ keys",
|
"%@ keys": "%@ keys",
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Community Pricing",
|
"Community Pricing": "Community Pricing",
|
||||||
// Itembox footer
|
// 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
|
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
"Data unavailable": "Data unavailable", // sourced from AppleGlot
|
"Data unavailable": "Veri yok", // sourced from AppleGlot
|
||||||
"%@ ref": "%@ ref",
|
"%@ ref": "%@ ref",
|
||||||
"%@ key": "%@ key",
|
"%@ key": "%@ key",
|
||||||
"%@ keys": "%@ keys",
|
"%@ keys": "%@ keys",
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Community Pricing",
|
"Community Pricing": "Community Pricing",
|
||||||
// Itembox footer
|
// 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
|
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
"Data unavailable": "Data unavailable", // sourced from AppleGlot
|
"Data unavailable": "数据不可用", // sourced from AppleGlot
|
||||||
"%@ ref": "%@ ref",
|
"%@ ref": "%@ ref",
|
||||||
"%@ key": "%@ key",
|
"%@ key": "%@ key",
|
||||||
"%@ keys": "%@ keys",
|
"%@ keys": "%@ keys",
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ module.exports = {
|
|||||||
// Itembox header
|
// Itembox header
|
||||||
"Community Pricing": "Community Pricing",
|
"Community Pricing": "Community Pricing",
|
||||||
// Itembox footer
|
// 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
|
"Acknowledgements": "Acknowledgements", // sourced from AppleGlot
|
||||||
|
|
||||||
// Price strings
|
// Price strings
|
||||||
"Data unavailable": "Data unavailable", // sourced from AppleGlot
|
"Data unavailable": "無法使用資料", // sourced from AppleGlot
|
||||||
"%@ ref": "%@ ref",
|
"%@ ref": "%@ ref",
|
||||||
"%@ key": "%@ key",
|
"%@ key": "%@ key",
|
||||||
"%@ keys": "%@ keys",
|
"%@ keys": "%@ keys",
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
// @inject-into content
|
// @inject-into content
|
||||||
// @connect steamcommunity.com
|
// @connect steamcommunity.com
|
||||||
// @domain steamcommunity.com
|
// @domain steamcommunity.com
|
||||||
// @connect prices.tf
|
// @connect pricedb.io
|
||||||
// @domain prices.tf
|
// @domain pricedb.io
|
||||||
// @connect open.er-api.com
|
// @connect open.er-api.com
|
||||||
// @domain open.er-api.com
|
// @domain open.er-api.com
|
||||||
// @grant GM.setValue
|
// @grant GM.setValue
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ const defines = {
|
|||||||
__VERSION__: JSON.stringify(package.version),
|
__VERSION__: JSON.stringify(package.version),
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = [
|
module.exports = (env, argv) => [
|
||||||
// WebExtension
|
// WebExtension
|
||||||
{
|
{
|
||||||
|
devtool: argv.mode === 'production' ? false : 'source-map',
|
||||||
entry: {
|
entry: {
|
||||||
content: './src/content/content.ts',
|
content: './src/content/content.ts',
|
||||||
background: './src/background/background.ts',
|
background: './src/background/background.ts',
|
||||||
|
|||||||
Reference in New Issue
Block a user