From b8fe58a4a04518d57d84c33020f6f6f823ac1218 Mon Sep 17 00:00:00 2001 From: xenticore Date: Wed, 16 Apr 2025 21:36:15 -0400 Subject: [PATCH 01/43] l10n: remove whitespace in es --- src/strings/es.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/es.js b/src/strings/es.js index 3eff563..112bb59 100644 --- a/src/strings/es.js +++ b/src/strings/es.js @@ -13,7 +13,7 @@ module.exports = { "%@ ref": "%@ ref", "%@ key": "%@ llave", "%@ keys": "%@ llaves", - + // Item quality names, all sourced from TF2 wiki "Normal": "de Calidad Normal", "Genuine": "de Calidad Genuina", From 460a53b06c64b3ed64f606bb1a37cc1cac748631 Mon Sep 17 00:00:00 2001 From: xenticore Date: Wed, 16 Apr 2025 21:36:27 -0400 Subject: [PATCH 02/43] l10n: add missing italian strings --- src/strings/it.js | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/strings/it.js b/src/strings/it.js index 961fa57..4d070d7 100644 --- a/src/strings/it.js +++ b/src/strings/it.js @@ -3,16 +3,16 @@ module.exports = { "View listings on %@": "Voir les offres sur %@", // Itembox header - "Community Pricing": "Community Pricing", + "Community Pricing": "Prezzo Comunitario", // Itembox footer - "Updated %@": "Updated %@.", - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Updated %@.": "Aggiornato il %@.", + "Trade prices sourced from %@. Currency conversions are approximate.": "Prezzi commerciali forniti da %@. Le conversioni valutarie sono approssimative.", // %@ is always a URL, (eg. prices.tf) // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot - "%@ ref": "%@ ref", - "%@ key": "%@ key", - "%@ keys": "%@ keys", + "%@ ref": "%@ raf", + "%@ key": "%@ chiave", + "%@ keys": "%@ chiavi", // Item quality names, all sourced from TF2 wiki "Normal": "Normale", @@ -22,5 +22,23 @@ module.exports = { "Strange": "Strano", "Collector's": "Da collezione", "Haunted": "Stregato", - "Australium": "Australium" + "Australium": "Australium", + "Festive": "Festivo", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Ammazzabot", + "Silver": "Argentato", + "Gold": "Dorato", + "Rust": "Arrugginito", + "Blood": "Insanguinato", + "Carbonado": "Carbonado", + "Diamond": "Diamante", + "Silver Mk.II": "Argentato Mk.II", + "Gold Mk.II": "Dorato Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Kit per Serie omicide", + "kt-1": "Standard", + "kt-2": "Specializzati", + "kt-3": "Professionali", } \ No newline at end of file From 9888e2adfad9812e4f8c913607529a45589fc7b7 Mon Sep 17 00:00:00 2001 From: xenticore Date: Wed, 16 Apr 2025 22:12:41 -0400 Subject: [PATCH 03/43] l10n: add missing japanese strings --- src/strings/ja.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/strings/ja.js b/src/strings/ja.js index 01b1528..1fa3eab 100644 --- a/src/strings/ja.js +++ b/src/strings/ja.js @@ -3,7 +3,7 @@ module.exports = { "View listings on %@": "%@で検索結果を見る", // Itembox header - "Community Pricing": "Community Pricing", + "Community Pricing": "共同体価格", // Itembox footer "Updated %@.": "アップデート: %@。", // %@ is a date string, sourced from AppleGlot "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) @@ -22,5 +22,23 @@ module.exports = { "Strange": "ストレンジ", "Collector's": "Collector's", "Haunted": "Haunted", - "Australium": "オーストラリウム" + "Australium": "オーストラリウム", + "Festive": "フェスティブ", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "ボットキラー", + "Silver": "シルバー", + "Gold": "ゴールド", + "Rust": "さびた", + "Blood": "ブラッド", + "Carbonado": "黒ダイヤ", + "Diamond": "ダイヤモンド", + "Silver Mk.II": "シルバー Mk.II", + "Gold Mk.II": "ゴールド Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "キルストリークキット", + "kt-1": "Standard", + "kt-2": "Specialized", + "kt-3": "Professional", } \ No newline at end of file From 94ddd6047e8632b4bfc07afec2714a27328b22b9 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 24 Apr 2025 17:07:38 -0400 Subject: [PATCH 04/43] l10n: stub all wiki languages --- src/content/utils/localization.ts | 42 ++++++++++++++--------------- src/strings/ar.js | 44 +++++++++++++++++++++++++++++++ src/strings/cs.js | 44 +++++++++++++++++++++++++++++++ src/strings/da.js | 44 +++++++++++++++++++++++++++++++ src/strings/de.js | 44 +++++++++++++++++++++++++++++++ src/strings/fi.js | 44 +++++++++++++++++++++++++++++++ src/strings/fr.js | 44 +++++++++++++++++++++++++++++++ src/strings/hu.js | 44 +++++++++++++++++++++++++++++++ src/strings/ko.js | 44 +++++++++++++++++++++++++++++++ src/strings/nl.js | 44 +++++++++++++++++++++++++++++++ src/strings/no.js | 44 +++++++++++++++++++++++++++++++ src/strings/pl.js | 44 +++++++++++++++++++++++++++++++ src/strings/pt-BR.js | 44 +++++++++++++++++++++++++++++++ src/strings/pt.js | 44 +++++++++++++++++++++++++++++++ src/strings/ro.js | 44 +++++++++++++++++++++++++++++++ src/strings/ru.js | 44 +++++++++++++++++++++++++++++++ src/strings/sv.js | 44 +++++++++++++++++++++++++++++++ src/strings/tr.js | 44 +++++++++++++++++++++++++++++++ src/strings/zh-Hans.js | 44 +++++++++++++++++++++++++++++++ src/strings/zh-Hant.js | 44 +++++++++++++++++++++++++++++++ 20 files changed, 857 insertions(+), 21 deletions(-) create mode 100644 src/strings/ar.js create mode 100644 src/strings/cs.js create mode 100644 src/strings/da.js create mode 100644 src/strings/de.js create mode 100644 src/strings/fi.js create mode 100644 src/strings/fr.js create mode 100644 src/strings/hu.js create mode 100644 src/strings/ko.js create mode 100644 src/strings/nl.js create mode 100644 src/strings/no.js create mode 100644 src/strings/pl.js create mode 100644 src/strings/pt-BR.js create mode 100644 src/strings/pt.js create mode 100644 src/strings/ro.js create mode 100644 src/strings/ru.js create mode 100644 src/strings/sv.js create mode 100644 src/strings/tr.js create mode 100644 src/strings/zh-Hans.js create mode 100644 src/strings/zh-Hant.js diff --git a/src/content/utils/localization.ts b/src/content/utils/localization.ts index 6b262b0..7ef2e1b 100644 --- a/src/content/utils/localization.ts +++ b/src/content/utils/localization.ts @@ -1,27 +1,27 @@ const localizations: {[lang: string]: any} = { 'en': require('../../strings/en'), // English 'es': require('../../strings/es'), // Spanish - // 'ja': require('../../strings/ja'), // Japanese - // 'it': require('../../strings/it'), // Italian - // 'ar': require('../../strings/ar.json') as object, // Arabic - // 'cs': require('../../strings/cs.json') as object, // Czech - // 'da': require('../../strings/da.json') as object, // Danish - // 'de': require('../../strings/de.json') as object, // German - // 'fi': require('../../strings/fi.json') as object, // Finnish - // 'fr': require('../../strings/fr.json') as object, // French - // 'hu': require('../../strings/hu.json') as object, // Hungarian - // 'ko': require('../../strings/ko.json') as object, // Korean - // 'nl': require('../../strings/nl.json') as object, // Dutch - // 'no': require('../../strings/no.json') as object, // Norwegian Bokmål - // 'pl': require('../../strings/pl.json') as object, // Polish - // 'pt': require('../../strings/pt.json') as object, // Portuguese - // 'pt-BR': require('../../strings/pt-BR.json') as object, // Brazilian Portuguese - // 'ro': require('../../strings/ro.json') as object, // Romanian - // 'ru': require('../../strings/ru.json') as object, // Russian - // 'sv': require('../../strings/sv.json') as object, // Swedish - // 'tr': require('../../strings/tr.json') as object, // Turkish - // 'zh-Hans': require('../../strings/zh-Hans.json') as object, // Simplified Chinese - // 'zh-Hant': require('../../strings/zh-Hant.json') as object, // Traditional Chinese + 'ja': require('../../strings/ja'), // Japanese + 'it': require('../../strings/it'), // Italian + 'ar': require('../../strings/ar') as object, // Arabic + 'cs': require('../../strings/cs') as object, // Czech + 'da': require('../../strings/da') as object, // Danish + 'de': require('../../strings/de') as object, // German + 'fi': require('../../strings/fi') as object, // Finnish + 'fr': require('../../strings/fr') as object, // French + 'hu': require('../../strings/hu') as object, // Hungarian + 'ko': require('../../strings/ko') as object, // Korean + 'nl': require('../../strings/nl') as object, // Dutch + 'no': require('../../strings/no') as object, // Norwegian Bokmål + 'pl': require('../../strings/pl') as object, // Polish + 'pt': require('../../strings/pt') as object, // Portuguese + 'pt-BR': require('../../strings/pt-BR') as object, // Brazilian Portuguese + 'ro': require('../../strings/ro') as object, // Romanian + 'ru': require('../../strings/ru') as object, // Russian + 'sv': require('../../strings/sv') as object, // Swedish + 'tr': require('../../strings/tr') as object, // Turkish + 'zh-Hans': require('../../strings/zh-Hans') as object, // Simplified Chinese + 'zh-Hant': require('../../strings/zh-Hant') as object, // Traditional Chinese } export function $T(s: string, locale?: Intl.LocalesArgument): string { diff --git a/src/strings/ar.js b/src/strings/ar.js new file mode 100644 index 0000000..485fd95 --- /dev/null +++ b/src/strings/ar.js @@ -0,0 +1,44 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "View listings on %@", + + // Itembox header + "Community Pricing": "Community Pricing", + // Itembox footer + "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + + // Price strings + "Data unavailable": "Data unavailable", // sourced from AppleGlot + "%@ ref": "%@ ref", + "%@ key": "%@ key", + "%@ keys": "%@ keys", + + // Item quality names, all sourced from TF2 wiki + "Normal": "Normal", + "Genuine": "Genuine", + "Vintage": "Vintage", + "Unique": "Unique", + "Strange": "Strange", + "Collector's": "Collector's", + "Haunted": "Haunted", + "Australium": "Australium", + "Festive": "Festive", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Botkiller", + "Silver": "Silver", + "Gold": "Gold", + "Rust": "Rust", + "Blood": "Blood", + "Carbonado": "Carbonado", + "Diamond": "Diamond", + "Silver Mk.II": "Silver Mk.II", + "Gold Mk.II": "Gold Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Killstreak Kit", + "kt-1": "Standard", + "kt-2": "Specialized", + "kt-3": "Professional", +} \ No newline at end of file diff --git a/src/strings/cs.js b/src/strings/cs.js new file mode 100644 index 0000000..485fd95 --- /dev/null +++ b/src/strings/cs.js @@ -0,0 +1,44 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "View listings on %@", + + // Itembox header + "Community Pricing": "Community Pricing", + // Itembox footer + "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + + // Price strings + "Data unavailable": "Data unavailable", // sourced from AppleGlot + "%@ ref": "%@ ref", + "%@ key": "%@ key", + "%@ keys": "%@ keys", + + // Item quality names, all sourced from TF2 wiki + "Normal": "Normal", + "Genuine": "Genuine", + "Vintage": "Vintage", + "Unique": "Unique", + "Strange": "Strange", + "Collector's": "Collector's", + "Haunted": "Haunted", + "Australium": "Australium", + "Festive": "Festive", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Botkiller", + "Silver": "Silver", + "Gold": "Gold", + "Rust": "Rust", + "Blood": "Blood", + "Carbonado": "Carbonado", + "Diamond": "Diamond", + "Silver Mk.II": "Silver Mk.II", + "Gold Mk.II": "Gold Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Killstreak Kit", + "kt-1": "Standard", + "kt-2": "Specialized", + "kt-3": "Professional", +} \ No newline at end of file diff --git a/src/strings/da.js b/src/strings/da.js new file mode 100644 index 0000000..485fd95 --- /dev/null +++ b/src/strings/da.js @@ -0,0 +1,44 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "View listings on %@", + + // Itembox header + "Community Pricing": "Community Pricing", + // Itembox footer + "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + + // Price strings + "Data unavailable": "Data unavailable", // sourced from AppleGlot + "%@ ref": "%@ ref", + "%@ key": "%@ key", + "%@ keys": "%@ keys", + + // Item quality names, all sourced from TF2 wiki + "Normal": "Normal", + "Genuine": "Genuine", + "Vintage": "Vintage", + "Unique": "Unique", + "Strange": "Strange", + "Collector's": "Collector's", + "Haunted": "Haunted", + "Australium": "Australium", + "Festive": "Festive", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Botkiller", + "Silver": "Silver", + "Gold": "Gold", + "Rust": "Rust", + "Blood": "Blood", + "Carbonado": "Carbonado", + "Diamond": "Diamond", + "Silver Mk.II": "Silver Mk.II", + "Gold Mk.II": "Gold Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Killstreak Kit", + "kt-1": "Standard", + "kt-2": "Specialized", + "kt-3": "Professional", +} \ No newline at end of file diff --git a/src/strings/de.js b/src/strings/de.js new file mode 100644 index 0000000..485fd95 --- /dev/null +++ b/src/strings/de.js @@ -0,0 +1,44 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "View listings on %@", + + // Itembox header + "Community Pricing": "Community Pricing", + // Itembox footer + "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + + // Price strings + "Data unavailable": "Data unavailable", // sourced from AppleGlot + "%@ ref": "%@ ref", + "%@ key": "%@ key", + "%@ keys": "%@ keys", + + // Item quality names, all sourced from TF2 wiki + "Normal": "Normal", + "Genuine": "Genuine", + "Vintage": "Vintage", + "Unique": "Unique", + "Strange": "Strange", + "Collector's": "Collector's", + "Haunted": "Haunted", + "Australium": "Australium", + "Festive": "Festive", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Botkiller", + "Silver": "Silver", + "Gold": "Gold", + "Rust": "Rust", + "Blood": "Blood", + "Carbonado": "Carbonado", + "Diamond": "Diamond", + "Silver Mk.II": "Silver Mk.II", + "Gold Mk.II": "Gold Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Killstreak Kit", + "kt-1": "Standard", + "kt-2": "Specialized", + "kt-3": "Professional", +} \ No newline at end of file diff --git a/src/strings/fi.js b/src/strings/fi.js new file mode 100644 index 0000000..485fd95 --- /dev/null +++ b/src/strings/fi.js @@ -0,0 +1,44 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "View listings on %@", + + // Itembox header + "Community Pricing": "Community Pricing", + // Itembox footer + "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + + // Price strings + "Data unavailable": "Data unavailable", // sourced from AppleGlot + "%@ ref": "%@ ref", + "%@ key": "%@ key", + "%@ keys": "%@ keys", + + // Item quality names, all sourced from TF2 wiki + "Normal": "Normal", + "Genuine": "Genuine", + "Vintage": "Vintage", + "Unique": "Unique", + "Strange": "Strange", + "Collector's": "Collector's", + "Haunted": "Haunted", + "Australium": "Australium", + "Festive": "Festive", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Botkiller", + "Silver": "Silver", + "Gold": "Gold", + "Rust": "Rust", + "Blood": "Blood", + "Carbonado": "Carbonado", + "Diamond": "Diamond", + "Silver Mk.II": "Silver Mk.II", + "Gold Mk.II": "Gold Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Killstreak Kit", + "kt-1": "Standard", + "kt-2": "Specialized", + "kt-3": "Professional", +} \ No newline at end of file diff --git a/src/strings/fr.js b/src/strings/fr.js new file mode 100644 index 0000000..485fd95 --- /dev/null +++ b/src/strings/fr.js @@ -0,0 +1,44 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "View listings on %@", + + // Itembox header + "Community Pricing": "Community Pricing", + // Itembox footer + "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + + // Price strings + "Data unavailable": "Data unavailable", // sourced from AppleGlot + "%@ ref": "%@ ref", + "%@ key": "%@ key", + "%@ keys": "%@ keys", + + // Item quality names, all sourced from TF2 wiki + "Normal": "Normal", + "Genuine": "Genuine", + "Vintage": "Vintage", + "Unique": "Unique", + "Strange": "Strange", + "Collector's": "Collector's", + "Haunted": "Haunted", + "Australium": "Australium", + "Festive": "Festive", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Botkiller", + "Silver": "Silver", + "Gold": "Gold", + "Rust": "Rust", + "Blood": "Blood", + "Carbonado": "Carbonado", + "Diamond": "Diamond", + "Silver Mk.II": "Silver Mk.II", + "Gold Mk.II": "Gold Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Killstreak Kit", + "kt-1": "Standard", + "kt-2": "Specialized", + "kt-3": "Professional", +} \ No newline at end of file diff --git a/src/strings/hu.js b/src/strings/hu.js new file mode 100644 index 0000000..485fd95 --- /dev/null +++ b/src/strings/hu.js @@ -0,0 +1,44 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "View listings on %@", + + // Itembox header + "Community Pricing": "Community Pricing", + // Itembox footer + "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + + // Price strings + "Data unavailable": "Data unavailable", // sourced from AppleGlot + "%@ ref": "%@ ref", + "%@ key": "%@ key", + "%@ keys": "%@ keys", + + // Item quality names, all sourced from TF2 wiki + "Normal": "Normal", + "Genuine": "Genuine", + "Vintage": "Vintage", + "Unique": "Unique", + "Strange": "Strange", + "Collector's": "Collector's", + "Haunted": "Haunted", + "Australium": "Australium", + "Festive": "Festive", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Botkiller", + "Silver": "Silver", + "Gold": "Gold", + "Rust": "Rust", + "Blood": "Blood", + "Carbonado": "Carbonado", + "Diamond": "Diamond", + "Silver Mk.II": "Silver Mk.II", + "Gold Mk.II": "Gold Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Killstreak Kit", + "kt-1": "Standard", + "kt-2": "Specialized", + "kt-3": "Professional", +} \ No newline at end of file diff --git a/src/strings/ko.js b/src/strings/ko.js new file mode 100644 index 0000000..485fd95 --- /dev/null +++ b/src/strings/ko.js @@ -0,0 +1,44 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "View listings on %@", + + // Itembox header + "Community Pricing": "Community Pricing", + // Itembox footer + "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + + // Price strings + "Data unavailable": "Data unavailable", // sourced from AppleGlot + "%@ ref": "%@ ref", + "%@ key": "%@ key", + "%@ keys": "%@ keys", + + // Item quality names, all sourced from TF2 wiki + "Normal": "Normal", + "Genuine": "Genuine", + "Vintage": "Vintage", + "Unique": "Unique", + "Strange": "Strange", + "Collector's": "Collector's", + "Haunted": "Haunted", + "Australium": "Australium", + "Festive": "Festive", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Botkiller", + "Silver": "Silver", + "Gold": "Gold", + "Rust": "Rust", + "Blood": "Blood", + "Carbonado": "Carbonado", + "Diamond": "Diamond", + "Silver Mk.II": "Silver Mk.II", + "Gold Mk.II": "Gold Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Killstreak Kit", + "kt-1": "Standard", + "kt-2": "Specialized", + "kt-3": "Professional", +} \ No newline at end of file diff --git a/src/strings/nl.js b/src/strings/nl.js new file mode 100644 index 0000000..485fd95 --- /dev/null +++ b/src/strings/nl.js @@ -0,0 +1,44 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "View listings on %@", + + // Itembox header + "Community Pricing": "Community Pricing", + // Itembox footer + "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + + // Price strings + "Data unavailable": "Data unavailable", // sourced from AppleGlot + "%@ ref": "%@ ref", + "%@ key": "%@ key", + "%@ keys": "%@ keys", + + // Item quality names, all sourced from TF2 wiki + "Normal": "Normal", + "Genuine": "Genuine", + "Vintage": "Vintage", + "Unique": "Unique", + "Strange": "Strange", + "Collector's": "Collector's", + "Haunted": "Haunted", + "Australium": "Australium", + "Festive": "Festive", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Botkiller", + "Silver": "Silver", + "Gold": "Gold", + "Rust": "Rust", + "Blood": "Blood", + "Carbonado": "Carbonado", + "Diamond": "Diamond", + "Silver Mk.II": "Silver Mk.II", + "Gold Mk.II": "Gold Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Killstreak Kit", + "kt-1": "Standard", + "kt-2": "Specialized", + "kt-3": "Professional", +} \ No newline at end of file diff --git a/src/strings/no.js b/src/strings/no.js new file mode 100644 index 0000000..485fd95 --- /dev/null +++ b/src/strings/no.js @@ -0,0 +1,44 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "View listings on %@", + + // Itembox header + "Community Pricing": "Community Pricing", + // Itembox footer + "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + + // Price strings + "Data unavailable": "Data unavailable", // sourced from AppleGlot + "%@ ref": "%@ ref", + "%@ key": "%@ key", + "%@ keys": "%@ keys", + + // Item quality names, all sourced from TF2 wiki + "Normal": "Normal", + "Genuine": "Genuine", + "Vintage": "Vintage", + "Unique": "Unique", + "Strange": "Strange", + "Collector's": "Collector's", + "Haunted": "Haunted", + "Australium": "Australium", + "Festive": "Festive", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Botkiller", + "Silver": "Silver", + "Gold": "Gold", + "Rust": "Rust", + "Blood": "Blood", + "Carbonado": "Carbonado", + "Diamond": "Diamond", + "Silver Mk.II": "Silver Mk.II", + "Gold Mk.II": "Gold Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Killstreak Kit", + "kt-1": "Standard", + "kt-2": "Specialized", + "kt-3": "Professional", +} \ No newline at end of file diff --git a/src/strings/pl.js b/src/strings/pl.js new file mode 100644 index 0000000..485fd95 --- /dev/null +++ b/src/strings/pl.js @@ -0,0 +1,44 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "View listings on %@", + + // Itembox header + "Community Pricing": "Community Pricing", + // Itembox footer + "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + + // Price strings + "Data unavailable": "Data unavailable", // sourced from AppleGlot + "%@ ref": "%@ ref", + "%@ key": "%@ key", + "%@ keys": "%@ keys", + + // Item quality names, all sourced from TF2 wiki + "Normal": "Normal", + "Genuine": "Genuine", + "Vintage": "Vintage", + "Unique": "Unique", + "Strange": "Strange", + "Collector's": "Collector's", + "Haunted": "Haunted", + "Australium": "Australium", + "Festive": "Festive", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Botkiller", + "Silver": "Silver", + "Gold": "Gold", + "Rust": "Rust", + "Blood": "Blood", + "Carbonado": "Carbonado", + "Diamond": "Diamond", + "Silver Mk.II": "Silver Mk.II", + "Gold Mk.II": "Gold Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Killstreak Kit", + "kt-1": "Standard", + "kt-2": "Specialized", + "kt-3": "Professional", +} \ No newline at end of file diff --git a/src/strings/pt-BR.js b/src/strings/pt-BR.js new file mode 100644 index 0000000..485fd95 --- /dev/null +++ b/src/strings/pt-BR.js @@ -0,0 +1,44 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "View listings on %@", + + // Itembox header + "Community Pricing": "Community Pricing", + // Itembox footer + "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + + // Price strings + "Data unavailable": "Data unavailable", // sourced from AppleGlot + "%@ ref": "%@ ref", + "%@ key": "%@ key", + "%@ keys": "%@ keys", + + // Item quality names, all sourced from TF2 wiki + "Normal": "Normal", + "Genuine": "Genuine", + "Vintage": "Vintage", + "Unique": "Unique", + "Strange": "Strange", + "Collector's": "Collector's", + "Haunted": "Haunted", + "Australium": "Australium", + "Festive": "Festive", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Botkiller", + "Silver": "Silver", + "Gold": "Gold", + "Rust": "Rust", + "Blood": "Blood", + "Carbonado": "Carbonado", + "Diamond": "Diamond", + "Silver Mk.II": "Silver Mk.II", + "Gold Mk.II": "Gold Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Killstreak Kit", + "kt-1": "Standard", + "kt-2": "Specialized", + "kt-3": "Professional", +} \ No newline at end of file diff --git a/src/strings/pt.js b/src/strings/pt.js new file mode 100644 index 0000000..485fd95 --- /dev/null +++ b/src/strings/pt.js @@ -0,0 +1,44 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "View listings on %@", + + // Itembox header + "Community Pricing": "Community Pricing", + // Itembox footer + "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + + // Price strings + "Data unavailable": "Data unavailable", // sourced from AppleGlot + "%@ ref": "%@ ref", + "%@ key": "%@ key", + "%@ keys": "%@ keys", + + // Item quality names, all sourced from TF2 wiki + "Normal": "Normal", + "Genuine": "Genuine", + "Vintage": "Vintage", + "Unique": "Unique", + "Strange": "Strange", + "Collector's": "Collector's", + "Haunted": "Haunted", + "Australium": "Australium", + "Festive": "Festive", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Botkiller", + "Silver": "Silver", + "Gold": "Gold", + "Rust": "Rust", + "Blood": "Blood", + "Carbonado": "Carbonado", + "Diamond": "Diamond", + "Silver Mk.II": "Silver Mk.II", + "Gold Mk.II": "Gold Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Killstreak Kit", + "kt-1": "Standard", + "kt-2": "Specialized", + "kt-3": "Professional", +} \ No newline at end of file diff --git a/src/strings/ro.js b/src/strings/ro.js new file mode 100644 index 0000000..485fd95 --- /dev/null +++ b/src/strings/ro.js @@ -0,0 +1,44 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "View listings on %@", + + // Itembox header + "Community Pricing": "Community Pricing", + // Itembox footer + "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + + // Price strings + "Data unavailable": "Data unavailable", // sourced from AppleGlot + "%@ ref": "%@ ref", + "%@ key": "%@ key", + "%@ keys": "%@ keys", + + // Item quality names, all sourced from TF2 wiki + "Normal": "Normal", + "Genuine": "Genuine", + "Vintage": "Vintage", + "Unique": "Unique", + "Strange": "Strange", + "Collector's": "Collector's", + "Haunted": "Haunted", + "Australium": "Australium", + "Festive": "Festive", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Botkiller", + "Silver": "Silver", + "Gold": "Gold", + "Rust": "Rust", + "Blood": "Blood", + "Carbonado": "Carbonado", + "Diamond": "Diamond", + "Silver Mk.II": "Silver Mk.II", + "Gold Mk.II": "Gold Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Killstreak Kit", + "kt-1": "Standard", + "kt-2": "Specialized", + "kt-3": "Professional", +} \ No newline at end of file diff --git a/src/strings/ru.js b/src/strings/ru.js new file mode 100644 index 0000000..f5b9a54 --- /dev/null +++ b/src/strings/ru.js @@ -0,0 +1,44 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "Просмотреть объявления на %@", + + // Itembox header + "Community Pricing": "Цены сообщества", + // Itembox footer + "Updated %@.": "Обновлено %@.", // %@ is a date string, sourced from AppleGlot + "Trade prices sourced from %@. Currency conversions are approximate.": "Цены на торговлю взяты с %@. Конвертация валюты приблизительна.", // %@ is always a URL, (eg. prices.tf) + + // Price strings + "Data unavailable": "Данные недоступны", // sourced from AppleGlot + "%@ ref": "%@ реф", + "%@ key": "%@ ключ", + "%@ keys": "%@ ключей", + + // Item quality names, all sourced from TF2 wiki + "Normal": "Обычное", + "Genuine": "высшей пробы", + "Vintage": "старой закалки", + "Unique": "Уникальный", + "Strange": "странного типа", + "Collector's": "из коллекции", + "Haunted": "призрачного вида", + "Australium": "из австралия", + "Festive": "Праздничный", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Боткиллер", + "Silver": "Серебряный", + "Gold": "Золотой", + "Rust": "Ржавый", + "Blood": "Кровавый", + "Carbonado": "Карбонадо", + "Diamond": "Алмазный", + "Silver Mk.II": "Серебряный вер. 2.0", + "Gold Mk.II": "Золотой вер. 2.0", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Набор убийцы", + "kt-1": "серийного", + "kt-2": "особо опасного", + "kt-3": "профессионального", + } \ No newline at end of file diff --git a/src/strings/sv.js b/src/strings/sv.js new file mode 100644 index 0000000..485fd95 --- /dev/null +++ b/src/strings/sv.js @@ -0,0 +1,44 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "View listings on %@", + + // Itembox header + "Community Pricing": "Community Pricing", + // Itembox footer + "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + + // Price strings + "Data unavailable": "Data unavailable", // sourced from AppleGlot + "%@ ref": "%@ ref", + "%@ key": "%@ key", + "%@ keys": "%@ keys", + + // Item quality names, all sourced from TF2 wiki + "Normal": "Normal", + "Genuine": "Genuine", + "Vintage": "Vintage", + "Unique": "Unique", + "Strange": "Strange", + "Collector's": "Collector's", + "Haunted": "Haunted", + "Australium": "Australium", + "Festive": "Festive", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Botkiller", + "Silver": "Silver", + "Gold": "Gold", + "Rust": "Rust", + "Blood": "Blood", + "Carbonado": "Carbonado", + "Diamond": "Diamond", + "Silver Mk.II": "Silver Mk.II", + "Gold Mk.II": "Gold Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Killstreak Kit", + "kt-1": "Standard", + "kt-2": "Specialized", + "kt-3": "Professional", +} \ No newline at end of file diff --git a/src/strings/tr.js b/src/strings/tr.js new file mode 100644 index 0000000..485fd95 --- /dev/null +++ b/src/strings/tr.js @@ -0,0 +1,44 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "View listings on %@", + + // Itembox header + "Community Pricing": "Community Pricing", + // Itembox footer + "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + + // Price strings + "Data unavailable": "Data unavailable", // sourced from AppleGlot + "%@ ref": "%@ ref", + "%@ key": "%@ key", + "%@ keys": "%@ keys", + + // Item quality names, all sourced from TF2 wiki + "Normal": "Normal", + "Genuine": "Genuine", + "Vintage": "Vintage", + "Unique": "Unique", + "Strange": "Strange", + "Collector's": "Collector's", + "Haunted": "Haunted", + "Australium": "Australium", + "Festive": "Festive", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Botkiller", + "Silver": "Silver", + "Gold": "Gold", + "Rust": "Rust", + "Blood": "Blood", + "Carbonado": "Carbonado", + "Diamond": "Diamond", + "Silver Mk.II": "Silver Mk.II", + "Gold Mk.II": "Gold Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Killstreak Kit", + "kt-1": "Standard", + "kt-2": "Specialized", + "kt-3": "Professional", +} \ No newline at end of file diff --git a/src/strings/zh-Hans.js b/src/strings/zh-Hans.js new file mode 100644 index 0000000..485fd95 --- /dev/null +++ b/src/strings/zh-Hans.js @@ -0,0 +1,44 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "View listings on %@", + + // Itembox header + "Community Pricing": "Community Pricing", + // Itembox footer + "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + + // Price strings + "Data unavailable": "Data unavailable", // sourced from AppleGlot + "%@ ref": "%@ ref", + "%@ key": "%@ key", + "%@ keys": "%@ keys", + + // Item quality names, all sourced from TF2 wiki + "Normal": "Normal", + "Genuine": "Genuine", + "Vintage": "Vintage", + "Unique": "Unique", + "Strange": "Strange", + "Collector's": "Collector's", + "Haunted": "Haunted", + "Australium": "Australium", + "Festive": "Festive", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Botkiller", + "Silver": "Silver", + "Gold": "Gold", + "Rust": "Rust", + "Blood": "Blood", + "Carbonado": "Carbonado", + "Diamond": "Diamond", + "Silver Mk.II": "Silver Mk.II", + "Gold Mk.II": "Gold Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Killstreak Kit", + "kt-1": "Standard", + "kt-2": "Specialized", + "kt-3": "Professional", +} \ No newline at end of file diff --git a/src/strings/zh-Hant.js b/src/strings/zh-Hant.js new file mode 100644 index 0000000..485fd95 --- /dev/null +++ b/src/strings/zh-Hant.js @@ -0,0 +1,44 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "View listings on %@", + + // Itembox header + "Community Pricing": "Community Pricing", + // Itembox footer + "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot + "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + + // Price strings + "Data unavailable": "Data unavailable", // sourced from AppleGlot + "%@ ref": "%@ ref", + "%@ key": "%@ key", + "%@ keys": "%@ keys", + + // Item quality names, all sourced from TF2 wiki + "Normal": "Normal", + "Genuine": "Genuine", + "Vintage": "Vintage", + "Unique": "Unique", + "Strange": "Strange", + "Collector's": "Collector's", + "Haunted": "Haunted", + "Australium": "Australium", + "Festive": "Festive", + + // Botkiller names, all sourced from TF2 wiki + "Botkiller": "Botkiller", + "Silver": "Silver", + "Gold": "Gold", + "Rust": "Rust", + "Blood": "Blood", + "Carbonado": "Carbonado", + "Diamond": "Diamond", + "Silver Mk.II": "Silver Mk.II", + "Gold Mk.II": "Gold Mk.II", + + // Killstreak tiers sourced from TF2 wiki + "Killstreak Kit": "Killstreak Kit", + "kt-1": "Standard", + "kt-2": "Specialized", + "kt-3": "Professional", +} \ No newline at end of file From edc969d03747964893387e2a63b98784d83fd05d Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 24 Apr 2025 17:07:49 -0400 Subject: [PATCH 05/43] lint: prepare eslint --- bun.lockb | Bin 166910 -> 196987 bytes package.json | 6 ++++++ src/eslint.config.mjs | 17 +++++++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 src/eslint.config.mjs diff --git a/bun.lockb b/bun.lockb index 5aa90dda8ed1494f26e2cce6714a8d1e79ee50d6..c24e42ba28a65cb7ca74cc71392c880a763064e8 100755 GIT binary patch delta 46103 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 delta 27568 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

1^*PY7AC^m+=uo z&6TpX1E>(n&2Q#5zp!@yN}glI0n5&3PmQIBGS3fMX~HOdnRy;{JI zyQz^z7qIu-)J7jIWSujpF<3=|Hf~$AdHwE5%19*q8-WK3Sbioo(ji4`16EYwqu&fZ z{=sce{QV)>#iajKk#)_aLG(}&i_84OW@S77oiVmIlLn=zpw&kkE>3)B)OD8>O+srN z?cBZ7uY{LnoskS$lR!w;NQI<(8El|X!77GzKl}UbpYZ)tEj< zOGi$6b#<$~_|G{XQTFmUY7FL)t^vH}2%l{ewwtji*L>c{U3gPi?eu z4jVU~8pVsifKaOqRM7064NJ^X+s|&{8p(B78YA(tki9ydc8^l83ROI76`hw)&P3cN zH6R6FsLS!#)pLhS@b-OBBr|&^FaRN0F{$ebEEuBC^#Nj#7tio%*5uHB!Ec>*Y zOMt(~iD8$p&Q9i-NKLHwL>eTX6UpPOaw0X+thwxtiD<0L*wYiKk=B>8cQEZ=&N^jL z8x1IDL$aumx++*#oVhB{_A_z!zvsv{su!k-yaOXfQ5%|uCDW*pbstTGf>nHO6>Yec zvvrlsGMZ)};l-n=F<3?DUgaD6K<3WJACbzD2C%t`?H?@&28JdA{8%lF_|BI$E!bB2 zx`&b_sra%v0mQpF^-8X!hV8~Kgx9hUvSD9kL{mC16ac(8XT-5Sif}1kUO-`GbwU(Z7Cp%8R?;V_D`&Kyq+1esg1Uu&oZ)U zNwA8A{YqXU{q(`tgi0mhH2g~gdo`OHgH?F$;~kf;jqmhxe{rZpD|N7e(aF>ntO9&* zJ~_R;Gitz_Qt49wd={_?lc}-izw`IP0=9TE?R^W*{TB*>9cy5JL+UDOw|P|U^Yvj- z|3k^s3xyNg8w=TYIkabcTvB}&u--Y;7OY}`A3PZ4I5eYlDB&)$!%Ob4plJCl43~&63=vBDy({A_d&KN50|~sYW2J; zSZE$Krl^4H*SFf6;%*zzpAz)j!k-GwzUkq9mwx)9U&lFW=M%kX4I4Cr8l5VX``0@kO4yqD z>(16ro#j`_cx5gw^@MMyr=qs5psdUy{-_SyJd>ic7kg$VwVK1Hi@o>q)Z~{*L+2Y4 zWX+Pbly%IfPF9*vpFW&cKqIEHvv1ST4&|Prn#%Hmx}rJkUr*67?2E6dndR-H9azFH z+La}~Ln98q`#Qaou)%lI&WB$-K^^39_B-^^4$S^3P3p=2bOZj41^gFZRZvr0QFnOm zr?k+A&3%q0vEM$U2Db2ZI*^e{8qDULq1Eibvve_A_!jMS`13RLO~PLNf{wrMQ`(Mg XJ3|B5k>_aC;gB!s5aYd{`fU0?e*Ciw diff --git a/package.json b/package.json index bdf0a82..56a8ecb 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,9 @@ "version": "0.7.1", "description": "Adds item pricing to the Team Fortress 2 wiki", "devDependencies": { + "@eslint/css": "^0.7.0", + "@eslint/js": "^9.25.1", + "@eslint/json": "^0.12.0", "@happy-dom/global-registrator": "^17.4.4", "@types/firefox-webext-browser": "^120.0.4", "@types/greasemonkey": "^4.0.7", @@ -12,10 +15,13 @@ "buffer": "^6.0.3", "bun-types": "^1.2.5", "copy-webpack-plugin": "^12.0.2", + "eslint": "^9.25.1", + "globals": "^16.0.0", "path-browserify": "^1.0.1", "raw-loader": "^4.0.2", "tf2-static-schema": "^1.74.0", "ts-loader": "^9.5.1", + "typescript-eslint": "^8.31.0", "webpack": "^5.94.0", "webpack-cli": "^5.1.4" }, diff --git a/src/eslint.config.mjs b/src/eslint.config.mjs new file mode 100644 index 0000000..d3cb68f --- /dev/null +++ b/src/eslint.config.mjs @@ -0,0 +1,17 @@ +import js from "@eslint/js"; +import globals from "globals"; +import tseslint from "typescript-eslint"; +import json from "@eslint/json"; +import css from "@eslint/css"; +import { defineConfig } from "eslint/config"; + + +export default defineConfig([ + { ignores: ["**/GM_fetch/**/*.js"] }, + { files: ["**/*.{js,mjs,cjs,ts}"], plugins: { js }, extends: ["js/recommended"] }, + { files: ["**/*.{js,mjs,cjs,ts}"], languageOptions: { globals: globals.browser } }, + tseslint.configs.recommended, + { files: ["**/*.json"], plugins: { json }, language: "json/json", extends: ["json/recommended"] }, + { files: ["**/*.jsonc"], plugins: { json }, language: "json/jsonc", extends: ["json/recommended"] }, + { files: ["**/*.css"], plugins: { css }, language: "css/css", extends: ["css/recommended"] }, +]); \ No newline at end of file From d71cfcd0d74bd3b7e81c68ac6197d655d86cccf7 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 24 Apr 2025 17:35:04 -0400 Subject: [PATCH 06/43] lint: change export style in localization files --- src/strings/ar.js | 2 +- src/strings/cs.js | 2 +- src/strings/da.js | 2 +- src/strings/de.js | 2 +- src/strings/en.js | 2 +- src/strings/es.js | 2 +- src/strings/fi.js | 2 +- src/strings/fr.js | 2 +- src/strings/hu.js | 2 +- src/strings/it.js | 2 +- src/strings/ja.js | 2 +- src/strings/ko.js | 2 +- src/strings/nl.js | 2 +- src/strings/no.js | 2 +- src/strings/pl.js | 2 +- src/strings/pt-BR.js | 2 +- src/strings/pt.js | 2 +- src/strings/ro.js | 2 +- src/strings/ru.js | 2 +- src/strings/sv.js | 2 +- src/strings/tr.js | 2 +- src/strings/zh-Hans.js | 2 +- src/strings/zh-Hant.js | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/strings/ar.js b/src/strings/ar.js index 485fd95..514a049 100644 --- a/src/strings/ar.js +++ b/src/strings/ar.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/cs.js b/src/strings/cs.js index 485fd95..514a049 100644 --- a/src/strings/cs.js +++ b/src/strings/cs.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/da.js b/src/strings/da.js index 485fd95..514a049 100644 --- a/src/strings/da.js +++ b/src/strings/da.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/de.js b/src/strings/de.js index 485fd95..514a049 100644 --- a/src/strings/de.js +++ b/src/strings/de.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/en.js b/src/strings/en.js index 485fd95..514a049 100644 --- a/src/strings/en.js +++ b/src/strings/en.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/es.js b/src/strings/es.js index 112bb59..b41fb6b 100644 --- a/src/strings/es.js +++ b/src/strings/es.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "Ver ofertas en %@", diff --git a/src/strings/fi.js b/src/strings/fi.js index 485fd95..514a049 100644 --- a/src/strings/fi.js +++ b/src/strings/fi.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/fr.js b/src/strings/fr.js index 485fd95..514a049 100644 --- a/src/strings/fr.js +++ b/src/strings/fr.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/hu.js b/src/strings/hu.js index 485fd95..514a049 100644 --- a/src/strings/hu.js +++ b/src/strings/hu.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/it.js b/src/strings/it.js index 4d070d7..7cd41be 100644 --- a/src/strings/it.js +++ b/src/strings/it.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "Voir les offres sur %@", diff --git a/src/strings/ja.js b/src/strings/ja.js index 1fa3eab..df06436 100644 --- a/src/strings/ja.js +++ b/src/strings/ja.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "%@で検索結果を見る", diff --git a/src/strings/ko.js b/src/strings/ko.js index 485fd95..514a049 100644 --- a/src/strings/ko.js +++ b/src/strings/ko.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/nl.js b/src/strings/nl.js index 485fd95..514a049 100644 --- a/src/strings/nl.js +++ b/src/strings/nl.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/no.js b/src/strings/no.js index 485fd95..514a049 100644 --- a/src/strings/no.js +++ b/src/strings/no.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/pl.js b/src/strings/pl.js index 485fd95..514a049 100644 --- a/src/strings/pl.js +++ b/src/strings/pl.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/pt-BR.js b/src/strings/pt-BR.js index 485fd95..514a049 100644 --- a/src/strings/pt-BR.js +++ b/src/strings/pt-BR.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/pt.js b/src/strings/pt.js index 485fd95..514a049 100644 --- a/src/strings/pt.js +++ b/src/strings/pt.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/ro.js b/src/strings/ro.js index 485fd95..514a049 100644 --- a/src/strings/ro.js +++ b/src/strings/ro.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/ru.js b/src/strings/ru.js index f5b9a54..a46cdbe 100644 --- a/src/strings/ru.js +++ b/src/strings/ru.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "Просмотреть объявления на %@", diff --git a/src/strings/sv.js b/src/strings/sv.js index 485fd95..514a049 100644 --- a/src/strings/sv.js +++ b/src/strings/sv.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/tr.js b/src/strings/tr.js index 485fd95..514a049 100644 --- a/src/strings/tr.js +++ b/src/strings/tr.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/zh-Hans.js b/src/strings/zh-Hans.js index 485fd95..514a049 100644 --- a/src/strings/zh-Hans.js +++ b/src/strings/zh-Hans.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/zh-Hant.js b/src/strings/zh-Hant.js index 485fd95..514a049 100644 --- a/src/strings/zh-Hant.js +++ b/src/strings/zh-Hant.js @@ -1,4 +1,4 @@ -module.exports = { +export default { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", From df3b40fbdddeb9a7381d86529d61e2a9dd81458b Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 24 Apr 2025 17:38:02 -0400 Subject: [PATCH 07/43] lint: `var` to `let`/`const` --- src/content/content.ts | 42 +++++++++++++++--------------- src/content/exchangeRateService.ts | 6 ++--- src/content/priceService.ts | 2 +- src/content/pricing/pricestf.ts | 2 +- src/content/schemaService.ts | 16 ++++++------ src/content/storage.ts | 4 +-- src/content/uiRenderer.ts | 6 ++--- src/content/utils/formatting.ts | 4 +-- src/content/utils/localization.ts | 2 +- src/content/utils/log.ts | 3 +-- src/content/utils/url.ts | 2 +- 11 files changed, 44 insertions(+), 45 deletions(-) diff --git a/src/content/content.ts b/src/content/content.ts index 5f2b11e..051b6e9 100644 --- a/src/content/content.ts +++ b/src/content/content.ts @@ -10,10 +10,10 @@ import { createPriceRow, createStoreButton } from './uiRenderer' import { findFirstElement, findFirstChildElement } from './utils/dom' import { extractPageTitleFromURL } from './utils/url'; import { ExchangeRates, prepareExchangeRates } from './exchangeRateService'; -var itemSchema: ItemSchema | null; -var exchangeRates: ExchangeRates | null; +let itemSchema: ItemSchema | null; +let exchangeRates: ExchangeRates | null; -var locale: string = 'en' +let locale: string = 'en' /** Exclude these from the pricelist. */ const excludedQualities = new Set([ @@ -33,8 +33,8 @@ async function inject() { // Not an item page return; } - var itemIndex: number | null = null; - var itemName: string | null = null; + let itemIndex: number | null = null; + let itemName: string | null = null; // Find buy buttons const buyButton = findFirstChildElement('.btn_buynow', itemInfobox); @@ -94,7 +94,7 @@ async function inject() { return; } - var qualities: number[] = [] + const qualities: number[] = [] const firstQualityTag = findFirstChildElement('.quality-tag', itemInfobox); @@ -122,7 +122,7 @@ async function inject() { // th.infobox-header (Basic Information) // ... - var storeButtons: HTMLTableRowElement[] = []; + const storeButtons: HTMLTableRowElement[] = []; // backpack.tf button storeButtons.push(createStoreButton("backpack.tf", new URL(`https://backpack.tf/classifieds?item=${encodeURIComponent(itemName)}`))); @@ -171,7 +171,7 @@ async function inject() { priceProgressRow.appendChild(priceProgressData); priceInfoboxHeadingRow.insertAdjacentElement('afterend', priceProgressRow); - var token: string | null; + 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. @@ -187,7 +187,7 @@ async function inject() { log('Failed to get an access token for prices.tf: ' + err); } - var updateTime: Date | null = null; + let updateTime: Date | null = null; enum PriceRowCategory { None, @@ -201,19 +201,19 @@ async function inject() { row: HTMLTableRowElement category: PriceRowCategory } - var priceRows: PriceRow[]= []; + const priceRows: PriceRow[]= []; // Get current key price const keyPrice = await fetchKeyPrice(token); - var currentTime = new Date() + const currentTime = new Date() const promises = qualities.filter(x => !excludedQualities.has(x)).map(async (quality) => { const qualifiedName = ((quality != 6 ? itemQualities[quality as unknown as keyof typeof itemQualities].toString() : '') + ' ' + itemName).trim() // logDebug(`Fetching price for ${qualifiedName}`) - var data: ItemPriceData | null + let data: ItemPriceData | null try { data = await fetchPrice(token, itemIndex + ";" + quality, currentTime); updateTime = new Date(data.update) @@ -231,7 +231,7 @@ async function inject() { if(itemSchema[itemIndex].hasAustraliumVariant) { promises.push(new Promise(async (resolve) => { logDebug(`Fetching price for Australium ${itemName}`) - var data: ItemPriceData | null + let data: ItemPriceData | null try { data = await fetchPrice(token, `${itemIndex};11;australium`, currentTime); updateTime = new Date(data.update) @@ -247,7 +247,7 @@ async function inject() { })) } - var festiveHeadingRow: HTMLTableRowElement | null + let festiveHeadingRow: HTMLTableRowElement | null // Check item schema for Festive variant of current defindex if(itemSchema[itemIndex].festiveVariant != null) { /// Create subheading @@ -263,7 +263,7 @@ async function inject() { promises.push(new Promise(async (resolve) => { logDebug(`Fetching price for Festive ${itemName}`) - var data: ItemPriceData | null + let data: ItemPriceData | null try { data = await fetchPrice(token, `${itemSchema[itemIndex].festiveVariant};6`, currentTime); updateTime = new Date(data.update) @@ -279,7 +279,7 @@ async function inject() { })) promises.push(new Promise(async (resolve) => { logDebug(`Fetching price for Strange Festive ${itemName}`) - var data: ItemPriceData | null + let data: ItemPriceData | null try { data = await fetchPrice(token, `${itemSchema[itemIndex].festiveVariant};11`, currentTime); updateTime = new Date(data.update) @@ -295,7 +295,7 @@ async function inject() { })) } - var killstreakKitHeadingRow: HTMLTableRowElement | null + let killstreakKitHeadingRow: HTMLTableRowElement | null // Check for Killstreak Kits if(itemSchema[itemIndex].slot == ItemSlot.Primary || itemSchema[itemIndex].slot == ItemSlot.Secondary || @@ -314,9 +314,9 @@ async function inject() { [1,2,3].map((tier) => { promises.push(new Promise(async (resolve) => { logDebug(`Fetching price for ${itemName} Killstreak Kit Tier ${tier}`) - var data: ItemPriceData | null + let data: ItemPriceData | null try { - var kitIndex: number + let kitIndex: number switch (tier) { default: case 1: kitIndex = 6527; break; @@ -351,7 +351,7 @@ async function inject() { "Silver Mk.II", "Gold Mk.II", ] - var botKillerHeadingRow: HTMLTableRowElement | null + let botKillerHeadingRow: HTMLTableRowElement | null if(itemSchema[itemIndex].botkillerVariants != null && itemSchema[itemIndex].botkillerVariants.length > 0) { /// Create subheading botKillerHeadingRow = document.createElement("tr") @@ -370,7 +370,7 @@ async function inject() { const variantName = itemName.includes('Mk.II') ? itemName.split(' ')[0] + ' Mk.II' : itemName.split(' ')[0] promises.push(new Promise(async (resolve) => { logDebug(`Fetching price for ${itemName}`) - var data: ItemPriceData | null + let data: ItemPriceData | null try { data = await fetchPrice(token, `${variantIndex};11`, currentTime); updateTime = new Date(data.update) diff --git a/src/content/exchangeRateService.ts b/src/content/exchangeRateService.ts index fe3a24a..5b41de7 100644 --- a/src/content/exchangeRateService.ts +++ b/src/content/exchangeRateService.ts @@ -16,8 +16,8 @@ export async function wipeExchangeRates(): Promise { } export async function prepareExchangeRates(): Promise { - var needsUpdate: Boolean = false - var rates: ExchangeRates | null = null + let needsUpdate: boolean = false + let rates: ExchangeRates | null = null rates = await getStorageValue(storage_exchangerates, null); const update = await getStorageValue(storage_exchangerates_update, null) @@ -39,7 +39,7 @@ export async function prepareExchangeRates(): Promise { const response = await GM_fetch(url); if (response.ok) { await setStorageValue(storage_exchangerates_update, new Date().toISOString()) - var json = await response.json() + const json = await response.json() if(json != null){ rates = json['rates'] await setStorageValue(storage_exchangerates, rates) diff --git a/src/content/priceService.ts b/src/content/priceService.ts index 5aa675d..a7fa811 100644 --- a/src/content/priceService.ts +++ b/src/content/priceService.ts @@ -41,7 +41,7 @@ export async function fetchKeyPrice(token: string) { */ export async function fetchPrice(token: string, sku: string, update: Date = new Date(), ttl: number = 30 * 60 * 1000): Promise { return new Promise(async (resolve, reject) => { - var data: ItemPriceData | null + let data: ItemPriceData | null const cached: ItemPriceData = await getStorageValue(storage_priceprefix + sku, null) if (cached != null && 'keys' in cached && 'metal' in cached && !isNaN(cached.update)) { diff --git a/src/content/pricing/pricestf.ts b/src/content/pricing/pricestf.ts index 1d6989d..4cec571 100644 --- a/src/content/pricing/pricestf.ts +++ b/src/content/pricing/pricestf.ts @@ -46,7 +46,7 @@ async function priceUsingPricesTF(token: string, sku: string, retries: number = if (!token) { reject(401) } - var response = await GM_fetch(`https://api2.prices.tf/prices/${encodeURIComponent(sku)}`, { + let response = await GM_fetch(`https://api2.prices.tf/prices/${encodeURIComponent(sku)}`, { method: 'get', headers: new Headers({ 'Accept': 'application/json', diff --git a/src/content/schemaService.ts b/src/content/schemaService.ts index 0a95ca8..eff2feb 100644 --- a/src/content/schemaService.ts +++ b/src/content/schemaService.ts @@ -14,8 +14,8 @@ export function checkAustraliumVariant(defindex: number): boolean { export declare const __VERSION__: string; function isDateAfterOneDay(date1: Date, date2: Date): boolean { - var diff = date2.getTime() - date1.getTime(); - var diffDays = Math.round(diff / (1000 * 3600 * 24)); + const diff = date2.getTime() - date1.getTime(); + const diffDays = Math.round(diff / (1000 * 3600 * 24)); return diffDays > 1; } @@ -119,8 +119,8 @@ export async function wipeSchema(): Promise { } export async function prepareSchema(): Promise { - var needsUpdate: Boolean = false - var itemSchema: ItemSchema | null = null + let needsUpdate: boolean = false + let itemSchema: ItemSchema | null = null const storedVersion: string | null = await getStorageValue(storage_version, null) if(!storedVersion || !semver.valid(storedVersion)) { @@ -150,14 +150,14 @@ export async function prepareSchema(): Promise { if (response.ok) { await setStorageValue(storage_lastUpdateTime, new Date().getTime()); - var cacheItems = {} + const cacheItems = {} - var responseItems: any[] = await response.json() + const responseItems: any[] = await response.json() // We want to keep the keys `defindex`, `item_name`, and `attributes` responseItems.forEach((item: any) => { const defindex: number = item['defindex'] - var tradable: Boolean = true + let tradable: boolean = true try { if(item['attributes'] != null) { if(item['attributes'].find((attribute: {}) => (attribute as any)['class'] == "cannot_trade")) { @@ -169,7 +169,7 @@ export async function prepareSchema(): Promise { log(item) } - var canKillstreakify: Boolean = false + let canKillstreakify: boolean = false try { if(item['capabilities'] != null) { if(item['capabilities']['can_killstreakify'] != null && item['capabilities']['can_killstreakify'] == true) { diff --git a/src/content/storage.ts b/src/content/storage.ts index d14fea8..1707b91 100644 --- a/src/content/storage.ts +++ b/src/content/storage.ts @@ -1,5 +1,5 @@ -declare var __ENV_USERSCRIPT: boolean; -declare var __ENV_WEBEXTENSION: boolean; +declare let __ENV_USERSCRIPT: boolean; +declare let __ENV_WEBEXTENSION: boolean; function getStorageValue(name: string, defaultValue: string): Promise { if(__ENV_USERSCRIPT) { diff --git a/src/content/uiRenderer.ts b/src/content/uiRenderer.ts index f555759..3f60e3d 100644 --- a/src/content/uiRenderer.ts +++ b/src/content/uiRenderer.ts @@ -22,14 +22,14 @@ export function createPriceRow(qualityName: string, data: ItemPriceData, keyPric const priceData = document.createElement("td"); const priceLink = document.createElement("span"); - var priceString: string = '' + let priceString: string = '' if(data) { const gamePrice = formatPrice(data.keys, data.metal, keyPrice.metal, locale).trim() const realPriceUSD = convertTF2PriceToUSD(data.keys, data.metal, keyPrice.metal) const USDFormatter = new Intl.NumberFormat(locale, { style: "currency", currency: 'USD' }) - var realPriceString = USDFormatter.format(realPriceUSD) + let realPriceString = USDFormatter.format(realPriceUSD) const currency = defaultCurrencyForPageLocale(locale) ?? 'USD' if(currency !== 'USD') { try { @@ -56,7 +56,7 @@ export function createPriceRow(qualityName: string, data: ItemPriceData, keyPric export function createStoreButton(storeName: string, url: URL) { const button = document.createElement("tr") - var source = `

` + let source = `` source = source.replace("{link}", url.toString()) source = source.replace("{title}", $T("View listings on %@").replace('%@', storeName)) button.innerHTML = source diff --git a/src/content/utils/formatting.ts b/src/content/utils/formatting.ts index 4c27a1e..391c98c 100644 --- a/src/content/utils/formatting.ts +++ b/src/content/utils/formatting.ts @@ -2,14 +2,14 @@ import { conversion_ref_usd } from '../config'; import { $T } from './localization' function toFixed(num: number, fixed: number) { - var re = new RegExp('^-?\\d+(?:\.\\d{0,' + (fixed || -1) + '})?'); + const re = new RegExp('^-?\\d+(?:\.\\d{0,' + (fixed || -1) + '})?'); return num.toString().match(re)[0]; } export function formatPrice(keys: number, metal: number, keyPrice: number, locale: string = 'en') { const formattedKeys = +(keys + (metal / keyPrice)).toFixed(2) - var output: string = '' + let output: string = '' if(keys > 0) { output += (formattedKeys == 1.0 ? $T("%@ key") : $T("%@ keys")).replace('%@', formattedKeys.toLocaleString(locale)) } else { diff --git a/src/content/utils/localization.ts b/src/content/utils/localization.ts index 7ef2e1b..5d21dfc 100644 --- a/src/content/utils/localization.ts +++ b/src/content/utils/localization.ts @@ -30,7 +30,7 @@ export function $T(s: string, locale?: Intl.LocalesArgument): string { } export function extractLocaleFromURL(url: string): string { - var split = url.substring(url.indexOf("/wiki/") + "/wiki/".length); + const split = url.substring(url.indexOf("/wiki/") + "/wiki/".length); if (split.indexOf('/') != -1) { // Remove language suffix e.g. `/es` return split.substring(split.indexOf('/') + 1); diff --git a/src/content/utils/log.ts b/src/content/utils/log.ts index 23ababe..49fbe16 100644 --- a/src/content/utils/log.ts +++ b/src/content/utils/log.ts @@ -1,5 +1,4 @@ -declare var __PRODUCTION: boolean; -declare var __EXTENSION_NAME: string; +declare let __EXTENSION_NAME: string; const logHeader = `[${__EXTENSION_NAME}] `; /** `console.debug` with header; automatically NO-OP on production build */ diff --git a/src/content/utils/url.ts b/src/content/utils/url.ts index 3aa388c..566fd4f 100644 --- a/src/content/utils/url.ts +++ b/src/content/utils/url.ts @@ -1,5 +1,5 @@ export function extractPageTitleFromURL(url: string): string { - var split = url.substring(url.indexOf("/wiki/") + "/wiki/".length); + let split = url.substring(url.indexOf("/wiki/") + "/wiki/".length); if (split.indexOf('/') != -1) { // Remove language suffix (/es) split = split.substring(0, split.indexOf('/')); From d449f73e0c525398e8183d7ec02cfdcf0cec7e25 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 24 Apr 2025 17:38:21 -0400 Subject: [PATCH 08/43] lint: use `boolean` --- src/content/schemaService.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/content/schemaService.ts b/src/content/schemaService.ts index eff2feb..c881960 100644 --- a/src/content/schemaService.ts +++ b/src/content/schemaService.ts @@ -36,15 +36,15 @@ export class ItemSchema { [key: string]: { name: string, slot: ItemSlot, - tradable: Boolean, - hasAustraliumVariant: Boolean, + tradable: boolean, + hasAustraliumVariant: boolean, festiveVariant: number | null botkillerVariants: Array | null - canKillstreakify: Boolean + canKillstreakify: boolean }; } -export function getItemIndexByName(schema: ItemSchema, name: string, excludeStock: Boolean = true, excludeDecorated: Boolean = true) { +export function getItemIndexByName(schema: ItemSchema, name: string, excludeStock: boolean = true, excludeDecorated: boolean = true) { for (const [defindex, value] of Object.entries(schema)) { if (value['name'] == name) { const index = parseInt(defindex) @@ -60,7 +60,7 @@ export function getTradableStatusByDefindex(schema: ItemSchema, defindex: number return schema[defindex.toString()].tradable } -export function getTradableStatusByName(schema: ItemSchema, name: string, excludeStock: Boolean = true, excludeDecorated = true,) { +export function getTradableStatusByName(schema: ItemSchema, name: string, excludeStock: boolean = true, excludeDecorated = true,) { for (const [defindex, value] of Object.entries(schema)) { if (value['name'] == name) { const index = parseInt(defindex) From 4b6198fcaa01785609c06d702e6db016204234d1 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 24 Apr 2025 17:38:34 -0400 Subject: [PATCH 09/43] lint: clarify types on log functions --- src/content/utils/log.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/content/utils/log.ts b/src/content/utils/log.ts index 49fbe16..ba32c97 100644 --- a/src/content/utils/log.ts +++ b/src/content/utils/log.ts @@ -2,17 +2,17 @@ declare let __EXTENSION_NAME: string; const logHeader = `[${__EXTENSION_NAME}] `; /** `console.debug` with header; automatically NO-OP on production build */ -function logDebug(message?: any, ...optionalParams: any[]): void { +function logDebug(message?: string, ...optionalParams: Array): void { if(process.env.NODE_ENV !== 'production') console.debug(logHeader + message, optionalParams); } /** `console.log` with header */ -function log(message?: any, ...optionalParams: any[]): void { +function log(message?: string, ...optionalParams: Array): void { console.log(logHeader + message, optionalParams) } /** `console.error` with header */ -function logError(message?: any, ...optionalParams: any[]): void { +function logError(message?: string, ...optionalParams: Array): void { console.error(logHeader + message, optionalParams) } From 2dc51d1a001289389e028ee05045df6b35c5fcc9 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 24 Apr 2025 17:38:42 -0400 Subject: [PATCH 10/43] lint: remove unused import --- src/content/utils/formatting.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/content/utils/formatting.ts b/src/content/utils/formatting.ts index 391c98c..9255d90 100644 --- a/src/content/utils/formatting.ts +++ b/src/content/utils/formatting.ts @@ -1,4 +1,3 @@ -import { conversion_ref_usd } from '../config'; import { $T } from './localization' function toFixed(num: number, fixed: number) { From a3d67b13589041943279f8f7a52506e00bfa4d5d Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 24 Apr 2025 17:55:00 -0400 Subject: [PATCH 11/43] lint: remove dead code --- src/content/content.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/content/content.ts b/src/content/content.ts index 051b6e9..abcd48a 100644 --- a/src/content/content.ts +++ b/src/content/content.ts @@ -1,7 +1,7 @@ import styleCss from './style.css' import { logDebug, log, logError } from './utils/log' -import { getPricesToken, priceUsingPricesTF } from './pricing/pricestf' +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' @@ -52,8 +52,6 @@ async function inject() { } } - const url = document.URL; - if (itemName && !itemIndex) { itemIndex = getItemIndexByName(itemSchema, itemName) } From b83e66d14532021e12049dbcc14e7c0988125f11 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 24 Apr 2025 17:55:10 -0400 Subject: [PATCH 12/43] lint: allow require imports --- src/eslint.config.mjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/eslint.config.mjs b/src/eslint.config.mjs index d3cb68f..d9f0b41 100644 --- a/src/eslint.config.mjs +++ b/src/eslint.config.mjs @@ -9,8 +9,13 @@ import { defineConfig } from "eslint/config"; export default defineConfig([ { ignores: ["**/GM_fetch/**/*.js"] }, { files: ["**/*.{js,mjs,cjs,ts}"], plugins: { js }, extends: ["js/recommended"] }, - { files: ["**/*.{js,mjs,cjs,ts}"], languageOptions: { globals: globals.browser } }, tseslint.configs.recommended, + { + rules: { + "@typescript-eslint/no-require-imports": "off" + }, + languageOptions: { globals: globals.browser } + }, { files: ["**/*.json"], plugins: { json }, language: "json/json", extends: ["json/recommended"] }, { files: ["**/*.jsonc"], plugins: { json }, language: "json/jsonc", extends: ["json/recommended"] }, { files: ["**/*.css"], plugins: { css }, language: "css/css", extends: ["css/recommended"] }, From f6499adc4ab4e5c3268a2acae127610a179c28fb Mon Sep 17 00:00:00 2001 From: xenticore Date: Mon, 28 Apr 2025 23:10:51 -0400 Subject: [PATCH 13/43] lint: correct promise types --- src/content/pricing/pricestf.ts | 2 +- src/content/storage.ts | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/content/pricing/pricestf.ts b/src/content/pricing/pricestf.ts index 4cec571..ea843e0 100644 --- a/src/content/pricing/pricestf.ts +++ b/src/content/pricing/pricestf.ts @@ -3,7 +3,7 @@ import '../GM_fetch' import { logDebug } from '../utils/log' async function getPricesToken(): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { GM_fetch('https://api2.prices.tf/auth/access', { method: 'post', headers: new Headers({ diff --git a/src/content/storage.ts b/src/content/storage.ts index 1707b91..756df98 100644 --- a/src/content/storage.ts +++ b/src/content/storage.ts @@ -1,25 +1,27 @@ declare let __ENV_USERSCRIPT: boolean; declare let __ENV_WEBEXTENSION: boolean; +// eslint-disable-next-line @typescript-eslint/no-explicit-any function getStorageValue(name: string, defaultValue: string): Promise { if(__ENV_USERSCRIPT) { return GM.getValue(name, defaultValue); } else if(__ENV_WEBEXTENSION) { return browser.storage.local.get(name); } else { + // eslint-disable-next-line @typescript-eslint/no-explicit-any return new Promise((resolve) => { resolve(defaultValue); }); } } -function setStorageValue(name: string, value: any): Promise { +function setStorageValue(name: string, value: unknown): Promise { if(__ENV_USERSCRIPT) { - return GM.setValue(name, value); + return GM.setValue(name, value as GM.Value); } else if(__ENV_WEBEXTENSION) { return browser.storage.local.set({name, value}); } else { - return new Promise((resolve, reject) => { + return new Promise((_, reject) => { reject(); }); } From 96f4dbd8837621371a2ccc48343406b51b2091dd Mon Sep 17 00:00:00 2001 From: xenticore Date: Mon, 28 Apr 2025 23:16:34 -0400 Subject: [PATCH 14/43] lint: remove unnecessary escape in regex --- src/content/utils/formatting.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/utils/formatting.ts b/src/content/utils/formatting.ts index 9255d90..0418a18 100644 --- a/src/content/utils/formatting.ts +++ b/src/content/utils/formatting.ts @@ -1,7 +1,7 @@ import { $T } from './localization' function toFixed(num: number, fixed: number) { - const re = new RegExp('^-?\\d+(?:\.\\d{0,' + (fixed || -1) + '})?'); + const re = new RegExp('^-?\\d+(?:.\\d{0,' + (fixed || -1) + '})?'); return num.toString().match(re)[0]; } From 60e845ddf87bfac0eae76d864bcf99bf1f4b06b1 Mon Sep 17 00:00:00 2001 From: xenticore Date: Tue, 29 Apr 2025 00:14:33 -0400 Subject: [PATCH 15/43] Revert "lint: change export style in localization files" This reverts commit d71cfcd0d74bd3b7e81c68ac6197d655d86cccf7. --- src/strings/ar.js | 2 +- src/strings/cs.js | 2 +- src/strings/da.js | 2 +- src/strings/de.js | 2 +- src/strings/en.js | 2 +- src/strings/es.js | 2 +- src/strings/fi.js | 2 +- src/strings/fr.js | 2 +- src/strings/hu.js | 2 +- src/strings/it.js | 2 +- src/strings/ja.js | 2 +- src/strings/ko.js | 2 +- src/strings/nl.js | 2 +- src/strings/no.js | 2 +- src/strings/pl.js | 2 +- src/strings/pt-BR.js | 2 +- src/strings/pt.js | 2 +- src/strings/ro.js | 2 +- src/strings/ru.js | 2 +- src/strings/sv.js | 2 +- src/strings/tr.js | 2 +- src/strings/zh-Hans.js | 2 +- src/strings/zh-Hant.js | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/strings/ar.js b/src/strings/ar.js index 514a049..485fd95 100644 --- a/src/strings/ar.js +++ b/src/strings/ar.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/cs.js b/src/strings/cs.js index 514a049..485fd95 100644 --- a/src/strings/cs.js +++ b/src/strings/cs.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/da.js b/src/strings/da.js index 514a049..485fd95 100644 --- a/src/strings/da.js +++ b/src/strings/da.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/de.js b/src/strings/de.js index 514a049..485fd95 100644 --- a/src/strings/de.js +++ b/src/strings/de.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/en.js b/src/strings/en.js index 514a049..485fd95 100644 --- a/src/strings/en.js +++ b/src/strings/en.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/es.js b/src/strings/es.js index b41fb6b..112bb59 100644 --- a/src/strings/es.js +++ b/src/strings/es.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "Ver ofertas en %@", diff --git a/src/strings/fi.js b/src/strings/fi.js index 514a049..485fd95 100644 --- a/src/strings/fi.js +++ b/src/strings/fi.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/fr.js b/src/strings/fr.js index 514a049..485fd95 100644 --- a/src/strings/fr.js +++ b/src/strings/fr.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/hu.js b/src/strings/hu.js index 514a049..485fd95 100644 --- a/src/strings/hu.js +++ b/src/strings/hu.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/it.js b/src/strings/it.js index 7cd41be..4d070d7 100644 --- a/src/strings/it.js +++ b/src/strings/it.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "Voir les offres sur %@", diff --git a/src/strings/ja.js b/src/strings/ja.js index df06436..1fa3eab 100644 --- a/src/strings/ja.js +++ b/src/strings/ja.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "%@で検索結果を見る", diff --git a/src/strings/ko.js b/src/strings/ko.js index 514a049..485fd95 100644 --- a/src/strings/ko.js +++ b/src/strings/ko.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/nl.js b/src/strings/nl.js index 514a049..485fd95 100644 --- a/src/strings/nl.js +++ b/src/strings/nl.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/no.js b/src/strings/no.js index 514a049..485fd95 100644 --- a/src/strings/no.js +++ b/src/strings/no.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/pl.js b/src/strings/pl.js index 514a049..485fd95 100644 --- a/src/strings/pl.js +++ b/src/strings/pl.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/pt-BR.js b/src/strings/pt-BR.js index 514a049..485fd95 100644 --- a/src/strings/pt-BR.js +++ b/src/strings/pt-BR.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/pt.js b/src/strings/pt.js index 514a049..485fd95 100644 --- a/src/strings/pt.js +++ b/src/strings/pt.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/ro.js b/src/strings/ro.js index 514a049..485fd95 100644 --- a/src/strings/ro.js +++ b/src/strings/ro.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/ru.js b/src/strings/ru.js index a46cdbe..f5b9a54 100644 --- a/src/strings/ru.js +++ b/src/strings/ru.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "Просмотреть объявления на %@", diff --git a/src/strings/sv.js b/src/strings/sv.js index 514a049..485fd95 100644 --- a/src/strings/sv.js +++ b/src/strings/sv.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/tr.js b/src/strings/tr.js index 514a049..485fd95 100644 --- a/src/strings/tr.js +++ b/src/strings/tr.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/zh-Hans.js b/src/strings/zh-Hans.js index 514a049..485fd95 100644 --- a/src/strings/zh-Hans.js +++ b/src/strings/zh-Hans.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", diff --git a/src/strings/zh-Hant.js b/src/strings/zh-Hant.js index 514a049..485fd95 100644 --- a/src/strings/zh-Hant.js +++ b/src/strings/zh-Hant.js @@ -1,4 +1,4 @@ -export default { +module.exports = { // Generic button text, %@ is always a URL (eg. backpack.tf) "View listings on %@": "View listings on %@", From 6fdc6fd132a931a6601756190c24c058cffbcb7c Mon Sep 17 00:00:00 2001 From: xenticore Date: Tue, 29 Apr 2025 00:49:32 -0400 Subject: [PATCH 16/43] refactor: rewrite async promise as promise chain --- src/content/pricing/pricestf.ts | 86 ++++++++++++++------------------- 1 file changed, 36 insertions(+), 50 deletions(-) diff --git a/src/content/pricing/pricestf.ts b/src/content/pricing/pricestf.ts index ea843e0..55e56da 100644 --- a/src/content/pricing/pricestf.ts +++ b/src/content/pricing/pricestf.ts @@ -1,6 +1,6 @@ declare function GM_fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise import '../GM_fetch' -import { logDebug } from '../utils/log' +import { logDebug, logError } from '../utils/log' async function getPricesToken(): Promise { return new Promise((resolve, reject) => { @@ -42,62 +42,48 @@ async function priceUsingPricesTF(token: string, sku: string, retries: number = // prices.tf // https://api2.prices.tf/prices/${sku} // Authorization: Bearer ${token} - return new Promise(async (resolve, reject) => { - if (!token) { - reject(401) + return GM_fetch(`https://api2.prices.tf/prices/${encodeURIComponent(sku)}`, { + method: 'get', + headers: { + 'Accept': 'application/json', + 'Authorization': `Bearer ${token}`, } - let response = await GM_fetch(`https://api2.prices.tf/prices/${encodeURIComponent(sku)}`, { - method: 'get', - headers: new Headers({ - 'Accept': 'application/json', - 'Authorization': `Bearer ${token}` - }) - }) - if (response.status === 404 && sku.includes(';')) { + }) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + 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 - response = await GM_fetch(`https://api2.prices.tf/prices/${encodeURIComponent(sku + ';uncraftable')}`, { - method: 'get', - headers: new Headers({ - 'Accept': 'application/json', - 'Authorization': `Bearer ${token}` - }) - }) + return priceUsingPricesTF(token, sku + ';uncraftable', retries); } } - switch (response.status) { - case 200: - const json = await response.json() - resolve({ keys: json['sellKeys'], metal: json['sellHalfScrap'] / 18.0 }) - break; - case 404: - reject("Not found in prices.tf") - break; - case 429: - case 503: - // Happens if we send too many requests in a short period of time - // Retry after a few seconds - if(retries > 0) { - logDebug(`Cloudflare rate limit exceeded, trying again after 2 seconds, ${retries} retries left`) - await new Promise(resolve => setTimeout(resolve, 2000)); - try { - const retryResult = await priceUsingPricesTF(token, sku, retries - 1); - resolve(retryResult); - } catch (err) { - reject(err); - } - } else { - reject("Cloudflare rate limit exceeded, stopping") - } - break; - default: - // Something went wrong - logDebug(`Received ${response.status} error while pricing ${sku}`) - logDebug(`${JSON.stringify(response.headers)}`) - reject("Unknown error") - break; + if(response.status === 404) { + throw new Error(`Item not found: ${sku}`); } + 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 2 seconds, ${retries} retries left`) + return priceUsingPricesTF(token, sku, retries - 1); + } else { + throw new Error(`Cloudflare rate limit exceeded, stopping`) + } + } + return response.json(); + }) + .then(data => { + 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; }) } From 726cc97b38dacb09cecd90ac3136f96eaaae7d85 Mon Sep 17 00:00:00 2001 From: xenticore Date: Tue, 29 Apr 2025 00:50:25 -0400 Subject: [PATCH 17/43] refactor: debuggable localization function --- src/content/utils/localization.ts | 51 ++++++++++++++++++------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/src/content/utils/localization.ts b/src/content/utils/localization.ts index 5d21dfc..4706b8b 100644 --- a/src/content/utils/localization.ts +++ b/src/content/utils/localization.ts @@ -1,32 +1,41 @@ -const localizations: {[lang: string]: any} = { +import { logDebug } 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') as object, // Arabic - 'cs': require('../../strings/cs') as object, // Czech - 'da': require('../../strings/da') as object, // Danish - 'de': require('../../strings/de') as object, // German - 'fi': require('../../strings/fi') as object, // Finnish - 'fr': require('../../strings/fr') as object, // French - 'hu': require('../../strings/hu') as object, // Hungarian - 'ko': require('../../strings/ko') as object, // Korean - 'nl': require('../../strings/nl') as object, // Dutch - 'no': require('../../strings/no') as object, // Norwegian Bokmål - 'pl': require('../../strings/pl') as object, // Polish - 'pt': require('../../strings/pt') as object, // Portuguese - 'pt-BR': require('../../strings/pt-BR') as object, // Brazilian Portuguese - 'ro': require('../../strings/ro') as object, // Romanian - 'ru': require('../../strings/ru') as object, // Russian - 'sv': require('../../strings/sv') as object, // Swedish - 'tr': require('../../strings/tr') as object, // Turkish - 'zh-Hans': require('../../strings/zh-Hans') as object, // Simplified Chinese - 'zh-Hant': require('../../strings/zh-Hant') as object, // Traditional Chinese + 'ar': require('../../strings/ar'), // Arabic + 'cs': require('../../strings/cs'), // Czech + 'da': require('../../strings/da'), // Danish + 'de': require('../../strings/de'), // German + 'fi': require('../../strings/fi'), // Finnish + 'fr': require('../../strings/fr'), // French + 'hu': require('../../strings/hu'), // Hungarian + '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 + 'ro': require('../../strings/ro'), // Romanian + 'ru': require('../../strings/ru'), // Russian + 'sv': require('../../strings/sv'), // Swedish + '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) - return localizations.hasOwnProperty(code) ? (localizations[code as unknown as keyof object])[s] || s : s; + if (code in localizations) { + const translation = localizations[code] as Record; + const result = translation[s] ?? s; + logDebug(`Translating "${s}" to locale "${code}": ${result}`); + return result; + } + logDebug(`Untranslated string "${s}" in locale "${code}`); + return s; } export function extractLocaleFromURL(url: string): string { From 1a973f6b523ef37f6ce3d4be888fafea9eb0007c Mon Sep 17 00:00:00 2001 From: xenticore Date: Tue, 29 Apr 2025 00:50:39 -0400 Subject: [PATCH 18/43] lint: stop linting JSON files --- src/eslint.config.mjs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/eslint.config.mjs b/src/eslint.config.mjs index d9f0b41..464108c 100644 --- a/src/eslint.config.mjs +++ b/src/eslint.config.mjs @@ -1,7 +1,6 @@ import js from "@eslint/js"; import globals from "globals"; import tseslint from "typescript-eslint"; -import json from "@eslint/json"; import css from "@eslint/css"; import { defineConfig } from "eslint/config"; @@ -16,7 +15,5 @@ export default defineConfig([ }, languageOptions: { globals: globals.browser } }, - { files: ["**/*.json"], plugins: { json }, language: "json/json", extends: ["json/recommended"] }, - { files: ["**/*.jsonc"], plugins: { json }, language: "json/jsonc", extends: ["json/recommended"] }, { files: ["**/*.css"], plugins: { css }, language: "css/css", extends: ["css/recommended"] }, ]); \ No newline at end of file From d93feafbb1a580b8af8bb5bb3feb451a99cfd18e Mon Sep 17 00:00:00 2001 From: xenticore Date: Tue, 29 Apr 2025 00:51:16 -0400 Subject: [PATCH 19/43] lint: define interface for remote schema response --- src/content/schemaService.ts | 46 ++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/content/schemaService.ts b/src/content/schemaService.ts index c881960..8404070 100644 --- a/src/content/schemaService.ts +++ b/src/content/schemaService.ts @@ -44,6 +44,44 @@ export class ItemSchema { }; } +interface SchemaResponseItem { + name: string; + defindex: number; + item_class: string; + item_type_name: string; + item_name: string; + item_description: string; + proper_name: boolean; + item_slot: ItemSlot; + model_player: string; + item_quality: number; + image_inventory: string; + min_ilevel: number; + max_ilevel: number; + image_url: string; + image_url_large: string; + drop_type: string; + craft_class: string; + craft_material_type: string; + capabilities: { + decodable?: boolean, + can_be_restored?: boolean; + can_card_upgrade?: boolean; + can_consume?: boolean; + can_craft_mark?: boolean; + can_gift_wrap?: boolean; + can_killstreakify?: boolean; + can_strangify?: boolean; + paintable?: boolean; + strange_parts?: boolean; + }; + attributes: Array<{ + name: string; + class: string; + value: number | string; // The value can sometimes be a string, but example uses numbers + }>; +} + export function getItemIndexByName(schema: ItemSchema, name: string, excludeStock: boolean = true, excludeDecorated: boolean = true) { for (const [defindex, value] of Object.entries(schema)) { if (value['name'] == name) { @@ -152,21 +190,20 @@ export async function prepareSchema(): Promise { const cacheItems = {} - const responseItems: any[] = await response.json() + const responseItems: SchemaResponseItem[] = await response.json() // We want to keep the keys `defindex`, `item_name`, and `attributes` - responseItems.forEach((item: any) => { + responseItems.forEach((item: SchemaResponseItem) => { const defindex: number = item['defindex'] let tradable: boolean = true try { if(item['attributes'] != null) { - if(item['attributes'].find((attribute: {}) => (attribute as any)['class'] == "cannot_trade")) { + if(item['attributes'].find((attribute) => attribute['class'] == "cannot_trade")) { tradable = false } } } catch(error) { logError(error) - log(item) } let canKillstreakify: boolean = false @@ -178,7 +215,6 @@ export async function prepareSchema(): Promise { } } catch(error) { logError(error) - log(item) } (cacheItems as any)[defindex.toString()] = { From 74ef520858b0e1a824f3604ee02eff8ba4178232 Mon Sep 17 00:00:00 2001 From: xenticore Date: Tue, 29 Apr 2025 00:52:47 -0400 Subject: [PATCH 20/43] chore: define extension author --- package.json | 1 + src/userscript_header.js | 1 + 2 files changed, 2 insertions(+) diff --git a/package.json b/package.json index 56a8ecb..cf153c7 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "tf2wikipricing", "version": "0.7.1", "description": "Adds item pricing to the Team Fortress 2 wiki", + "author": "rapture.party", "devDependencies": { "@eslint/css": "^0.7.0", "@eslint/js": "^9.25.1", diff --git a/src/userscript_header.js b/src/userscript_header.js index f8f443a..e9f7a78 100644 --- a/src/userscript_header.js +++ b/src/userscript_header.js @@ -2,6 +2,7 @@ // @name EXTENSION_NAME // @description EXTENSION_DESCRIPTION // @version EXTENSION_VERSION +// @author EXTENSION_AUTHOR // @match *://wiki.teamfortress.com/wiki/* // @run-at document-start // @inject-into content From 36550df74974fce37dc463aa0b0257e7abf97489 Mon Sep 17 00:00:00 2001 From: xenticore Date: Wed, 30 Apr 2025 15:40:37 -0400 Subject: [PATCH 21/43] lint: exclude localization string files --- src/eslint.config.mjs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/eslint.config.mjs b/src/eslint.config.mjs index 464108c..b3ef968 100644 --- a/src/eslint.config.mjs +++ b/src/eslint.config.mjs @@ -15,5 +15,13 @@ export default defineConfig([ }, languageOptions: { globals: globals.browser } }, + { files: ["**/strings/*.js"], plugins: { js }, extends: ["js/recommended"] }, + tseslint.configs.recommended, + { + rules: { + "@typescript-eslint/no-require-imports": "off" + }, + languageOptions: { sourceType: "commonjs" } + }, { files: ["**/*.css"], plugins: { css }, language: "css/css", extends: ["css/recommended"] }, ]); \ No newline at end of file From 5e5846abde34a9f27a269a8fc646fb45b30fe477 Mon Sep 17 00:00:00 2001 From: xenticore Date: Wed, 30 Apr 2025 18:31:33 -0400 Subject: [PATCH 22/43] refactor: rewrite async promises as chain --- __tests__/priceService.test.ts | 5 +- src/content/content.ts | 107 ++++++++++++-------------------- src/content/priceService.ts | 11 ++-- src/content/pricing/pricestf.ts | 42 ++++++------- 4 files changed, 67 insertions(+), 98 deletions(-) diff --git a/__tests__/priceService.test.ts b/__tests__/priceService.test.ts index 2ee36f9..b66c64d 100644 --- a/__tests__/priceService.test.ts +++ b/__tests__/priceService.test.ts @@ -69,7 +69,7 @@ describe('Price Service', () => { }) test('fetchPrice rejects with 401 when no token provided', async () => { - await expect(fetchPrice('', mockDefIndex + ";" + mockQuality)).rejects.toBe(401) + await expect(fetchPrice('', mockDefIndex + ";" + mockQuality)).rejects.toThrow() }) test('fetchPrice handles pricing API errors', async () => { @@ -77,7 +77,8 @@ describe('Price Service', () => { (priceUsingPricesTF as jest.Mock).mockRejectedValue(testError); (getStorageValue as jest.Mock).mockResolvedValue(null) - await expect(fetchPrice(mockToken, mockDefIndex + ";" + mockQuality)).rejects.toBe(testError) + await expect(fetchPrice(mockToken, mockDefIndex + ";" + mockQuality)).rejects.toThrow() + }) }) test('fetchKeyPrice uses correct parameters', async () => { diff --git a/src/content/content.ts b/src/content/content.ts index abcd48a..8629543 100644 --- a/src/content/content.ts +++ b/src/content/content.ts @@ -227,21 +227,17 @@ async function inject() { // Check item schema for Australium variant of current defindex if(itemSchema[itemIndex].hasAustraliumVariant) { - promises.push(new Promise(async (resolve) => { - logDebug(`Fetching price for Australium ${itemName}`) - let data: ItemPriceData | null - try { - data = await fetchPrice(token, `${itemIndex};11;australium`, currentTime); - updateTime = new Date(data.update) - } catch { - log(`Australium ${itemName} is unpriced or unavailable, skipping...`) - } + promises.push(fetchPrice(token, `${itemIndex};11;australium`, currentTime).then(data => { + updateTime = new Date(data.update) + log(`Saving price for Australium ${itemName}`) const priceRow = createPriceRow($T("Australium"), data, keyPrice, exchangeRates, locale, "https://wiki.teamfortress.com/wiki/Australium_weapons") priceRows.push({order: 99, row: priceRow, category: PriceRowCategory.None}) - resolve() - return + }) + .catch((error) => { + logError(error) + log(`Australium ${itemName} is unpriced or unavailable, skipping...`) })) } @@ -259,37 +255,29 @@ async function inject() { festiveHeadingRow.style.display = 'none'; festiveHeadingRow.appendChild(festiveHeading); - promises.push(new Promise(async (resolve) => { - logDebug(`Fetching price for Festive ${itemName}`) - let data: ItemPriceData | null - try { - data = await fetchPrice(token, `${itemSchema[itemIndex].festiveVariant};6`, currentTime); - updateTime = new Date(data.update) - } catch { - log(`Festive ${itemName} is unpriced or unavailable, skipping...`) - } + promises.push(fetchPrice(token, `${itemSchema[itemIndex].festiveVariant};6`, currentTime).then(data => { + updateTime = new Date(data.update) + log(`updateTime price for Festive ${itemName}`) const priceRow = createPriceRow($T("Unique"), data, keyPrice, exchangeRates, locale) priceRows.push({order: -1, row: priceRow, category: PriceRowCategory.Festive}) - resolve() - return + }) + .catch((error) => { + logError(error) + log(`Festive ${itemName} is unpriced or unavailable, skipping...`) })) - promises.push(new Promise(async (resolve) => { - logDebug(`Fetching price for Strange Festive ${itemName}`) - let data: ItemPriceData | null - try { - data = await fetchPrice(token, `${itemSchema[itemIndex].festiveVariant};11`, currentTime); - updateTime = new Date(data.update) - } catch { - log(`Strange Festive ${itemName} is unpriced or unavailable, skipping...`) - } + promises.push(fetchPrice(token, `${itemSchema[itemIndex].festiveVariant};11`, currentTime).then(data => { + updateTime = new Date(data.update) + log(`Saving price for Strange Festive ${itemName}`) const priceRow = createPriceRow($T("Strange"), data, keyPrice, exchangeRates, locale) priceRows.push({order: 11, row: priceRow, category: PriceRowCategory.Festive}) - resolve() - return + }) + .catch((error) => { + logError(error) + log(`Strange Festive ${itemName} is unpriced or unavailable, skipping...`) })) } @@ -310,30 +298,23 @@ async function inject() { killstreakKitHeadingRow.style.display = 'none'; killstreakKitHeadingRow.appendChild(heading); [1,2,3].map((tier) => { - promises.push(new Promise(async (resolve) => { - logDebug(`Fetching price for ${itemName} Killstreak Kit Tier ${tier}`) - let data: ItemPriceData | null - try { - let kitIndex: number - switch (tier) { - default: - case 1: kitIndex = 6527; break; - case 2: kitIndex = 6523; break; - case 3: kitIndex = 6526; break; - } - data = await fetchPrice(token, `${kitIndex};6;uncraftable;kt-${tier};td-${itemIndex}`, currentTime); - updateTime = new Date(data.update) - } catch { - log(`${itemName} Killstreak Kit Tier ${tier} is unpriced or unavailable, skipping...`) - resolve() - return - } + 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}`) 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}) - resolve() - return + }) + .catch((error) => { + logError(`Failed to fetch price for ${itemName} Killstreak Kit Tier ${tier}`, error) })) }) } @@ -366,23 +347,17 @@ async function inject() { const itemName = itemSchema[variantIndex].name // FIXME: variantName should match wiki display name const variantName = itemName.includes('Mk.II') ? itemName.split(' ')[0] + ' Mk.II' : itemName.split(' ')[0] - promises.push(new Promise(async (resolve) => { - logDebug(`Fetching price for ${itemName}`) - let data: ItemPriceData | null - try { - data = await fetchPrice(token, `${variantIndex};11`, currentTime); - updateTime = new Date(data.update) - } catch { - log(`${itemName} is unpriced or unavailable, skipping...`) - } + promises.push(fetchPrice(token, `${variantIndex};11`, currentTime).then(data => { + logDebug(`Saving price for ${itemName}`) + updateTime = new Date(data.update) const priceRow = createPriceRow($T(variantName), data, keyPrice, exchangeRates, locale, "https://wiki.teamfortress.com/wiki/Botkiller_weapons") - // FIXME: order should be by release - // Silver Mk.I, Gold Mk.II, Rust, Blood, Carbonado, Diamond, Silver Mk.II, Gold Mk.II priceRows.push({order: botkillerOrder.indexOf(variantName), row: priceRow, category: PriceRowCategory.Botkiller}) - resolve() - return + }) + .catch((error) => { + logError(error) + log(`Strange Festive ${itemName} is unpriced or unavailable, skipping...`) })) }) } diff --git a/src/content/priceService.ts b/src/content/priceService.ts index a7fa811..efa0a1a 100644 --- a/src/content/priceService.ts +++ b/src/content/priceService.ts @@ -1,7 +1,7 @@ import { defindex_key, storage_priceprefix } from "./config" import { priceUsingPricesTF } from "./pricing/pricestf" import { getStorageValue, setStorageValue } from "./storage" -import { logDebug, log } from "./utils/log" +import { logDebug } from "./utils/log" /** Pricing data for a given TF2 item. */ export class ItemPriceData { @@ -40,7 +40,6 @@ export async function fetchKeyPrice(token: string) { * @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 { - return new Promise(async (resolve, reject) => { let data: ItemPriceData | null const cached: ItemPriceData = await getStorageValue(storage_priceprefix + sku, null) @@ -51,7 +50,7 @@ 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) { - reject(401) + throw new Error('No token provided') } data = new ItemPriceData() data.sku = sku @@ -65,8 +64,7 @@ export async function fetchPrice(token: string, sku: string, update: Date = new data.metal = response.metal } } catch (error) { - log(`Received ${error} error while pricing ${sku} using prices.tf`) - reject(error) + throw new Error(`Received ${error} error while pricing ${sku} using prices.tf`) } if ('metal' in data && 'keys' in data) { @@ -75,6 +73,5 @@ export async function fetchPrice(token: string, sku: string, update: Date = new } else { logDebug(`Using cached price data for ${sku}`) } - resolve(data) - }) + return data } diff --git a/src/content/pricing/pricestf.ts b/src/content/pricing/pricestf.ts index 55e56da..f125ec3 100644 --- a/src/content/pricing/pricestf.ts +++ b/src/content/pricing/pricestf.ts @@ -42,49 +42,45 @@ async function priceUsingPricesTF(token: string, sku: string, retries: number = // prices.tf // https://api2.prices.tf/prices/${sku} // Authorization: Bearer ${token} - return GM_fetch(`https://api2.prices.tf/prices/${encodeURIComponent(sku)}`, { - method: 'get', - headers: { - 'Accept': 'application/json', - 'Authorization': `Bearer ${token}`, - } - }) - .then(response => { - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } + try { + const response = await GM_fetch(`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', retries); + return priceUsingPricesTF(token, sku + ';uncraftable'); } } - if(response.status === 404) { - throw new Error(`Item not found: ${sku}`); - } 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 2 seconds, ${retries} retries left`) + 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`) } } - return response.json(); - }) - .then(data => { + 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 => { + } + 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 From 39ad61b7686850e8e553572f849e0f568f6c0c46 Mon Sep 17 00:00:00 2001 From: xenticore Date: Wed, 30 Apr 2025 18:32:02 -0400 Subject: [PATCH 23/43] fix: mock data not using correct date format --- __tests__/priceService.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/__tests__/priceService.test.ts b/__tests__/priceService.test.ts index b66c64d..ad54796 100644 --- a/__tests__/priceService.test.ts +++ b/__tests__/priceService.test.ts @@ -35,7 +35,7 @@ describe('Price Service', () => { const mockCachedData: ItemPriceData = { sku: mockSku, - update: new Date(Date.now() - 15 * 60 * 1000), // 15 minutes ago + update: new Date(Date.now() - 15 * 60 * 1000).getTime(), // 15 minutes ago ttl: mockTtl, keys: 1, metal: 21.11, @@ -56,7 +56,7 @@ describe('Price Service', () => { }) test('fetchPrice fetches new data when cache is expired', async () => { - const expiredCache: ItemPriceData = { ...mockCachedData, update: new Date(Date.now() - 2 * mockTtl) }; + const expiredCache: ItemPriceData = { ...mockCachedData, update: new Date(Date.now() - 2 * mockTtl).getTime() }; (getStorageValue as jest.Mock).mockResolvedValue(expiredCache); (priceUsingPricesTF as jest.Mock).mockResolvedValue(mockPriceResponse) From d4b2fdeff08c449f5da3ac4427c3686c4699da8d Mon Sep 17 00:00:00 2001 From: xenticore Date: Wed, 30 Apr 2025 18:32:25 -0400 Subject: [PATCH 24/43] lint: force allow any --- src/content/schemaService.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/content/schemaService.ts b/src/content/schemaService.ts index 8404070..47b00d4 100644 --- a/src/content/schemaService.ts +++ b/src/content/schemaService.ts @@ -188,7 +188,8 @@ export async function prepareSchema(): Promise { if (response.ok) { await setStorageValue(storage_lastUpdateTime, new Date().getTime()); - const cacheItems = {} + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const cacheItems: any = {} const responseItems: SchemaResponseItem[] = await response.json() // We want to keep the keys `defindex`, `item_name`, and `attributes` @@ -217,7 +218,7 @@ export async function prepareSchema(): Promise { logError(error) } - (cacheItems as any)[defindex.toString()] = { + cacheItems[defindex.toString()] = { "name": item['item_name'], "slot": item['item_slot'], "tradable": tradable, From a34c040acd2b0875bf35988666636cec2d464454 Mon Sep 17 00:00:00 2001 From: xenticore Date: Wed, 30 Apr 2025 18:33:17 -0400 Subject: [PATCH 25/43] refactor: correct typing of `getKeyByValue` --- src/content/content.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/content/content.ts b/src/content/content.ts index 8629543..d5b8775 100644 --- a/src/content/content.ts +++ b/src/content/content.ts @@ -22,8 +22,13 @@ const excludedQualities = new Set([ ]); // Helper functions -function getKeyByValue(object: any, value: string) { - return Object.keys(object).find(key => object[key] === value); +function getKeyByValue(obj: Record, value: V): K | undefined { + for (const [key, val] of Object.entries(obj)) { + if (val === value) { + return key as unknown as K; + } + } + return undefined; } // Main function From 19fe1c70a4d6b0c7169d83f0ef6c443b8957ef72 Mon Sep 17 00:00:00 2001 From: xenticore Date: Wed, 30 Apr 2025 19:11:21 -0400 Subject: [PATCH 26/43] oops --- __tests__/priceService.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/__tests__/priceService.test.ts b/__tests__/priceService.test.ts index ad54796..60376a8 100644 --- a/__tests__/priceService.test.ts +++ b/__tests__/priceService.test.ts @@ -79,7 +79,6 @@ describe('Price Service', () => { await expect(fetchPrice(mockToken, mockDefIndex + ";" + mockQuality)).rejects.toThrow() }) - }) test('fetchKeyPrice uses correct parameters', async () => { (getStorageValue as jest.Mock).mockResolvedValue(null); From c8f71b4f47b42d3acca77c11757341bb52130e8f Mon Sep 17 00:00:00 2001 From: xenticore Date: Wed, 30 Apr 2025 19:15:38 -0400 Subject: [PATCH 27/43] l10n: update attribution text and style New text is easier to localize, strings can be found in free dictionaries --- src/content/content.ts | 8 +++++--- src/strings/ar.js | 2 +- src/strings/cs.js | 2 +- src/strings/da.js | 2 +- src/strings/de.js | 2 +- src/strings/en.js | 2 +- src/strings/es.js | 2 +- src/strings/fi.js | 2 +- src/strings/fr.js | 2 +- src/strings/hu.js | 2 +- src/strings/it.js | 2 +- src/strings/ja.js | 2 +- src/strings/ko.js | 2 +- src/strings/nl.js | 2 +- src/strings/no.js | 2 +- src/strings/pl.js | 2 +- src/strings/pt-BR.js | 2 +- src/strings/pt.js | 2 +- src/strings/ro.js | 2 +- src/strings/ru.js | 2 +- src/strings/sv.js | 2 +- src/strings/tr.js | 2 +- src/strings/zh-Hans.js | 2 +- src/strings/zh-Hant.js | 2 +- 24 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/content/content.ts b/src/content/content.ts index d5b8775..f486c98 100644 --- a/src/content/content.ts +++ b/src/content/content.ts @@ -403,10 +403,12 @@ async function inject() { const label = document.createElement("td"); 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 attributionText = $T("Trade prices sourced from %@. Currency conversions are approximate.", locale).replace('%@', 'prices.tf'); - const exchangeRateAttribution = `Rates By Exchange Rate API.`; - label.innerHTML = `${updateText}
${attributionText}
${exchangeRateAttribution}`; + const attributionHeader = $T("Acknowledgements"); + const pricesAttribution = `prices.tf`; + const exchangeRateAttribution = `Rates By Exchange Rate API`; + label.innerHTML = `${updateText}
${attributionHeader}
${pricesAttribution}
${exchangeRateAttribution}`; row.appendChild(label); priceProgressRow.insertAdjacentElement('afterend', row); diff --git a/src/strings/ar.js b/src/strings/ar.js index 485fd95..dc30ea5 100644 --- a/src/strings/ar.js +++ b/src/strings/ar.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Community Pricing", // Itembox footer "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot diff --git a/src/strings/cs.js b/src/strings/cs.js index 485fd95..dc30ea5 100644 --- a/src/strings/cs.js +++ b/src/strings/cs.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Community Pricing", // Itembox footer "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot diff --git a/src/strings/da.js b/src/strings/da.js index 485fd95..dc30ea5 100644 --- a/src/strings/da.js +++ b/src/strings/da.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Community Pricing", // Itembox footer "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot diff --git a/src/strings/de.js b/src/strings/de.js index 485fd95..dc30ea5 100644 --- a/src/strings/de.js +++ b/src/strings/de.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Community Pricing", // Itembox footer "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot diff --git a/src/strings/en.js b/src/strings/en.js index 485fd95..dc30ea5 100644 --- a/src/strings/en.js +++ b/src/strings/en.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Community Pricing", // Itembox footer "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot diff --git a/src/strings/es.js b/src/strings/es.js index 112bb59..0cd9a43 100644 --- a/src/strings/es.js +++ b/src/strings/es.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Precios de la comunidad", // Itembox footer "Updated %@.": "Actualizado %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Precios comerciales obtenidos de %@. Las conversiones de divisas son aproximadas.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Agradecimientos", // sourced from AppleGlot // Price strings "Data unavailable": "Datos no disponibles", // sourced from AppleGlot diff --git a/src/strings/fi.js b/src/strings/fi.js index 485fd95..dc30ea5 100644 --- a/src/strings/fi.js +++ b/src/strings/fi.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Community Pricing", // Itembox footer "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot diff --git a/src/strings/fr.js b/src/strings/fr.js index 485fd95..dc30ea5 100644 --- a/src/strings/fr.js +++ b/src/strings/fr.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Community Pricing", // Itembox footer "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot diff --git a/src/strings/hu.js b/src/strings/hu.js index 485fd95..dc30ea5 100644 --- a/src/strings/hu.js +++ b/src/strings/hu.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Community Pricing", // Itembox footer "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot diff --git a/src/strings/it.js b/src/strings/it.js index 4d070d7..734302e 100644 --- a/src/strings/it.js +++ b/src/strings/it.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Prezzo Comunitario", // Itembox footer "Updated %@.": "Aggiornato il %@.", - "Trade prices sourced from %@. Currency conversions are approximate.": "Prezzi commerciali forniti da %@. Le conversioni valutarie sono approssimative.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Note legali", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot diff --git a/src/strings/ja.js b/src/strings/ja.js index 1fa3eab..ffcc18d 100644 --- a/src/strings/ja.js +++ b/src/strings/ja.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "共同体価格", // Itembox footer "Updated %@.": "アップデート: %@。", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "謝辞", // sourced from AppleGlot // Price strings "Data unavailable": "データがありません", // sourced from AppleGlot diff --git a/src/strings/ko.js b/src/strings/ko.js index 485fd95..dc30ea5 100644 --- a/src/strings/ko.js +++ b/src/strings/ko.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Community Pricing", // Itembox footer "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot diff --git a/src/strings/nl.js b/src/strings/nl.js index 485fd95..dc30ea5 100644 --- a/src/strings/nl.js +++ b/src/strings/nl.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Community Pricing", // Itembox footer "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot diff --git a/src/strings/no.js b/src/strings/no.js index 485fd95..dc30ea5 100644 --- a/src/strings/no.js +++ b/src/strings/no.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Community Pricing", // Itembox footer "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot diff --git a/src/strings/pl.js b/src/strings/pl.js index 485fd95..dc30ea5 100644 --- a/src/strings/pl.js +++ b/src/strings/pl.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Community Pricing", // Itembox footer "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot diff --git a/src/strings/pt-BR.js b/src/strings/pt-BR.js index 485fd95..dc30ea5 100644 --- a/src/strings/pt-BR.js +++ b/src/strings/pt-BR.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Community Pricing", // Itembox footer "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot diff --git a/src/strings/pt.js b/src/strings/pt.js index 485fd95..dc30ea5 100644 --- a/src/strings/pt.js +++ b/src/strings/pt.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Community Pricing", // Itembox footer "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot diff --git a/src/strings/ro.js b/src/strings/ro.js index 485fd95..dc30ea5 100644 --- a/src/strings/ro.js +++ b/src/strings/ro.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Community Pricing", // Itembox footer "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot diff --git a/src/strings/ru.js b/src/strings/ru.js index f5b9a54..245f637 100644 --- a/src/strings/ru.js +++ b/src/strings/ru.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Цены сообщества", // Itembox footer "Updated %@.": "Обновлено %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Цены на торговлю взяты с %@. Конвертация валюты приблизительна.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Уведомления", // sourced from AppleGlot // Price strings "Data unavailable": "Данные недоступны", // sourced from AppleGlot diff --git a/src/strings/sv.js b/src/strings/sv.js index 485fd95..dc30ea5 100644 --- a/src/strings/sv.js +++ b/src/strings/sv.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Community Pricing", // Itembox footer "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot diff --git a/src/strings/tr.js b/src/strings/tr.js index 485fd95..dc30ea5 100644 --- a/src/strings/tr.js +++ b/src/strings/tr.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Community Pricing", // Itembox footer "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot diff --git a/src/strings/zh-Hans.js b/src/strings/zh-Hans.js index 485fd95..dc30ea5 100644 --- a/src/strings/zh-Hans.js +++ b/src/strings/zh-Hans.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Community Pricing", // Itembox footer "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot diff --git a/src/strings/zh-Hant.js b/src/strings/zh-Hant.js index 485fd95..dc30ea5 100644 --- a/src/strings/zh-Hant.js +++ b/src/strings/zh-Hant.js @@ -6,7 +6,7 @@ module.exports = { "Community Pricing": "Community Pricing", // Itembox footer "Updated %@.": "Updated %@.", // %@ is a date string, sourced from AppleGlot - "Trade prices sourced from %@. Currency conversions are approximate.": "Trade prices sourced from %@. Currency conversions are approximate.", // %@ is always a URL, (eg. prices.tf) + "Acknowledgements": "Acknowledgements", // sourced from AppleGlot // Price strings "Data unavailable": "Data unavailable", // sourced from AppleGlot From bef368440eddf0e1e2627643efa90c77ac1fd985 Mon Sep 17 00:00:00 2001 From: xenticore Date: Wed, 30 Apr 2025 19:35:08 -0400 Subject: [PATCH 28/43] l10n: correct untranslated spanish string --- src/strings/es.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/es.js b/src/strings/es.js index 0cd9a43..d8ed300 100644 --- a/src/strings/es.js +++ b/src/strings/es.js @@ -38,7 +38,7 @@ module.exports = { // Killstreak tiers sourced from TF2 wiki "Killstreak Kit": "Kit Cuentarrachas", - "kt-1": "Standard", + "kt-1": "Estándar", "kt-2": "Especializado", "kt-3": "Profesional", } \ No newline at end of file From 4ce57450dd8aa59625e5aa3af24beeec5f9c8346 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 1 May 2025 13:07:33 -0400 Subject: [PATCH 29/43] chore: add exchangerates-api to userscript header --- src/userscript_header.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/userscript_header.js b/src/userscript_header.js index e9f7a78..11d0f31 100644 --- a/src/userscript_header.js +++ b/src/userscript_header.js @@ -10,6 +10,8 @@ // @domain steamcommunity.com // @connect prices.tf // @domain prices.tf +// @connect open.er-api.com +// @domain open.er-api.com // @grant GM.setValue // @grant GM_setValue // @grant GM.getValue From d02bd7ac9d2237cd5b8d7e4999e2e344b0447c24 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 1 May 2025 14:25:44 -0400 Subject: [PATCH 30/43] chore: rename package Preserve userscript name while making package name neater: **TF2 Wiki Pricing** `tf2wikipricing.user.js` --- package.json | 2 +- webpack.config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index cf153c7..a3acaa6 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "tf2wikipricing", + "name": "TF2 Wiki Pricing", "version": "0.7.1", "description": "Adds item pricing to the Team Fortress 2 wiki", "author": "rapture.party", diff --git a/webpack.config.js b/webpack.config.js index 5e87d44..a26fc60 100755 --- a/webpack.config.js +++ b/webpack.config.js @@ -100,7 +100,7 @@ module.exports = [ devtool: false, output: { path: path.resolve(__dirname, 'dist/userscript'), - filename: `${package.name}.user.js` + filename: `tf2wikipricing.user.js` }, resolve: { extensions: [".ts", ".tsx", ".js", ".json", ".css"] From 95ce63789281b0d842bd87b94537ee6c46350920 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 1 May 2025 15:27:14 -0400 Subject: [PATCH 31/43] feat: enable webextension builds Currently only supports Chrome due to no `chrome` -> `browser` polyfill --- .gitea/workflows/build.yaml | 5 +- .gitea/workflows/release.yaml | 18 +++-- bun.lockb | Bin 196987 -> 215301 bytes package.json | 4 ++ src/background/background.ts | 111 +++++++++++++++++++++++++++++ src/content/content.ts | 6 +- src/content/exchangeRateService.ts | 45 ++++++++---- src/content/fetchWrap.ts | 10 +++ src/content/priceService.ts | 9 ++- src/content/pricing/pricestf.ts | 13 ++-- src/content/schemaService.ts | 26 ++++--- src/content/storage.ts | 5 +- src/manifest.json | 20 ++++++ tsconfig.json | 2 +- webpack.config.js | 60 +++++++++++++--- 15 files changed, 281 insertions(+), 53 deletions(-) create mode 100644 src/background/background.ts create mode 100644 src/content/fetchWrap.ts diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 349d978..380ea99 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -30,6 +30,5 @@ jobs: - name: Archive production artifacts uses: actions/upload-artifact@v3 with: - name: tf2wikipricing.user.js - path: | - dist/userscript/tf2wikipricing.user.js \ No newline at end of file + name: tf2wikipricing + path: dist/ \ No newline at end of file diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index c071d53..a6cfd0b 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -26,9 +26,8 @@ jobs: - name: Archive production artifacts uses: actions/upload-artifact@v3 with: - name: tf2wikipricing.user.js - path: | - dist/userscript/tf2wikipricing.user.js + name: tf2wikipricing + path: dist/ deploy: runs-on: debian-latest needs: build @@ -36,8 +35,14 @@ jobs: - name: Download release artifacts uses: actions/download-artifact@v3 with: - name: tf2wikipricing.user.js - path: userscript + name: tf2wikipricing + path: dist/ + - name: Package Chrome extension + run: | + cd dist/ + echo "${{ secrets.CRX_PRIVATE_KEY }}" > private.pem + bun x crx pack -p private.pem -o tf2wikipricing.crx extension/ + rm -f private.pem - name: Create release id: use-go-action uses: akkuman/gitea-release-action@v1 @@ -45,5 +50,6 @@ jobs: title: "v${{ need.build.outputs.version }}" name: "v${{ need.build.outputs.version }}" files: | - userscript/** + dist/tf2wikipricing.crx + dist/tf2wikipricing.user.js sha256sum: true \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index c24e42ba28a65cb7ca74cc71392c880a763064e8..a4c615f26f8c1870ce7a98e24342e12a17f77183 100755 GIT binary patch delta 47362 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 { + console.log(response) + return response.json() + }) + .then(json => { + sendResponse(json) + }) + .catch(error => { + console.error("Failed to get exchange rates", error); + }) + return true; + } +}) + +chrome.runtime.onMessage.addListener( + function(request, sender, sendResponse) { + if (request.contentScriptQuery == "querySchema") { + const url = "https://raw.githubusercontent.com/danocmx/node-tf2-static-schema/master/static/items.json"; + fetch(url) + .then(response => response.json()) + .then(json => sendResponse(json)) + .catch(error => { + console.error("Failed to get schema", error); + }) + return true; + } + } +); + +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)}`; + 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'); + } + } + 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) { + console.log(`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`) + } + } + const data = await response.json(); + const prices = new PricesResponse(); + prices.keys = data['sellKeys'] + prices.metal = data['sellHalfScrap'] / 18.0; + return prices; +} + +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 + switch (service) { + case "prices.tf": { + priceUsingPricesTF(token, sku).then((response) => { + sendResponse(JSON.stringify(response)); + }) + return true; + } + default: + return false; + } + } + } +); \ No newline at end of file diff --git a/src/content/content.ts b/src/content/content.ts index f486c98..955f2ea 100644 --- a/src/content/content.ts +++ b/src/content/content.ts @@ -1,4 +1,5 @@ -import styleCss from './style.css' +declare const __ENV_WEBEXTENSION: boolean; +declare const __ENV_USERSCRIPT: boolean; import { logDebug, log, logError } from './utils/log' import { getPricesToken } from './pricing/pricestf' @@ -417,7 +418,8 @@ async function inject() { } function addStyles() { - const head = document.head || document.getElementsByTagName('head')[0], + if(__ENV_USERSCRIPT) { + const head = document.head || document.getElementsByTagName('head')[0], style = document.createElement('style'); head.appendChild(style); style.innerHTML = styleCss; diff --git a/src/content/exchangeRateService.ts b/src/content/exchangeRateService.ts index 5b41de7..ba6a645 100644 --- a/src/content/exchangeRateService.ts +++ b/src/content/exchangeRateService.ts @@ -1,8 +1,9 @@ import { getStorageValue, setStorageValue } from './storage' import { logDebug, log, logError } from './utils/log' import { storage_exchangerates, storage_exchangerates_next, storage_exchangerates_update } from './config' -declare function GM_fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise -import './GM_fetch' +import { fetchWrap } from './fetchWrap'; +declare const __ENV_WEBEXTENSION: boolean; +declare const __ENV_USERSCRIPT: boolean; export interface ExchangeRates { [key: string]: number; @@ -26,7 +27,7 @@ export async function prepareExchangeRates(): Promise { const lastUpdateTime = new Date(update); const nextUpdateTime = new Date(nextUpdate); log(`Exchange rates updated at ${lastUpdateTime}`); - if (rates == null || Object.keys(rates).length === 0 || lastUpdateTime.getTime() > nextUpdateTime.getTime()) { + if (rates == null || Object.keys(rates).length === 0 || Date.now() > nextUpdateTime.getTime()) { needsUpdate = true } } else { @@ -35,19 +36,35 @@ export async function prepareExchangeRates(): Promise { if(needsUpdate) { log("Exchange rates out of Date. Rebuilding..."); - const url = "https://open.er-api.com/v6/latest/USD" - const response = await GM_fetch(url); - if (response.ok) { - await setStorageValue(storage_exchangerates_update, new Date().toISOString()) - const json = await response.json() - if(json != null){ - rates = json['rates'] - await setStorageValue(storage_exchangerates, rates) - await setStorageValue(storage_exchangerates_next, json['time_next_update_utc']) + let exchangeResponse: { + rates: ExchangeRates, + time_next_update_utc: string + } + if(__ENV_USERSCRIPT) { + const url = "https://open.er-api.com/v6/latest/USD" + try { + const response: Response = await fetchWrap(url) + if(response.ok) { + exchangeResponse = await response.json() + } + } catch (e) { + logDebug(e); + throw e; } - logDebug(`Exchange rates updated at ${new Date()}`) } else { - logError(`Failed to fetch exchange rates. Status code: ${response.status}`, response) + exchangeResponse = await chrome.runtime.sendMessage({contentScriptQuery: "queryExchangeRates"}) + } + try { + if(exchangeResponse == null) { + throw new Error("Rates are null") + } + rates = exchangeResponse.rates + await setStorageValue(storage_exchangerates_update, new Date().toISOString()) + await setStorageValue(storage_exchangerates, exchangeResponse.rates) + await setStorageValue(storage_exchangerates_next, exchangeResponse.time_next_update_utc) + logDebug(`Exchange rates updated at ${new Date()}`) + } catch(e) { + logError(`Failed to store exchange rates.`, e) } } diff --git a/src/content/fetchWrap.ts b/src/content/fetchWrap.ts new file mode 100644 index 0000000..49a30ce --- /dev/null +++ b/src/content/fetchWrap.ts @@ -0,0 +1,10 @@ +declare let __ENV_USERSCRIPT: boolean; +declare function GM_fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise + +export function fetchWrap(input: string | URL | globalThis.Request, init?: RequestInit): Promise { + if(__ENV_USERSCRIPT) { + return GM_fetch(input, init) + } else { + return fetch(input, init) + } +} diff --git a/src/content/priceService.ts b/src/content/priceService.ts index efa0a1a..4036c8c 100644 --- a/src/content/priceService.ts +++ b/src/content/priceService.ts @@ -2,6 +2,8 @@ import { defindex_key, storage_priceprefix } from "./config" import { priceUsingPricesTF } from "./pricing/pricestf" import { getStorageValue, setStorageValue } from "./storage" import { logDebug } from "./utils/log" +declare const __ENV_WEBEXTENSION: boolean; +declare const __ENV_USERSCRIPT: boolean; /** Pricing data for a given TF2 item. */ export class ItemPriceData { @@ -58,7 +60,12 @@ export async function fetchPrice(token: string, sku: string, update: Date = new data.ttl = ttl try { - const response = await priceUsingPricesTF(token, sku) + let response: PricesResponse + if(__ENV_USERSCRIPT) { + response = await priceUsingPricesTF(token, sku) + } else { + response = JSON.parse(await chrome.runtime.sendMessage({contentScriptQuery: "priceSKU", service: "prices.tf", sku: sku, token: token})); + } if (response) { data.keys = response.keys data.metal = response.metal diff --git a/src/content/pricing/pricestf.ts b/src/content/pricing/pricestf.ts index f125ec3..abeff72 100644 --- a/src/content/pricing/pricestf.ts +++ b/src/content/pricing/pricestf.ts @@ -1,10 +1,12 @@ -declare function GM_fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise -import '../GM_fetch' +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) => { - GM_fetch('https://api2.prices.tf/auth/access', { + fetchWrap('https://api2.prices.tf/auth/access', { method: 'post', headers: new Headers({ 'Accept': 'application/json' @@ -18,6 +20,9 @@ async function getPricesToken(): Promise { }) .then((responseData) => resolve(responseData['accessToken'])) }) + } else { + return chrome.runtime.sendMessage({contentScriptQuery: 'getPricesTFToken'}) + } } class PricesResponse { @@ -43,7 +48,7 @@ async function priceUsingPricesTF(token: string, sku: string, retries: number = // https://api2.prices.tf/prices/${sku} // Authorization: Bearer ${token} try { - const response = await GM_fetch(`https://api2.prices.tf/prices/${encodeURIComponent(sku)}`, { + const response = await fetchWrap(`https://api2.prices.tf/prices/${encodeURIComponent(sku)}`, { method: 'get', headers: { 'Accept': 'application/json', diff --git a/src/content/schemaService.ts b/src/content/schemaService.ts index 47b00d4..1709ff1 100644 --- a/src/content/schemaService.ts +++ b/src/content/schemaService.ts @@ -1,10 +1,11 @@ +declare const __ENV_WEBEXTENSION: boolean; +declare const __ENV_USERSCRIPT: boolean; import { getStorageValue, setStorageValue } from './storage' import { logDebug, log, logError } from './utils/log' import './config' -declare function GM_fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise -import './GM_fetch' import { storage_version, storage_schema, storage_lastUpdateTime } from './config' import Australiums from '../resources/australiums.json' +import { fetchWrap } from './fetchWrap' const semver = require('semver') export function checkAustraliumVariant(defindex: number): boolean { @@ -162,7 +163,7 @@ export async function prepareSchema(): Promise { const storedVersion: string | null = await getStorageValue(storage_version, null) if(!storedVersion || !semver.valid(storedVersion)) { - log(`Cache is from an unknown version of the extension. Updating for version ${__VERSION__}`); + log(`Preparing the extension for the first time.`); needsUpdate = true } else if(semver.valid(storedVersion) && semver.lt(storedVersion, __VERSION__)) { log(`Cache is from a previous version (${storedVersion}) of the extension. Updating for version ${__VERSION__}`); @@ -183,15 +184,22 @@ export async function prepareSchema(): Promise { if(needsUpdate) { log("Item Schema out of Date. Rebuilding..."); - const url = "https://raw.githubusercontent.com/danocmx/node-tf2-static-schema/master/static/items.json" - const response = await GM_fetch(url); - if (response.ok) { + try { await setStorageValue(storage_lastUpdateTime, new Date().getTime()); // eslint-disable-next-line @typescript-eslint/no-explicit-any const cacheItems: any = {} - const responseItems: SchemaResponseItem[] = await response.json() + let responseItems: SchemaResponseItem[]; + if(__ENV_USERSCRIPT) { + const url = "https://raw.githubusercontent.com/danocmx/node-tf2-static-schema/master/static/items.json" + const response = await fetchWrap(url); + if(response.ok) { + responseItems = await response.json(); + } + } else { + responseItems = await chrome.runtime.sendMessage({contentScriptQuery: "querySchema"}) + } // We want to keep the keys `defindex`, `item_name`, and `attributes` responseItems.forEach((item: SchemaResponseItem) => { const defindex: number = item['defindex'] @@ -234,8 +242,8 @@ export async function prepareSchema(): Promise { itemSchema = cacheItems await setStorageValue(storage_version, __VERSION__); logDebug(`Item schema updated at ${new Date()}`) - } else { - logError("Could not fetch item schema."); + } catch (e) { + logError("Could not fetch item schema.", e); } } return itemSchema diff --git a/src/content/storage.ts b/src/content/storage.ts index 756df98..6b1d2b7 100644 --- a/src/content/storage.ts +++ b/src/content/storage.ts @@ -6,7 +6,8 @@ function getStorageValue(name: string, defaultValue: string): Promise { if(__ENV_USERSCRIPT) { return GM.getValue(name, defaultValue); } else if(__ENV_WEBEXTENSION) { - return browser.storage.local.get(name); + return chrome.storage.local.get(name) + .then((result) => result[name]) } else { // eslint-disable-next-line @typescript-eslint/no-explicit-any return new Promise((resolve) => { @@ -19,7 +20,7 @@ function setStorageValue(name: string, value: unknown): Promise { if(__ENV_USERSCRIPT) { return GM.setValue(name, value as GM.Value); } else if(__ENV_WEBEXTENSION) { - return browser.storage.local.set({name, value}); + return chrome.storage.local.set({[name]: value}); } else { return new Promise((_, reject) => { reject(); diff --git a/src/manifest.json b/src/manifest.json index 09c8e80..0d5fd6e 100755 --- a/src/manifest.json +++ b/src/manifest.json @@ -4,14 +4,34 @@ "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" diff --git a/tsconfig.json b/tsconfig.json index 8282a68..4b38ead 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,6 @@ "moduleResolution": "node", "resolveJsonModule": true, "allowSyntheticDefaultImports": true, - "types": ["bun-types", "jest", "greasemonkey", "firefox-webext-browser"] + "types": ["bun-types", "jest", "greasemonkey", "chrome", "firefox-webext-browser"] } } \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index a26fc60..b67934b 100755 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,5 +1,6 @@ var path = require('path'); var CopyPlugin = require('copy-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); var webpack = require('webpack'); var fs = require('fs'); var package = require('./package.json'); @@ -21,32 +22,66 @@ const defines = { } module.exports = [ - /* // WebExtension { + devtool: "source-map", entry: { - content: './src/content/content.ts' + content: './src/content/content.ts', + background: './src/background/background.ts', + style: './src/content/style.css' }, module: { rules: [ { test: /\.tsx?$/, use: 'ts-loader', - exclude: /node_modules/, + exclude: /node_modules|GM_fetch/, }, { - test: /\.(png|jpg|gif|svg)$/i, + test: /\.css$/i, use: [ { - loader: 'url-loader', + loader: MiniCssExtractPlugin.loader, options: { - limit: true, + publicPath: '/', // Adjust if needed for relative path resolution + }, + }, + { + loader: 'css-loader', + options: { + url: true, // Ensures url() in CSS is processed + }, + }, + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: { + 'postcss-url': { + url: (asset) => { + // Transform relative URLs to extension-style URLs + const relativePath = asset.url.replace(/^\.\.\//, '') // Remove leading ../resources part + return `chrome-extension://__MSG_@@extension_id__/${relativePath}`; + }, + }, + }, + }, }, }, ], }, + { + test: /\.(jpe?g|png|ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/, + type: 'asset/resource', + generator: { + filename: 'resources/[name][ext]', + }, + }, ], }, + externals: { + './src/content/GM_fetch': 'commonjs2 null' + }, optimization: { minimize: true }, @@ -55,12 +90,12 @@ module.exports = [ filename: "[name]/[name].js" }, resolve: { - extensions: [".ts", ".tsx", ".js", ".json", ".css"] + extensions: [".ts", ".tsx", ".js", ".json"] }, plugins: [ - new webpack.DefinePlugin({__ENV_WEBEXTENSION: true, __ENV_USERSCRIPT: false}), + new webpack.DefinePlugin({ ...defines, __ENV_WEBEXTENSION: true, __ENV_USERSCRIPT: false}), new CopyPlugin({ patterns: [ - { from: './src/manifest.json', to: 'manifest.json', + { from: './src/manifest.json', to: 'manifest.json', transform(content, absoluteFrom) { return allReplace(content.toString(), defines) }, @@ -68,14 +103,17 @@ module.exports = [ ]}), new CopyPlugin({ patterns: [ { from: './src/icons', to: 'icons/[file]'}, + { from: './src/resources/*.png', to: 'resources/[name][ext]' }, ]}), + new MiniCssExtractPlugin({ + filename: 'lib/style.css' + }), ], }, - */ // Userscript { entry: { - content: './src/content/content.ts' + content: ['./src/content/content.ts', './src/content/GM_fetch/index.js' ] }, module: { rules: [ From 8d8dea0bdcf1628f70995dcde075796dc9418eb8 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 1 May 2025 15:38:51 -0400 Subject: [PATCH 32/43] test: test both userscript and extension builds --- .gitea/workflows/build.yaml | 6 ++++-- .gitea/workflows/release.yaml | 6 ++++-- bun.lockb | Bin 215301 -> 316849 bytes happydom.ts | 3 ++- package.json | 1 + 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 380ea99..aed693a 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -23,8 +23,10 @@ jobs: id: version - name: Install dependencies run: bun install - - name: Test project - run: bun test --define __VERSION__='${{ steps.version.outputs.version }}' --define __EXTENSION_NAME='"tf2wikipricing"' + - name: Test UserScript version + run: bun test --define __VERSION__='${{ steps.version.outputs.version }}' --define __EXTENSION_NAME='"tf2wikipricing"' --define __ENV_USERSCRIPT=1 --define __ENV_WEBEXTENSION=0 + - name: Test WebExtension version + run: bun test --define __VERSION__='${{ steps.version.outputs.version }}' --define __EXTENSION_NAME='"tf2wikipricing"' --define __ENV_USERSCRIPT=0 --define __ENV_WEBEXTENSION=1 - name: Build project run: bun run build - name: Archive production artifacts diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index a6cfd0b..adff067 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -19,8 +19,10 @@ jobs: id: version - name: Install dependencies run: bun install - - name: Test project - run: bun test --define __VERSION__='${{ steps.version.outputs.version }}' --define __EXTENSION_NAME='"tf2wikipricing"' + - name: Test UserScript version + run: bun test --define __VERSION__='${{ steps.version.outputs.version }}' --define __EXTENSION_NAME='"tf2wikipricing"' --define __ENV_USERSCRIPT=1 --define __ENV_WEBEXTENSION=0 + - name: Test WebExtension version + run: bun test --define __VERSION__='${{ steps.version.outputs.version }}' --define __EXTENSION_NAME='"tf2wikipricing"' --define __ENV_USERSCRIPT=0 --define __ENV_WEBEXTENSION=1 - name: Build project run: bun run build --mode production - name: Archive production artifacts diff --git a/bun.lockb b/bun.lockb index a4c615f26f8c1870ce7a98e24342e12a17f77183..c4e438a87aad27075e30c334671296d22f8e8036 100755 GIT binary patch delta 103865 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%0D|SFKF1KJlGT5J%en39CPM z==*9M_Z|v*7UYh{x|*E2+PjcOPa4Jv&XCEuc`kXifQNzw->LKa&dWQC-@PxlP5e~O z%w>ve;=aax$*>-Oc9*wEWB1V5)riEBYu!UDyY8HP(&VL9Bm4P~dY>4$;Ad{o6_^hQqL0f44P1yIqNWCaJ@V0PSl8V*bNdMi3E`Ls( zEs*qZ1^?%#gK-rfkG+yv*D{@cJ@lofi^w+qjoEKmGkwK*-*D4AYXyY{B=o)1?HY44`B9MC^D zYfKLJ6Y<>a;*)&Dv#z8y3MX93`p7HRe!J;=Q=?1SR_#;omUXm-toX2_*foJO+xgBP z=lQ=A?BD4+%ky+IRGMGcA1)o~;}VZ-g_|n$c3j``alVlI8rdj$%PTg|mh&b&48L6Y z@yCnHS0Ae*5h4B;?SH}>SW=xyyR1O5_`x;*1ozSGEiR7rOC9~$ARLKvT_Mx*a8=61 z8+;dM_6O{KZd$Z>Wrpgm6yZhuLGl-ePSuuK{wmN3f3WYd@S8lRGc^v@yWj4(DljAK zn9q8r{dZMN6hA0)iFY2aQYT9^o1qXcQim4sA|50JpBofP>;GkeF2OYOKWtn{{(b(B z1-cB=M*py}w>uO{bN*|AuEMmEziiC(fI?|P|13}iOxyU6jqj3w*Zi|U*I=5!7#oLq zLZLMOF$;7Zrd5rxv7#3gN|PA3Ks7MUcbtu%k$<<0TcBE)Ax^fFZ6UinAhbZYAl?lj z8*6WcLiiL6A^kc~eQCILYUR1a|{VY0lT5Z0$b3)BelAsS?3Qy(aV`w7zou`V65 z@h`&U(#Zxwh(8hLv@aCGRt&O%5aJvL*+94r3gLf*c>u9F4`gFLKeEw}hiv_Wc$6@e zgmLDD7U&VgCA?(Yi9gw*$455pLA;TVY&!{nLb!%7PaziIhirV8F#h~xyB)+;gb52I zo9hI~b~}iD1;}>0AhMN?FfSn%7bIanVfF};?RF416DB^G=w}+VKyM)4J&ovRJJAne z-a@P-MD#| z(>sWMW6qMw~aKZN-PvH47*pIt;hGob|=CehCxD1_fLp#`ELtidAsAxs7fS|A3(J%q{H zOFkwRhZcwz;Z)KNO*!OyW5VzwY&e(bhcHERp#>5|c!)5kqo5F8KMz_UA%qL(5&cAy z6;BChfu@G?4vkwa4 zdcueyEFwkpLzo>>&;qd#zD=00{ZI(YNka=X7vb&FL_cv*2zL-h0$~{$q94NSmw^^Y z65)0k$VO5KD=mO*B#m$kK^cTQ2`)fbLl&};EW$kma|kLSJVbC2!sZH)jg%1{C8&aMi6Ug9#R#K?kd0Ik-bhdl;WC2i2n#4dHqt=Y zouDSdRRotHEV2l)(Nctc2`)qUHbE_f#g!o&X(POypbo;#1a%SaTnyPr4`B^e$VST% zK15I-;U0o35Y|AnZ%f6=88h$VQtH-cHaB;bwyF2+J5jHu6BYouDVe zO2&|lybz8dxCP-(f?E;RSPj|88{tC)eGu*;=!>wf31p*f2&WSCL--Rxe}oO!KsE|M zIEP>$!b1du5H>f3Y!r-eA;Ikkj}i<)xWo*yQ7FR59J0|4gvG5O8|_4RJHcHDHxmp) zSjHN%Q8>c83GPO?onQpQN;Z&_mMkB0i2iYhF z;Z%aL2!A5D4`IW#kd5{uoI@}U;UR(t5H?>2*(e_2LV^bo9wm4P;kEXVjSeGRLhuN} zXgy@31cWydOhmYhU=qUOj*yL#5#CNP1>t6bsR+w#fNYe8@NR!BJ1alDXA$SyF-A#~`w3$!c_!MBP`+$+2{OeS^;1h&D5qyfUVF+ZSX9(vIe2(xC!A^wDLm?ZzK)8@#7s8_i zUn0DA2V|pH2$v9ijWF5?*{B=gjRfByTt=`5VS!zcjou>cPVgPVRRrH7ED{FU=mWyO z1bY#_P4FYa;^B~u`Vihu@Dsw#1V1Azvm3I}7ld~c>_@nr;8%o|A|M-mLpX-u0KyuP zkPQd%!AR%;hwvK$!&qkzbb#M+3V{*aN8kruwHG?TpE#SqFZ`PW9pG=gAsRZsQS252 z9pE24GZs3)zu1SsKYWY87-sE*4saX??}H8q&~WoU$VQNcW%ffhLNvUaAdQCG3DRj; zDGss`gN9=W^3ZT6L0%fxH~?c9YY+g!cxE~r#%k&CF*Znq#%VWY==`Bx+e5sz1CWIu z9)unYNg)9E|5MMCRGIx9P2_Bh^6>Ln>>Dz9RP@e@Gl!uTf`a%>0@RxXRdUzm(u2$<^N%0w4-N6rU0R)Bhv>5S&30L&)O zT{Cg>$ZW{Rc)5&v*9rOl&#$=%P3mkyapXT~$uji+$jCn-BgssgW~Zw9Kb8Lb|LjS9 z0UM`@)T~h(oUm{@{PCYE=@WN5mjd}10-lr>C__zIOM9Y9_X)`Nzu9QwLDe{@0MgUa zeU-@t=l)N1uaG}TZ};)v?zRO0cb|a&*z>=X`X2>OC|tljG|-JGd`&!FoC@8MQX-z8 z2*t_0_zDvz+Xj|Of>Q81ex3@)$sYvV-Q2_d0t0|YI(gMm>9tvt)^g68Q~|M?Z-`fj z2LRwdhTwi@wRBiI@gDhEkZ-bqY2tw_$jA734gdin<7B(~Z(F{exW+FB@-ceLxa$dc zY!91=WJj)4pri0Mnh=xhVI_@dMs3A~JSYJ1v2&BH9$OYbKE{SS#2TczPAFKKNzyDu zac3w_5C9-UaU~Q-^5rg|xU&=|MDl-4q&MXNaE>B{Nm6wx=jnMgIZ#jM%ItcPsz%2T3JA&R_0RU|5%MR8XtZVmvTnMyWL zPH|!Y08J{1W(CC&)$37QCB?A-0Q!^`u2CE@2WKi{cNIkvbC7i5#9r4a8xwP6Qw`(> zn(V{G97iecHl;0+K_`zkneq1ybw5dx0~bu@Nv)?iVz&HAVK|_HBBcQUIbdQu0gV(# zY$*>Wa~|KNIAY6rRNm7jiX*vCM=5PJQ=A;haZPdeD309g55?V|5Sjyk0stiW?L=v% zDk=g1?ovJA0mUsOS$`=Nwo#lCF$u*zq_{-@05|G>?G&dB05GEJJ)$@jl4+jUpV;Ox zMJ^_ZB&doV6sJmBE5$vbI5pB(Defu7sRJ;PH=UGc6sG|II7OfAde142w3pM=ILzs! zY_|jeaE2ngsESLua@oJ6xMd`3D^>3m#c7cYt`zs0;k;yzK_3IKoz&tyK_&r}0m2>{^Z znQTj6sd@$gfQQ8X#ID~cauwL@Lci6B`IHX&_>4kpYsLQ+1HUbF@PP)!FD_WDItG^G;F5PSWmI5RT%&?o!+ zD8-Q^z2~Th{Gm9Ku=gw-OqlgA#aWUzLjx0L{YPn4r-KO_LfWK+)&PJ`8kn#l zqBt8O1og%=+T<8u3jmlp1BRF4>_{DI*;|mhb}iSLF4L%b>qxI7<3F+g45}hY6HI|eu;-o35H<5vc;^b&x!da1aFm=R^3U#du0N@u@PmL1LmE=*U zO%BZ(lz^K_|0k!Ck;bD*kz~3^5)hHbvxK^#JJmjj{k5oiWb$``vb#3Lc>(}R>63|@ zb*O8-sB1}^SWea3LY_;TOnDCUDRL_S;2jN2G?Wz-=S_MCHO{Z3I3ECjBmxs{!hqs@ z0RYkzw~FGnksR3wOtce2it_^i=uFN*fDsLFCfbTWc`J%ErYZ&i0CXvCHN^#zi3I`^ zjm(7Nf&c)ORGV2ty>T!A;2H!I4bPmaw;h0quir?qptumy|9R15erijKB;k<^Z8A5t z6~*lUV4_`+Volj|CrP?ZX~vfFdb>#fr#J_y9+@{Urnn6h7fv)oIAVV%+N8VPO$Hw7 z!EV%pBLD#87bVi--6<{-fQcVnNsITOxIJX3Mw5xOJt=N40Dxp(AT8dD;>h#J^ox`& z#QsEcQN))cFwqOPQWc|#lb|YkQ&PteXF}EUrMOt~5>!1uO8xr)0J|tIkgB&I01!!W z!PGP2$oq#PFwxbvQ{({vKs3dLP+U9!Aeg#1l;RGOwm{i^2gMyCvnh((Nj>;58DA(a zjH-7806@;?*hKUZPLU+z2-zl0`uJ{&OC;`@;vy)HB>x~u4oJO7ic1CnxS&a&K>9d& z9!bNond0_RTq^M;2uzF*9KsQMkc=4Q=s%LXJc_EAPFgV4x1%X80{~D(2NV51hT<}* zwm`aHEX9$O6=VxK=^6VdlVkz#pMBp4D9w=M5gZLlH3zAS$rNmAi}N9hBWWD!sd|U0 zk?hGatMOjXRJBqTWo z94Rh~y0{R4iK#g0YGhJMX8Gy$wY-T#;X)p zPD}^EL?bJw)K5;*CL3r;d#a-DR|x=UgkYjQ)l6Q?nK;wBi6U=MH>?5xn9{&RL#d@W za=0`Z%Sq$8NpU38J2}^gw4GZNM^1Sr!3Qa~DeeXlg5v5ZjvTjq9g+S=E}+PpL?D2^N`OiV(|+B`7`000_Du!z8f z4ewDlCTIK7D6WO#NKoPl!Gy{mP&Xt&37JC>^|nzQ2};Q9mz0MTcaIW=Jgc4JIApe) z2qy)2M3F6&ghYVH6xT`(QsiMB6h{u&?MIUl&=bnWZDck?J@`3QPm#>;NL!MeM1kcy zG&NR8Es+ZG!N--*OzbF)j&Mj|XTk%(8Xf>lc>plt0l=6COytoLXg1_1LJ07xU)@{}>&BTsm251t6LZKnxv#SULdv=m6}e0}w}_98k#s zdX&t}j?nO|%ng3(}@QVS!7=tqzl@Bri7-9f0%mCm! z1AsRS0D2ezykh|Ho-sMO+0FnUgaN<~2AIge_k;n!Qw9Lfh!Lm=odiji3;;|S0GJU$ z8DJs?A+ZUGW5@@dvk(BH2mlcTfIb!gz+wgfstf>>832&YL$VA2 z6d3?4U;rS`06>m0IS3NZO1y9#1AsdW0O}b4)Gz?3WdLxK0YD}Lfb(PsAhRK%Wf#H_|7={Y`WLoaq3#&;f8Ga}_!Op7hBC%fx9CCrO+l zae~C>5g$i#*$99+2!N#w0G2QSP-B3JX#<%iG%x^YB=*l@0Fcc9Acq0KQ3e1P zA()uv6PHCo1riLAB?1x>lJJ1Uup|~8qysQS2Vj^!x%O~}4nRE}fCf4MjdTF+lJNlo z@C*V_LkFOR4nQk?atWc34nRH~fFxr79vT3<=m3P#0f?Xj;7bRTYxW*n0r*D)V2lR94;lbpXkfyB-KR}DTH?LQw9pexE-6ebA;8IF zUq=7{UjP6f$!n6O022C>P@f##N8{8nb8wxNTw0; zJYd58FXI7VDGvaeJOEsw10c);06z}^kqiKKGr)xR&Vc|dV*sGe0Dwk3^K}LQ)eHcR zF#yP80C1cE0NF4>X6hsK$>^Iz(UEijg6IGQ(*fwG0q~VJ88j0Q?S%kXf&f^C0MJI0 z!7qt*No=c$09cFwP(=W!qsch776Nbu0>B|#wJZ?;3m6dpWn>A*-cq|eZTMj<$3b#-aGxwnKS47&dk}_R2WtPDvT+kBn+t95|DX` z5FAy-4VLkgkh@{tL%);>xnCySj(|C^P$t}bm?sl%GC;H1ED@4ciID1*2)V5iAvG!y zGCP(KYb+s~VhMRRmXIy6glvr^9BA7aOUTApLSB#v$2wk(2@x_ohLB?tA;%>`5XN^f zC1e0dCJAE(whN*~!Ct}7GrlPaI|V^&zm^C=5W#AckXn?Gs&L^_LaIXVpnFn6 z9;Aeacb_B?a#$kdGfF~Se+1qPd>OFaDM~mD43riC__vghhtQ}*IP43U7XT@M&Oa#O z_#{+8{LU%-764j-Wdhy(GKP@HVuY&!*4d|;n*i1wOomoi|G&o)@_j5JXJZKo#tKA! zXe=RpV+rYoZB!y8J(h6164(`RO8{vAycu@47(!OY2+Vm>3?XU^A=xp6+a540VAtXY z6<~?sJIc4MB;ezVp;$sHR}#4KC8(4fFBb4H;9#IHC;%7$urGf30I)8MBm6u#P+B0c zKwM|U2#^(^>M5C!FJumWa=a3O*UxLvARq?VH0!bcUyLE-I(QAC{#=cr;{yFNjRrSOVx0BIGtnAT2;vCQ^Z>06ob;Wsq>#%Oj|S(E}hU z`|byskaHk8ln$+f_Vr*PX%@jn+I3}&{{wVfDSsz0xrN!Hq@zq;yWM%d^V5;R1g9g5t9*c0L=fn zRG9oQ@t0|tkq4$cegO>DJFbi)WEv{MjVhGa8__?%Cxq-3_gmgGU5Ix_%Ptp z`W}=#&cPhlYI(~+$)9t>Ypf;{*KS>X!$lM?R1fm>&vX7FB2m)XPZXfuvBjK-3X z#oi0=$_+}mI|gnVh|7zbya&Djxea+gMw9(;-9o-UP6=5<2^pn1$9hn~$+v5389N?! zJ18r#9RLLv5K`Q_J^g`(wrZdgV(tU=gk^E_=FOY)%2*hHSOL8PRt1a-*c596-~y}( z+?&FL$AFeVxh3K2g%24HP>>B#$`DP|EQxKjD>w@geF2$b^M>|^Jryz(&?>YIh&e>~ z2Kp*zVRr$z033t5v76}f8RpMA>_KP%ia8W?DCfXx zp|$`h0)PY|48RE*Bz|;lCtGBq@gX2=Xbb2YG+0={u#R8_W9I?@3Sbm%q8~tKf#d?= zg<}jJ8vtjp4}r+)b?D_#2KliWAr0^G4*#owD8jx20H!Cv@r?r$FvZa@G6MKS<>&-D zt9J%wJK#lFJ-|HC8Q@ab{a|LYr%XGfgl?|}Vag9{~pu=qYK+6XnhHh{|13pdyEC&Me ziRPOIA_BklX+p>s5tU;X(-#B|KSc5-_z^q|g9Qc*gc6R9Tmq1$88M*nx0+4>Gao-| zgN^Qf%s+mD2ZH9DHh-ubynw2J{xF;3*1AnXiy16 z4pewdq0R@GCcRT=7_to%j7-DGcDxj@E;$j8pJ+^Ce zhEbujx`2hHgs{J89C=|e;c7kd>8l36UjXd~!NXxqb=IaYOr?7>G3VLL#y zQDp9~Rg4aRQqUtHy+I{i%0p2>_|M*&!lFxkP)x5Ua%!O5^uYZ0jF>ZI)H6Zr0U`#n zqG46Pv1huL7SxysVM%b89)UW*SU!mm z1V2MM>OdF(d?F7%!FY(n3M?E^$RO}Ats#&Us!i9)C#FIETYL0|@1}G*JCm@-+c7pe~7jn=jY3_7 zDhiuNVg7-90=hv%5)F4suM?hmC>;Ex2NgU4oe}yX`0gg=AF%q^!+W#l3qVI8H6f01U@jey92^I5B&2Fi1-&6O1{A9cC8V=fAu5Lcj|$NMo%s)I9Gbtg{H7`a1B_tp2 zH0`uDwo2Ft(0p*T44w@f9RtwlMS0i=xIF_tf*(Nh4Z;e7uMj7;fHi{i0T;k~0zQND zBgBt*;cOZX!BOE}K?0uf2}dI;$<|KS0H_3D=>n!t3CW{`xG5nX%ro4$(0a5E;yaEG z%0+>6H8_)Q@2qcB_KnYn&30VRN zj|z1Qb?9K`aw_uROw&rOJUW2(&~bEnK1MBiA{qdbl#8y~pSf-54pih_G9Ph!d>!aZkDOheF2amfXmE;L!3q{Q75U2BDl z4F@D~_ayQiD#Bq(EH?Yf#631LVqY=Ywm&L%jWaC-w%4j3NA)Q zr}r^;P`i;w2bPcrB*sa8G^hhhz>|=sqa-Mge5eDLwQwjOSaOU^%v|!}^gY%wgaX83 zC0R&Bpw~F+n}~DYz%oCV*<&3kj;WL4y&dUkItX|5s@DGGuT9_X{(wpo%%*gcS=e&7 z5mHoecg^{eX54!lPqCORgj;fr2q}7cbcXb;(zg+nCRj}!O;$o4W=FcvxOgDIs5G(h zmdf{SNWY6p6D($v#bhI!nADZVNiVVZt~AaFJ+m4iMdJ%M2d*vrB%MkVQbZHgC>z$5 z8l;A7>g_Zx9@{SZa?gi{jFn{5ekx6{nat>A2ljYZ+P*4wz}kcqEo8@0|7v!jD~*fC zsx=T&^u(`gdN%4dYYqBgvRF+Pa)34MMs4xnDU9S~=iHRaQ~wysGjuei67n0%>qg_^ zLE^fE6g_kO#kW43x_CCvV8&4EvM0OII4Ozk!iR;OKz%qek87*9U-7knt@4laIn7c) zvq5ZlZ`v@%Vj-hhR}2Yz+C#0y^spJW*Cp0_DyUz37$0qimHfaT$ zJ(R|=tvzUpRGWQ;qD|Rf_^`1CJ*iDHvkR+eTzuzhf(zcO@WPhrOvyIa60O5qPlwjh8mGn57NMBIFeo zk$2V6T65-Su-ejjX6r+nNvqkGel$+{o_YJwI2zl4b?-|J@ogG#VNyIrqlSKZl+ zN*0sFVzLv`jXC<$IB5i%iI4N_mp(L3^0DfDX`FFeBYr2dXy@3DCmwm7{1q;x*PCeI zie^!@_wA=fO{lduoI`X#uTbmNb+*56M6V2S{4QtFW5v}!e%NyP3tk#4nsj2d`q8-5 z;c;TR($=4Cv+?;G&xSL=bJm!UqQFbbs*f`?qT!TWHXa1fTGR$qs(ne+9v{69k^Ns& zuT(2~X{|MOy+2Kn7BkBL8b_;9mN9^4Xpd~i0Gcc9Wbp%OPJHw!zrr3LNNtkBjt``9 z(tYd#K9;iBLDWX8G-I6x(TsWt%>>J}*<54d(9NSd)tBnA)q|+5dD~_}rk-ua?`al2 zI_&u6JKtYfjk6pl`Ooa9LDVK)XU#L{V5ujYn!(v(K?aSBAB|%GgcNmt=&8@G&8|XZ zTTM1BsLO0`28~ON(rYszePkW zCA)htHN>xIDLQ!Jw#2yV_qMc#cZWpw@L)Pv;&P@T`*ko)kT$V8Luj0|g0;s-ZPu)i z#<2-QXk-2RGJJ2!o}xCLvhDcwZ+E_XQWH?3i`cKuwiR;ZiKproY}@T> zSYJBama7y+z9~6Mmdj6${C(uEH~U`sCcG1kX~(_@QUeHmB}lC_qdn`8PYrZTd$x5Z zZO^9W)6Q|~bQcdDQET&=F9#go5!CbqD`9n3(s=g#O4@js9v$J?D>K}`FCH{8JVR=p zD;QM$g`4)TJ2_@_$2)oHbgbu>JBV4GF{@kpQ`g4G;Zg4Czz*L>4XJN;5OhdL`l|W9 zs!6X>X@Xts2Oo40lKt}3Cf8@}X#8xrtk_Br8y(JiPD?q~ zWXRlQO)nh_r~KMMkhxR#=D_mS6DDgEvk-DqOX<>X-lTViHeL`eQ(X~dTy1VVTswCX z6fJ8=%x5Dl=~%*VPV#is!f>JHikP%7$Gvdp7tehX!2@P3<+*o1H=NryxJI~4S1l#| zuV>faRp<$YQwAzRI~i7eOu^?H5+9_Y5|$dP5Zr1nI=nQd>c<_=K+n;bEk{eKR@-h) zu6Ez&;gkX`rT5D0om=~m#Bj>}3U^=@IoZY=`NylQ4yUZtQi{gzE=t}Udh3gD(ndw_ z{iz2&l-@b8p?f&xRfRijiyoL?$+dIt*AItNqBHd^I3P^9Hf2Z`bwr$%$!AN7p4iFd zT^eeOK8i2{zh@GR)v(_88ExkGhoDUptKegk;GW>RwNE{^$0H?-@QX>XNaHJcKR!Ds=^?b!MRYSdj{v&>U!C68=Xkfc4I@#V$pYo6Rx*2i zH8q5qB#T|OZkKyLptTdS5|W?HjlrTv2DaYQ|J3SQVLkBgWP*qRMax@< z9HTA`xxx#jqq-!s5S|k@*GjsSIIX^xVk4xhS(JGsFaP~UtEbN3DNs>tnPwhVpr~5A zad+1_qCj4yg-jMG+7FufHT0s%Rl2&KJ|7E-l4i1|n9|5Lv*_HVVPh7btK$aKrI{>d zQwlk1<``ws*gNkVy;FMm0#8Xt7k)PLd*nsWJ^0wEeIuq~&1z+uSvZtbWE!6K&cR+o z-hl{66NQFbL>JDtxnuvj!e1AM3r(|#ejihRTes}|uh+v3Jpq$|kfQhxS7mnD^oZyj z@4`D;ZC|GT_Vd*`Eq)4@`N_h$qUhDUUz_(_b=RxCMMI!M6DvPkQndS_)lFudcuf$M zr<7eHwb5;^`p>Wm!-!<>dGp65e5_WXQgeNze#n|;vXBb7jHf(o6;w=_wP@AlSvw$* z(nQL3tKgV%GuH3^y}=jHhf|JPg&^(swYyKd{l$DDMVZr9vE*v?>U*kI*B{fV6w<3Z zZxu_S=g?6@2QON_CtO`cD@#;Y;o9ciz6ukRkfICc9({B~@_`J#usA`o)XaPwdtH1V zN%^YryKGyCRvOODtsK=8DD*hVq{c(j){L%xgi6(IqPvp=UJt@Lbn<6T=xW$9jn`%c zD+ScN0)(<^FyOn^D!$)7|WebJNMDN^^bg@Vd8SCA6{D)eGaG9sl3F6WeUD%>-cp~({ z+E>C!dSo`;Ocu&ripfm&rHd*1`OVqBH6I`TPy;e7(N_r}g$}pd6$p~cZ2QT^vzlGj zG+!$rVN<`3ZtKtJD3&9)^&5uLY$Tew-;os56+7LqpPt-!=i9UH=9?Dh6E)y;xE)#Q zb@ze42j4YaDBB!lh|XJO2VGk_Usp5x{(*%Wg;GfMF2b^&{rZTa&ifxY&53zSxviN0 z*UE%AS{aRTqBQisSOx;#Y>L(p->LtiYY0R{ASANHZ%>^%6GEkmNU> zihK6}R6RidWb4D8ZQ(mWOEz)~<4Ncd zY$Btv#;X0q-g9`$ms?)9uehd_Hxu$iKUT!3RYSGyo%;*>jinAg^LBa-?xYYfM!)_7 z%A8%di}jUaH})IN>oK7}Gp?crW6}U2c>jJpE!1H|a(gNv^cemo^k>6XQG@-x0o<`& zwE2gkK>C--2f{fr{SK!ZAY@S=!Q1ff%(?%h_E{_fthI&!LaznqLx?<_Ur)NgUQKit4=O&9k zT~O=l(_1Mgl*MMUkjgE^$Sz*VJUy{ql@zU)WV#UZULX$bx4 z^SkmKE!?%D*aG#f5tPwd$hcF(Hk;d-?Q3UnCGZg^>SsKW@6Be_>16AM!MkmtVke%zih2b+du*gwgx1gZH%^6SqaGws0FGg7}&Yw-bVLdn*T+NG{IWg#Bo3kXD$Udr*DGNEphz8hs`bY z=$l!o#+#z?iYnnkLmtCSUTO$EI7T!xrOo`#HQHRb03VszWU&$w%=6)z5m{K!af-Tc z{VtIL&>(sTqN@y1u)UDUatm-zovu0&1#kAaaS4-tTW$j)C^`=+r=M*Q(v zk|sedx9XhNqXu1hDjCyh%fv->B-nYWT}&3tYy3Dtg_A@2ZoKgDR}fp+`^|*37$+vF z%`ad46lnA_TvM9M!8DF}{L~PNvi@v@M0a0z94EwN!5C}8qFF=U)%HjWA$`V)HM?Qu z6BlC>C-vYx;X?P0abjcljXFB^>=-|X`Aue1BQqgBM=)2Jq85G-*n8u|9bKwo{jtyT zX(G#-O&ilKce0|{)L=k-H8`cJ{V*os_H_%Pp>XwrUCF1$$$73kms831Y#ZOZd3Cl1 z5(b=l%l$UGyW)?#MQ`)pPO?97{{A*$*`uj4Y!#zIV@Qqe_=(PGg4L~2*ve!f(Y#cU z+i^6~Srs3>{m5eh|J0WV7gKa`6y04Eos=iWi)mXvDDC+j>0KL#ndNIOWlxH?fb{9L z3R4ymd>dW=`Dwh+$;DsXDgpr^lGoD1v$`vt(n2|{PO zo=L9tyxHgx;LBzqr=mNBqBBR634(22nK}E1rf=T{nW$;N74E})iI&!kx@)Qt=UmGK zaURI&n={NYf6Y4@>CEI7jwH4&L>pA%B3exU6t0d&Z$ElUo@0s%PwVo(^UtRoy8x6` zQ*5HxJZ4t&UfQTrDk%HnL?I)lF9`0gck$YyuuO_hB;MhMs*O~vvGU_B6Z#7PZ`CvH ze*E+MYR8bN?hUEsd4VE8B1CaRz1BlAbg%RN+AQv}8JjOx<6%^Y0Tgmc7Tf zH!GOtoI}XesfJ_UFIw>#(PV>_kmwHHC^jp#M3{TbR6zebX%f~X{)IEOL|@pFDKv>q z-jlnIPptvJsDPQtYnftoe01WetqVK1xt*t2G19VvpuW_OAN3<*JRvs}4MNBrOF9s~SGGi1KV7E8e`9Ut`U(?84!m-$Ic zdEk%!Z?x($?Y4+h3Q?yrnm%c=++uV2Pbal?`M$WIzJ)&DprQyLiWryRi${V%@s^xfw-cBc|}2vO9dbR@=P(Rz{^ zeSA^MhKrV)gOVAIe%4Ctj+OYPA_VBLj)X)#GjWSfMCA<*gu0?D`~S^hqJN^r|4@&s z=-7`iE2C?8S6yOB-&qEHP*igMoAnevDpYB*Fo*Bjw(HgRcYVT7 zpo)gdpR}ETleTt343h6c)Bd}BCeav;l#poLrR zIs3}elXvB$VpZ6=(h{Asql@Ezn&(h-GDTkv(*go*tsFC8_Rhs!pw!^tw4D$~rUSv* z$>nPc#~zuY^21RkGwfxD)9c4p{^_3&XT7%W+h4dCu$n?Cgg65MKIHDojS0UMRgTpl zfD}Rkj_JT^N!-!o+nVhj1MpBoTs_kQ4xfvVZ;iXox2TtL2?P}TKgZWn32FJpmJNx` z^4(N|sS2pU83+V|g>JR_rIaPdhwTGEV-1PJneR;&JKaLVsy9Y`+JtW>LV^28OWAp; z<=e}Dsp8@dan-+_}gV?K3?P~m2c zkJ&aoFcW4z_N`PxdTS{cpI&c%eDL+Bc|-hI>xd~rh+7+NZ8gI(3x*ULvJm2&BKErV zSrcbJmEpxH8BCs;kf4?_wbO{uqn(;;-5ne5z(pegz^I^{q5_*4iaC0V`b#S_2Zns87}tc zHm3BXhLDAjgFdm&p0GQIzVK!aPR&|O+|Ya9Cn$LR>C z?^8CrSex83bxswm{L~9KtNk_O)Z^Q3XziyHvNtGJMa}Lj z&&SUkF;;J%kc518WX+2cF?adxaxmem*ljNDcOS1;W45ti~@-U9FS=ECnR664I_fbiuH8=ITY8M&h(N zQY_&vbSV(sH+4Cno_lBnjugWHv=AcfAcJ90ftY=tuV%w9y4dsqTG)+;Xrp*O+4J*- ztgF0c>EY)0egBlGgl!*2n+zMNHL$Yf+iHH@5jak+S(wA83Fg_ct!nxdbgWbk#3Yja~bmy2&HZaWeJhwB~ z;SUsiwr6(ID^G!yw5`=SOLQ(b@chkdwYIqGmvlRCmY~G3)cPaGeg0eJ`a;%QE4Cp! z3&rXGSZn#$=D*28Cd?M&>QnWzzc=+iDK=%ZN#c&LxYD@GnpL#%u-0<~i}yZxVD-0q z4cqv3$gz&-qczVFWNq7d!`N1L|Gpdq7ZlPw!b7h&(+HnB@tNcfKd#_gDQC$F{lold zM|90EjPrDwBUanij337iZ}i@4;g+M{0*G#tM<;NfIYK(Mn6T)Ljpr(HMR=4LVsxk} z3Mg|+cNV{p($H-oMQ?EH0fk1hwMO&T5^+JB>!2Lp=7o28PuW9`{>-u_}jeW_d5 zj$DoiVovTzZejC8H(#Qg_D z^9FPq^z?7@|JIf7lnT$_2=%M%9)a_EA+bqBJ$zU3F%cZgBOeqAHTkw>eMZ)q`$@m> z*{br_Q1o+I=Zb`0nXq)A$GL9#G#V;Ns;~=u#Ya!6_zVjaue0}vhK9E`HGlchyW+x> zD}CK16nWpJA_Co_3pOZS72a#$DHTo;A5a8+YDC`Cv2%B<(FWY`R<{a6g${oX@Q{Zu ze7!lX=+a-%bA{W(=lACMf&ubsJjMKBllD zqVM+6cfyKpyM@Gwt|@LcA3tR`rt7^6$GI1PWv&zuzY|xoxg3Cl1%!(-0}53o&!W z!vFU4@V8t)Ch&W7ViA{i3WiFqed5B={X&bon04o<(_QxAW)ZY}+=jYetcI;#%S-M{ z&L_eURn2)qPaXB)#TGNXwZ~~=HQ&nn7%|j|WyV_JxmMnVh(h5qe5sZmQOSVJgzTBe zo|sAv!;a1q)M&MTxg)l?{e-Zj$$dbiZ0$DOeC_d@i^3_=d@=FM+p>>dJ>B&Y4j@_W zCL5_fpB)}b4aRo!1!6V#gJG>7+dVIfvj(@}JI!ASw_P>LQif5h-RbqK%H#k!xNdi1 z`^z78=F`N_J06@bJi)hZK6b2A%o>~##kFRWnLP6#YciY~?C(5C@H@9fRc9Z#;(l!T z$D$ih*?)hK2mdd!tZ6uXMe78(!|f)X(&QoL9!?F$K@ahtDlHm#>El1x?(yJXh-uD#>l%M>|)#~po0+<_a=cTeRtUu=Q?q?IKL*d>&0zd-ctg)d${ zQ26X$)x$Z)ED-n5HpE=$|9XKFAs_4}UhB*StjitLV0>nQxGWd{@~VRIyQ>0&(`rAw zfcfsA2K(;|2!6J=XrcUZqpv6WU_5GdG+!uE$flcdP2`|0Ax@F~8XGHYu@IrRm2x>6iSSb4bbG`Ln*(-g0JX|(u z5$isJ8tfU1#09+Gaeq6LDqY5+(`ui&NL=Teu+-M8`IGB$u0!kDGmF^b5!8@+e39sT zr{izV{AyR{j^VO57l}*zzc(2icix(f(?nXWzp+yzXdG>}nEf$=T0{D;+tZJy7O@`I z_L@LKV1RO0qAo2K92&kWqhFPo{;)8lM%5M8&4tbm$2-;`IO}9F*-R;EOPW|PGnYci8g^V!ys)IjfC!VZn3R?6Ak!0L{oDfZ8n2)&|5{VlG>+eXjS zx@RL~_fkQfhJR-)IamEoL3?gto?FUhjiLtHX+9e^h8pNA%h>kF=R23MedDNsj$h7h zRP=uPa+Wcg8tA^|tjNjhSvgE}?~H>QSi8GuV|I8nO#uz*7@DCqlp$SakB^}?V+8_4 z1K>~fVi(6ygRz2h84$GMG@)Op0VL^8?Q651=RfUqN!?~VOUXG z9sO6a*T+&Ty<-*o8J~jUAj}yDe-wn%38+`B6Hb4xf0li(6L#4ecK4msK)0@8zKPU8 z-&@0$;JIvm(Y$f|asg_@9u2g6$=4msV~2<|6VWkF`jm35>aCUOjq&Tp#Zj6kyU(_~ z-NW#`s1Bic%H*V*=nN5U9QwF~(5YVuZRvTnHjNbvq6%_S z*OTnn_%Qp4it4j{4jO}&z>9jVB`s^M{Rd;$X)yI^b|bUGIT1Xdk4dLN*}2r~(LeS4 z`g57_m(W&kSEs$6Pix0G<{6ODegxPu4CJABiur+Y$Eu%`=YG7rjFiRkcQI&b|Ht#)KM}-y~8?5 z!yOlG>hfpF7M9_p2IJZ-Lb%rIKLx(bdBt!<4I#;se#%4zzw@>b~A=0ijdtQ?IP z|7!F?#L=r(X3wJ8+9`V4VL02FMKiQpAk9*7v^<(pqqoc4Ds4>HZe{aTnh{^2LpsG| zN!1Q3FgcqxH3ps&iqgguF|pS}({W%=4}W zlfM6x>!*5y&q%8_Gq=&&MC z_3rF0nSa8knIev?YP17(?NZgbibDR^2+QUDi5dXQt9_bnIT1=%)tzkJZKcK3TJ)r*4Bm z1F$&E9FcJB6)8!7XEqDHGU#V*9b1TgnZO<$KbX8%i0Eba41V^{-3{RAOEslo9Mkr) z*ZtIBoWEBnvR!6)&#!B-3YXtC+xpSH>}TY7Zm-ag<1BR~OVa`v0ywW@CghF1#VrDq zO2+*CVv5IQj~IPtzxi=5$PV964b}P|6f5n_ zL3X$RCeA@o@~io$f-ektw+inR4+7Wd(8_7lU~GR#2)Sva_HJHL`RG=Cc@fg%5F0j! z8tC9dY{)cfHI6?dh!n_}GHOzrqeWP8prOKPOb4z^b-ue_{klk{+@kwltw{d5^U{s8b_Z$ z#9o_Dv$gPJbpIjNrI1?b@k7jq&kiHl`a)_={rr&72SzvUf4u20FXHUF)(`0|u~0l$ zJ6;()`HpyPp;+jwNV5ZV-{Bz^ zxfs=Q(6`@V6J}DYF{8bZ@}q~pH^fob%Dy{`wyXN>5pnZU z#9}l?bxC7JjdQ5MxcaDATpv{bK6B=B1tFEpCJPQh3;wa{5#?6Tq4!Hav!)?xlLWCP z;dqO+5c1`5Ha|qIS^|1=GDO>T`}6|=*|~B&UcXAn`8y81^NIQlLjLfGo`kIWP)y02 zvT?g61BCPQbMHs%N#Rm?!k}UJXtL)tm**?_u z!-s6iJ=DN<-cRf5OU|e-xw4D2wp8^a*6ALaNL?SXCihbVEmIA>=Oec6{_x_Z{SUIA z@1Y~9e1dgbL=DnTHt}8>NA;1EsfHc8m)32exBmPgfi9hWzHsT7uc!XWIh%WXwPHMb z)oxu=cu*#VCEr&P=FqtAq+r_xS&zK%`e*Nasj;n_ZKH+k~ zwO(uWZOE!0YckNKXV?=9s6o@u*pUS^fxho!7g3@1DGg#x7t%QULtlxd5uYTl9lkCF zf#EDB3r^}*aFkOs4<7xBc@|P@eDt$fJZ^ErSc|nJdUGPHx`?)~rUz%%M1Z!nHDGy* zXo9ZjRuo%Pb06^N!#3mAZ-m?|Xm(`w!ta_wm(=ua`2?%Em|E#u->{yGsnvMw8zH{$ zKG@-0-0apcPPHwh!FO!QVrtN@w7liVVVKW@9+}XIuFwn zD%9;bmb`?Xg(*@iR9Z0tXUb`M)8f%0*UqOxs^+41|2$r z4OvR9)cgaxcPY)b7oQhhJu==#zj^AYn28jeb$#=^SVpS{-nHY*`2eE$qzc*h!FkqX z88y(;=UMtPYNcn-GxsuTNKq~b(K9A*!R-EXzN{3!9baA@{`rDnC)`ADT!E31c2T6C zJ*hBi|Fc$?6ya!K^nzcK0<93G|o?1SqW+T6AL~&RUjADh^j71TgiDr^FBw*E!T*va%>-nj!G zEE09W(lsTsWh$0-rrh=@~CyKI~iwwTct`pW3spS{nh!5*FB{}`i$zBhnPUPUwhdCO){?4Q6UHTf2zk2j#Xch*ynhXxl_+B)Wc(r4;!$dCc_l%xZ2J>W`;SlA zA)?Tj>1R_{7YZwUX5Pu(XL797%bw)l zTqx?VHF0>OHu9>zAFzFoQgi(1gJNaRO;0^6O|N-_|2#DAAe@LPuKpN(K&r+g6zBTA zd1~>}HT2WgT}8}d{w7&Tj(Ae|j|!NAIWr~zB{W$QXU$5);Xbp;oayrLtZev)w8=;m z(#wiNASl(HzlI;umFM&NgNobhbhx>9S5(E%pz%t%ac#ySHO1o%y0TrWzqnT=c>`s& zd&-qUs3Te7RYla#yP6+#x$|63zn4d^2n78upW^TYT%z${p-&BnA$dL7t{nc=pYQRg zeq>VM8rO69)qvMspz2u=XMlg7<_HCyxvF0ujwj%CiJMnWLgU$9f1YSd%~7ZGacHTSA-@{PcZ-ViR9hh6$WeI~Uc#B{ z@Z_jjO18_bjZW*klBN1oPnPO&y4e1GG@+B$03TRHq8iX=t9(HDj#pMuwY*F^Lz6<~()HwIMKoAJ z6?zth9}MS9sG`2`)A&lsxB?Yr3MrYc9G6EYS?R$z-Ht#&@%gX}d_e}4fX}Zw zvbu*Vnia1v=<<5Neh8zZWV$?AE>BK{4TrPlI$WM;xqNr-&$+^smKIb1>Z%MNN}KkcSfLSf-4B6f1la^(4ZelNzB>{s&~K3}*WxvJZz`uRF? zxg8jm5_I@;)SymxEkm9+E8nduCE~1fvi{_=VPn*%AgJc~+>W5?^m+9_Zlqa&Zi?lU+p|jkAUD{16 zTZB|lgfNL5v6wZj?}FD9Y}#zUBsT%2DzVmkXgmw;p-EC2lj7tg{?=T+i0V&PDyT{I z^Y+3;4Z2W4e>AZrvOv7DS!TJOY%y7wV>3>|a5o0hVW8>9C5W2p77fs{^ z>vQ!3-4b6)ccht=CR(&TPa89ORcp;yB18m@#bVLgV=sS9?UHRp54kySp`joHwhrxE z={72j)96%=ElNt1Pl^~@>G{zr5pshy>meubxv-XQLnN1PKzm=NgCn)V*dWRG_mRQnmf4<^$ICE9D^g1;*)Sr@-YI z`=;1<0_*%7ZCW2_5cDqayUlDiTkPrS?1ks(P?p(4PGlROr_H5~tjAWG$Syoj%~A?$ zmMSN*&f929nr2~3chF`f3|*(C=v;V+nyTXUYm%rmnW`A`eA?K!&Qjr;LySyO!Q(OA9e2jfWJC*b22N6s4H4hqgvdrQVC}Jv?Bo4d{wx zd174Y_M;2ka$Sv{RrH$G*`Te>KBe0zC;rP-T`q@`W=dy{9&$5I)1lGoi`;o`QE6nh z(lz%hQ**h!uY9fLvXri|%-GBAx#cP>mjl#fOZ&*pIY*b7uUqW-71SJ6KPcVj2BoPG zsd~hLtD+~zg1sVo+qrbsNG+#aJ&}o}`_r*>EfKm@xk@6^O82Bpr?q@xwXJMT?8$Ak zUJUew@NPDsbU%a|8L1(bt)zl1W!FaEis}rjrcr7PXFz>clZE|amBmI_W}re1E?13s zU7F0jN_VBKnq97rvKe@7W`ib?(o5IIwGwVXl&dG46qc}TT`##(E56Z(Efd{z2){uA zGn>>_ZpQAuU2ZF{u8u@h$$WCtSayyfMLmUBaAF?@(V(9o}dP1 z`<=F6Pj{0WK>fKyo3kCa%T2?T{(igMD3({s>U5Wflq3{aX(SJj*t^~3)>TW|WU)Qu zb!^f{^e%R>hul$=D{j|QULeH+P3XvW^^#{4FG`dbOKjK$T9ftaEw^BmYs-CE_pWl~ zMw(s6*Dm+DunrZgc2Yxk5Z_tDL0pOML=63~EZX_#?pf}Iu)bu8?;kW`p zhbJ@N4RftcNPp5vtZy0mdxhUd0WG|ZcOY5fCu!r)#3-wVAa23%sIiLn~)Iv|upE<5mPrK=xte^}6yecVd|25j2 zy|I_pug6{4+)PEHCz!2FW>2=3jg3kkv1Y;#?8!EA%cRIog7dlN8SZzm(8tX62bcaB2lWFzeqFgD94=1N^-e(3cn8mu5;z(`}6YsxsE_?z?B0t6PImV0Zzg!hu`lgWFEVm z%nWIAhZvkJi#&%%;6bY2<#4-ZsD2G>$w36`?0mP9EqFx_$C|7p>t7YC$tG4g0n>1{;n<|h6ST7@U!cag zlE;)}B{}?{n3=Do+??g~mK&9bNA^^2+1^eMu$!zT>t7WffDzN{0loz?DLzM#O-+=I zY|=H_J5j{z1#9rYz1;pyM*)N-RQ-O|tfrh0uRVB?vE)0?Yhm|1NfT=z4lMXJ&*xSw zY+qBkc^xgpH#Q>??3-Eo$Sv9D-Q+qY9q+)WuoT*;6SmaLFm3CI`@3k1M|V~1d>?V} zKy45shz6_hBW|3l1F*wYM;?TN_~!I`0|6!Ab4>Gie0jiXL6Y3u{5*$85gZy2C~vk4 z%fO>fbGtk$+kb#IODgpW9L?e2$hT};XSr3=QnkTMh^$?)!sJbeY#nnP(HUI|_OB+V zvv(WH&1(g6)l7#cM{#=d@*JKl^v1|uswx+<##QCjapB=<(oT$3v2$tKpo9!!fr`Wx z`{kDPw0O74O0xD1=Hn*Xyn!C+6*H~{eHH!YJEJ`eA?spF0Ll?7)#-J+0c;hdD^72L z>UZR*tl3%`-?oATZU>gne)C7#k^}S&odOl-`8Yl`bb8g~$T1A>CD&_Ik`3E%7-HfgCpAhU zx)v=-pYh?nWD`3XD<`rAyJ&qWh0FZrkB<#J?-k8+soI<>i&*Kw0+3?%sCT&ZOZNf!QYBmD& z!db_W$%v(z>Pm{aYWVlkzg z>?NF^r3RU!liY+gttH1AveY0@ca8PM6WE$1rpnD4mrzb8E~qHz4{_R@6C#2#n8Prg z!V}fHEKSk7GGtaPrj!zUsuDZWQZ~e31@M;Qc}v=aVc!xz#TIUXow-6|>iICaC3|hA zH%}2#QRsid*QyUiJVi_lA8eM_$^A?G!L2%`Xf)AWc9Ellrp2>GzT`m%S&!T0#8Q;t zul1J8B@7sO_KxDavi~G zIXqioUZ&Tr1pTTy&E`~c{hTYaym{($r^=l}L2rHz+*95uE|o_p<3|@m4!2&5>T!DE zHPR~0bLFWJk1!Ddc}y=<9672d$W6w`7ie#Ko;$4VPE#{oS@49y$r5mRJ%Q8gX|H#qR4<^D@eNQTi0Ivly>e^j>nm3_>F=Q#iwV%*aJ82M#x9Iral3+|&*|Z3 z^sXdP5mE3k;oZ6>Lf^M0c1g4a9NDU0$;{8r=G!x?`X}w)x;zn+mE`g-+0wr9V95?O zvSqocL6^%;7u()hZkbkYS~(ShzEV@I@DP&dEoTKK)4`T*p$T#NQvjn2KWGYgx3Vmx zRUOGhd}rk~cMp2KL5JJzaRk+5ug9r!Ut*)OZ@E_LRN2jqv?Zn`i7l%lx3Gjc+u`OK z0b*xzPItHi3O_l{yU$<4uHh`zIQ)zOlq4!?CgB}>wt-wzN@Gjb)8HON{VmFsd{+?0Pe2({^HqPL&_-R^g?@EvzRRy-P56WCp5JLg ze8daBhetfK<2Px`q|!AcE6Jr_C|1#vn!W&3IA6dGBqx9`c#S3~6{P_As<;S7 zl@KA_Ane1kPO+J+Ks}rBm03UEk5PrCqLOY(<6~n*f743r{iD=e0|5y!ZT=LyFj|aZ zd5Te&2v@~oHVMkGRVKM%n-Vz0>CMY@dDJY;R#F2BPGbbUV&Z~q*g={wvg}(2#6|g+ zTpQ*rsRk6)@Avuxu-9B3oJI(^oVkubnVOlqwY-FNY$G=vjUEMaW;h*gH+T6f4xi6m zs0eRVKn*BaYPQQ0cH*l6{XhXAN#2mp?+xPgQf|0Ou7o!){SK5V^G307=j`s*vT;RE zIf?z%TCT|#OM4Eopi|)t9IGb5!9JKoL>Q<#*-_Yj3h6 zkmvHK)`(+y0d;D=>T&WDBi#H;=8&-Vnww3fUxZ0=JFi}^Q}37l9MLnLPjxtRakWK1 zyryKc7gA*-8{a{$*#JylI#FYFVLYI$UO&Ev`%vA9CF}GmDJDA?%1t81tQQd!2p6g+ z!nE<3K)f~uzi#>DIog_!sKjByYYLmsny*D1gF{;B!*)2)DNZQC(KT3ebZO^2Bc8Z} zOXzc@E6@+elzz+U#hTWXn{w>9jaE|$8TH;y48IaSx38eSperr4B6|Jd68ak@O<#V; zKB1MnhexPq)lc?SOcCLI;%T{FS0Kp!PXbEd&*56i<_u3sqb!|!`K)N1tfX+~NFBc` z&+o{Fa>BphV96!g+`yZ3x*?JDui+Y7Y9Y8F#Y8YJ#fs?A*ewFCKv0~Q<+!Gy_%Dn@2c9k}7UZzHEUyNu&v2tKMDY6?SJ=V@t z@d#KQndJga>>JT@&HD9d15rRX4(qFV-pMY3QMm$2cD~!qN2*|dQhZ*QC&+bQbCg7} z%qu9yetCtqijSzBi@}n%lyu;dEt(=C7}h&J6&ST#FP`Rih$GTy+t^Tl!X~1BSJF7J zSqss|Km$v<2X+SsDEPu!@uOT{wpdb%VF0L*s!+G-jp?bR?^_lh$N)FVYsMyi)P}i}cQF?Bf?{Jga@3R$;^cq?g$tlN`^Q zq{vCdi?7f-C6;)Nb}wFkjc$^Q9XIJlNzQb6*u+Y566-+a)Z*z>eotmqFVT2__)S>- zV>Ge2sG2-PX4{^o4cSjM{R~rv$t66(N^_5r9{|BK&Ze;)f delta 40943 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 diff --git a/happydom.ts b/happydom.ts index 9cae201..a51cee6 100644 --- a/happydom.ts +++ b/happydom.ts @@ -1,3 +1,4 @@ import { GlobalRegistrator } from "@happy-dom/global-registrator"; -GlobalRegistrator.register(); \ No newline at end of file +GlobalRegistrator.register(); +Object.assign(global, require('jest-chrome')) \ No newline at end of file diff --git a/package.json b/package.json index 9e9db52..e83fc63 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "css-loader": "^7.1.2", "css-to-string-loader": "^0.1.3", "extract-loader": "^5.1.0", + "jest-chrome": "^0.8.0", "jsonc-loader": "^0.1.1", "mini-css-extract-plugin": "^2.9.2", "postcss-loader": "^8.1.1", From b84b53c544c7c4523e390afae580362d1e225c45 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 1 May 2025 18:24:51 -0400 Subject: [PATCH 33/43] fix: error handling in `priceSKU` --- src/background/background.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/background/background.ts b/src/background/background.ts index 4eb32b1..38b9c5b 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -96,16 +96,21 @@ chrome.runtime.onMessage.addListener( const sku: string = request.sku const service: string = request.service const token: string = request.token + if(token === "" || !token) { + sendResponse(new Error("No token provided")) + return false; + } switch (service) { case "prices.tf": { - priceUsingPricesTF(token, sku).then((response) => { - sendResponse(JSON.stringify(response)); + priceUsingPricesTF(token, sku) + .then((response) => sendResponse({response})) + .catch(error => { + sendResponse(error); + return false; }) - return true; } - default: - return false; } + return true; } } ); \ No newline at end of file From c053af19281af1e9cafdb298bfc2d94bceb7b2c9 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 1 May 2025 18:25:13 -0400 Subject: [PATCH 34/43] refactor: less noise in `queryExchangeRates` --- src/background/background.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/background/background.ts b/src/background/background.ts index 38b9c5b..cfcc6ee 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -3,13 +3,8 @@ chrome.runtime.onMessage.addListener( if (request.contentScriptQuery == "queryExchangeRates") { const url = "https://open.er-api.com/v6/latest/USD"; fetch(url) - .then(response => { - console.log(response) - return response.json() - }) - .then(json => { - sendResponse(json) - }) + .then(response => response.json()) + .then(json => sendResponse(json)) .catch(error => { console.error("Failed to get exchange rates", error); }) From 745087a1cbfdbdc72c3ea25c71b15a22af0f79ee Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 1 May 2025 18:25:47 -0400 Subject: [PATCH 35/43] fix: stronger error handling in `fetchPrice` --- src/content/priceService.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/content/priceService.ts b/src/content/priceService.ts index 4036c8c..fac3b1a 100644 --- a/src/content/priceService.ts +++ b/src/content/priceService.ts @@ -51,7 +51,7 @@ 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) { + if(!token || token === '') { throw new Error('No token provided') } data = new ItemPriceData() @@ -64,14 +64,15 @@ export async function fetchPrice(token: string, sku: string, update: Date = new if(__ENV_USERSCRIPT) { response = await priceUsingPricesTF(token, sku) } else { - response = JSON.parse(await chrome.runtime.sendMessage({contentScriptQuery: "priceSKU", service: "prices.tf", sku: sku, token: token})); + response = await chrome.runtime.sendMessage({contentScriptQuery: "priceSKU", service: "prices.tf", sku: sku, token: token}); } - if (response) { - data.keys = response.keys - data.metal = response.metal + if (!response || response instanceof Error) { + throw new Error(`Bad response: ${response}`) } + 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 prices.tf`) } if ('metal' in data && 'keys' in data) { From 91f43295e8cf624c7aa760bc086a6976eb620df0 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 1 May 2025 18:26:05 -0400 Subject: [PATCH 36/43] test: fix unit tests for WebExtension build --- __tests__/exchangeRateService.test.ts | 14 +++++---- __tests__/priceService.test.ts | 12 ++++---- __tests__/schema.test.ts | 42 +++++++++++++++------------ 3 files changed, 38 insertions(+), 30 deletions(-) diff --git a/__tests__/exchangeRateService.test.ts b/__tests__/exchangeRateService.test.ts index c9c153c..a9ac197 100644 --- a/__tests__/exchangeRateService.test.ts +++ b/__tests__/exchangeRateService.test.ts @@ -57,7 +57,7 @@ describe('prepareExchangeRates', () => { const rates = await prepareExchangeRates(); expect(rates).toEqual(mockRates); - expect(GM_fetch).not.toHaveBeenCalled(); + expect(GM_fetch as jest.Mock).not.toHaveBeenCalled(); }); it('should fetch new rates when they are expired', async () => { @@ -66,17 +66,18 @@ describe('prepareExchangeRates', () => { if (key === storage_exchangerates_next) return new Date(Date.now() - 50000).toISOString(); return null; }); + const mockResponse = { + rates: mockRates, + time_next_update_utc: new Date(Date.now() + 100000).toISOString() + }; (GM_fetch as jest.Mock).mockResolvedValue({ ok: true, - json: async () => ({ - rates: mockRates, - time_next_update_utc: new Date(Date.now() + 100000).toISOString() - }) + json: async () => (mockResponse) }); + (chrome.runtime.sendMessage as jest.Fn).mockImplementation(() => mockResponse); const rates = await prepareExchangeRates(); expect(rates).toEqual(mockRates); - expect(GM_fetch).toHaveBeenCalled(); expect(setStorageValue).toHaveBeenCalledWith(storage_exchangerates, mockRates); }); @@ -86,6 +87,7 @@ describe('prepareExchangeRates', () => { ok: false, status: 500 } as Response); + (chrome.runtime.sendMessage as jest.Fn).mockImplementation(() => {}); const rates = await prepareExchangeRates(); expect(rates).toBeNull(); diff --git a/__tests__/priceService.test.ts b/__tests__/priceService.test.ts index 60376a8..29b719a 100644 --- a/__tests__/priceService.test.ts +++ b/__tests__/priceService.test.ts @@ -58,11 +58,11 @@ describe('Price Service', () => { test('fetchPrice fetches new data when cache is expired', async () => { const expiredCache: ItemPriceData = { ...mockCachedData, update: new Date(Date.now() - 2 * mockTtl).getTime() }; (getStorageValue as jest.Mock).mockResolvedValue(expiredCache); - (priceUsingPricesTF as jest.Mock).mockResolvedValue(mockPriceResponse) + (priceUsingPricesTF as jest.Mock).mockResolvedValue(mockPriceResponse); + (chrome.runtime.sendMessage as jest.Fn).mockImplementation(() => mockPriceResponse); const result = await fetchPrice(mockToken, mockDefIndex + ";" + mockQuality) - expect(priceUsingPricesTF).toHaveBeenCalledWith(mockToken, `${mockDefIndex};${mockQuality}`) expect(setStorageValue).toHaveBeenCalled() expect(result.metal).not.toBe(mockCachedData.metal) expect(result.metal).toBe(mockPriceResponse.metal) @@ -73,8 +73,8 @@ describe('Price Service', () => { }) test('fetchPrice handles pricing API errors', async () => { - const testError = 500; - (priceUsingPricesTF as jest.Mock).mockRejectedValue(testError); + (chrome.runtime.sendMessage as jest.Fn).mockResolvedValue(null); + (priceUsingPricesTF as jest.Mock).mockImplementation(() => Promise.reject(new Error('500 Internal Server Error'))); (getStorageValue as jest.Mock).mockResolvedValue(null) await expect(fetchPrice(mockToken, mockDefIndex + ";" + mockQuality)).rejects.toThrow() @@ -82,11 +82,11 @@ describe('Price Service', () => { test('fetchKeyPrice uses correct parameters', async () => { (getStorageValue as jest.Mock).mockResolvedValue(null); - (priceUsingPricesTF as jest.Mock).mockResolvedValue(mockKeyPriceResponse) + (priceUsingPricesTF as jest.Mock).mockResolvedValue(mockKeyPriceResponse); + (chrome.runtime.sendMessage as jest.Fn).mockImplementation(() => mockKeyPriceResponse); const result = await fetchKeyPrice(mockToken) - expect(priceUsingPricesTF).toHaveBeenCalledWith(mockToken, `${defindex_key};6`) expect(result.keys).toBe(0) // A key cannot cost a key :P expect(result.metal).toBe(mockKeyPriceResponse.metal) }) diff --git a/__tests__/schema.test.ts b/__tests__/schema.test.ts index bd1d516..5ba02b8 100644 --- a/__tests__/schema.test.ts +++ b/__tests__/schema.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test, mock } from "bun:test"; +import { describe, expect, test, mock, beforeEach, jest } from "bun:test"; import { ItemSchema, ItemSlot, getItemIndexByName, getTradableStatusByDefindex, getTradableStatusByName, linkBotkillerVariants, linkFestiveVariants, prepareSchema } from '../src/content/schemaService' // Mock the storage and log functions @@ -13,6 +13,24 @@ mock.module('../src/content/utils/log', () => ({ logError: mock(() => {}) })); +const mockSchemaResponse = [ + { + defindex: 1, + item_name: 'Test Item', + item_slot: 'misc', + attributes: [ + { "name": "cannot trade", "class": "cannot_trade", "value": 1 } + ], + capabilities: {} + }, + { + defindex: 208, + item_name: 'Flame Thrower', + item_slot: 'primary', + capabilities: { can_killstreakify: true } + } +] + const mockSchema: ItemSchema = { '21': { name: 'Flame Thrower', @@ -89,6 +107,7 @@ const mockSchema: ItemSchema = { } describe('Schema Service', () => { + test('getItemIndexByName returns correct defindex', () => { expect(getItemIndexByName(mockSchema, 'Flame Thrower')).toBe(208) expect(getItemIndexByName(mockSchema, 'Mann Co. Supply Crate Key')).toBe(5021) @@ -113,25 +132,12 @@ describe('Schema Service', () => { // Mock GM_fetch response const mockResponse = { ok: true, - json: async () => [ - { - defindex: 1, - item_name: 'Test Item', - item_slot: 'misc', - attributes: [ - { "name": "cannot trade", "class": "cannot_trade", "value": 1 } - ], - capabilities: {} - }, - { - defindex: 208, - item_name: 'Flame Thrower', - item_slot: 'primary', - capabilities: { can_killstreakify: true } - } - ] + json: async () => mockSchemaResponse }; + // Mock Chrome runtime message + (chrome.runtime.sendMessage as jest.Fn).mockImplementation(() => mockSchemaResponse); + // Mock GM_fetch globalThis.GM_fetch = mock(async () => mockResponse); From 9946cafaf566481f6874388157117c02662120e0 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 1 May 2025 18:51:21 -0400 Subject: [PATCH 37/43] fix: remove unnecessary files from builds --- bun.lockb | Bin 316849 -> 317623 bytes package.json | 3 ++- webpack.config.js | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bun.lockb b/bun.lockb index c4e438a87aad27075e30c334671296d22f8e8036..c86c55f3e65e6d46e3637a36bd080eabca5b1978 100755 GIT binary patch delta 62357 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

F{x~ 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 delta 61993 zcmeFa32+rvxA(nvpVJ|YMg>GvWQd9k5+qC~7(5Y2;NSoV0TD4kfP^uGDBy5{h~g}^ z*f^o6sDP-bh%@4VvxxJohyz4XqvGT2SHHb>b90~9d*Azh^;UiLRhcT--T&U>e@|tmW-KZMN{1R-at?>a~k6>wHE2 zn@Qm>cVTIp?8Lu3d!v85iPS!AL>i0Ko>4w#dfC`Xa(hB#cahp936VggHj@z9MWpty zgxL5OCPbQx)E?}M>?2aU!WU^NQXBdrEktTt_#%6X)UM9abicZ=ttYZ;R-|@OBavN2 zYRf85x3%b=E3&UhZJS(8xJRy5pk_uzL1neb^qQ%&$4(npKCh8TQ<2(Tea-mT##*6? zWz_}aCznkqZmjusZLH5TyXgJQ@)_0TGemycMI=iGr|qgO9=fYW+`fypv~29O8B;~% z?OjCn5~;lmb(nAywIe5O=PDK8i|MyeAcc_OvL5+bccYRgYA zpE|Qzq%ffs8MlXIE!89U&;rNpp&9J&C~uk&*+ZmO657%qp-iOq^rl*&Pnv4{^6If= zlgq}HS2WcEhc;E_`Zv}1A9`Bxr#xkLNOO^9BDLw}S#3#ObIqtjbIo}AUYe2LT;u?e z+AsDLX(Lj5ji(j4yM?x>?cO3CL~2)hB72L}PTWWPe$YNz;Gr)3eQ%NdL~8fmM`VAI z+Dkmmzj5GNbXm)=aiX=?*b6%)x_Cdg3Qtb7-uJ;)N=rc-9RLg4Xf5Zp39n4_%I4;4 zcm1BP@R0pQnuydM;lkhc*TTNvU$ZHnF=N_{g7R5A4$yR!)djQ5rc9Qy855?L&8RN_ zE??Pd)K0@+57bI7YNzSH%-3+1v5m{>2>zP^ynlP8Hy*&_%c^G<)Xb>3;~*_~ve5!A z?4a@NOke^SSF!TynaqSII%>SqPKw7Bn?7km!MKWMGh}?l)Nuth)1~xajW94=->gOW zT{1WOKu6`|p#q(hrxz%NT?@2LHHV0_6{&rqi>91VKDB!G)Uh(Id`!&*5t&l03&Jnm zw8LNQqE(4G?cGB=y1t9%J7My)F=dmhrLLR3&(?m}O$&-UBOa=)578lASzTT|Rzx;- z(@A?zHyvRQq;#ZQ-c2N*3Kl8pv&$w=uBe_V|2_r&e#mE~_U0my)*`i66ln|Qbkm{U zx|_&>BDK5p(Yf$#p~xX3wa*ud9KBn>xY0t8DSXYsDBDMQ>RjU3tMDK?kqAflA5S?pX4$%sJ)w%Lr{usO4i0nB`tG%YPR(EVg zLB-VZ)1++b>?vh4$2OaoDAf$lD4RTq4eG+u@V(sD{ckVOd}mIaF|BN-NcpVtv1KAM zc!c)D(Id2ey+>#(rqab-C^ILMR4EypA#bzyIPsy?4mHmST|T*ZvMQf(x71HZY2DVJrs;p^ ztntQ_Sq`$bZ@LK2xxuuxOsn^oi}>&uEokK!ZNy=&f^nZuhIZ5^6BL=Zk*1p zlgDX31IKBFCYDuCJ7cQIl5(w3`K*eWB3Y3Z1zMwBT=?yHE%4|G+WR+6&=!6>LD{@< zy!PHlF5WBS_4(s2Ts>Y3xWmQU>Sk3zrB3(C>T%Pih{)rUMB1`WET5!My%&eGPEiYDv=k7WzyLYAbNKU1u|D{6f zbL!cezI;l>%$emgW`F3qcJMhmRE|DJ18v^<>V{0dhcDK z9BjH&q>D)H)`cP|k=h5Y6zL>VJ1PuU>~>K0iBRizPpIX`n_s+#HQWT*1LI{S-b~RA z%BEIVJho7G`P(kl@b^gTJ$SKJCl*g;luxLvo=`ru-(uTU+1k2D>v^V2IPg*xANIdg z>-py;Dqg&FiAXn*+FLJC3HinAwc>YPuj|v5*XySG=?%Ks&bdU>f3QUNzF{}2v@qxr zk&Ys@FI`NSmD(K_D?t@YmC*w((fivjR{7)1i?u~nw`c*S7i)zMzF4F)x`cba@gkLD zR$Qb7mENY~C3}&k&s-$Z11aJnkphw09T$pp1o-(5)d^m|LlZuKp=NyBg^KU8KwD@Q z=;%HzoV!Ph?42vM{U}}NTvH~EtC%reD;dl}w|1 zRIZzKk2a^V(^PuguLWP$GOTRUy78zrn*4<|;gwAe>VNoJ&HenfIzHR4)#{)4pynPc zzkMFmYF1R2&6rU(Te?1|{HT(>^+5on}|LPMch_ zUbA~)z1~kk{g^gh9$6pOHEq3bEN_&p(^|)B^ze1z{>{2%^Pbhz)1OspCYPVi#x3I= zKE#Eip4H^z%FCx0R8)(3RxAC%1|4q|)dg05d+vG7uVTW~X*0@2Hf_+66e~pool-Qt zVDhxFWi!K#&4TQh7d6iM4O)jEUer9sPcEA|vwWOPDxYm_5I4T0@ye^KYRYHKwi0N0 zxyW4`l%XLnYaL@vK3;xez4qD-+A(KsP$gvA2FqBs_L_}KT^wQ52Bo_125sa4q7^#M zRWR184&I=nw}s32<4xLSe?Fu7-6zjzL2(6JJfoxc&!_eI+&5SSvQqoP>pGk(-cZ4A z>FZi>EE>nUcH&KK;gs?j6Uw{&2fO#YrOctzR#~~Lrn+MMY^i!%nd$b9=6B9JIs|WW z@wdG$(iSt(s@K(Y@Ws;rNjG5;C}Dh`WDxYuP!K`HFHK;EDcpw zPn$ZvVsiPyPqfF%W>ri>DZX~IW)Mp;7i|s)?A4{?t6MZ$tm8bpMeBFx7G2Mmi{3X6 zm+o~)uya`m-`%Tyu=^J}2)c$ri#9>mZQ8;U!on6U`v0(1@1FZydH=Fb2S~wJ%I~b1 z%gK6LnNbsGQaI<02>ylc3yMVrF6zt&pBsmD&9Hnm(tuK8Mf?Xs^m_1ZdZO~-I^ zix$~fGi~*qq7%N;zKS)|@>$c%$IcXqHQE14Q*B?b?Ti)CSmBHn%UCfwU$hp`-Ap7r zWA7%zANx^j9!s^c5LzHw^OtsLf5#JPZT4Z zkqSodCell!*5l4B3b!3vVm5{yi%ZO|VMTFCaOCbHM`$g2r2P52i;M}U^eXnh++Czc zxT;5S&~guv!$oR+C=I6W5fi++M=DschsdFE>fkf?%;%YD8&(`vV#bHd4=eHSZXz-; zoKjfq{njMxluieiG!;3?RdxFVf4hlJbGC1yg{v82ST2rEiTyx*FID@xMl zh;Um;Nw9cNk>j=by;Ejsy5@j!g%CYaIfHA>s{f9d_!M4lZo1qf=}h*0|YXG*G6fUaCJHryxm6RDBINf?x~<a`8b(yqx*ZyIrfoan!tQc4l%sXJ` zSWKn7#|{YV2BrhdgiIN+lVRY2IvJ9-2In5A_3>=YA3jitN`zItQbDtJ4QTU+v=ccZ zT$L^kmbBACv<>gLJDprj!;UAE1QQR6Q_?~AxjWyM_`^Y}Xyk^g4@(6-+v|AtELZc| z?;KpIRPcKHn5(MGigG(>BfPL`guP)pik*(vchEc%+Dakqex-6 zy;pJ2qSMZ%Z7s^})NrWX7*^s9?H;ZemJTlHt{RKSNcQjR&ib+{RqX%OUF6WPU|6w#QV)^w_Uhpt%y_V- zJw*=Z${XA>tQ?;9FNy*Gd0 zDUq~IIV{B@LqV^lL{7F>or^@qh1-Ys$t}{Mqw8p*w~}N#&h!qKk1Po;=p8SL%+B|F zhn-GIo7Q2)DJ9NwBN3e$S@o6 zOt!;t)u>|cw?1LzscC=WVIpUQQ%)`RKR!%k8dv7{u;b_w|IxI_T>Ip(5|Q)5g3-nP z`Vx_`7CZ28_6D%?4;PtgGym~$k?A(`aec$O)6)K?z9OYI%MM{hS&1JW;TZ9LI3lbo zOZ$C~6gkdz_GL%Lo&Cj;I<)NE4h}pjp6M(oryV8ITcoy0Som&H@+gyabj7nX-#g=&uv2;3zwa243>lf0 z;qvkl@3dpXy7IJt@3D4!PZj%KKat+H%sKr;MugkTi-XtuiJTz9s!^Eo`}7wX8cvzm z$Mg>?C!~Xq`s-rdJX~F#3Jw~e4NHdg{Zjs<0SweBy^4cn19T-!*jQU!tVVWz7i6>* zz9nN$Mu$)%J6rG1#G{ZEEO}h;~78;em-8?BX&hN{DgQwSM^Hyb53ByP3ch_ ze13xFp0Ha->O@_9gh3sg;qG#%v%mI4I9S!aIH*5ScZx=Aa(#CE6Q1B&?g34(=Ng z?|y6>JBEan)6+rsleAa~3(He})k$uMd7qvXcB)F7eZz{X5`Xki)~!`n_sJcqv2`K; zY-qgcAuqKarUVKV@lPJ+hHbEDSUmr$rltHBhlv!0+j|xVO-i-HRT3Cly0cOJQr?xN zVO@3Ff4NkoYdGbo;^2?cxXY@hr~K~2MS9xtIdOPA$<_=rou+7!mJWOuU{a$2$Qmx*9gK?QzWVMdzjTzyVd0c9 zeUhW%!QKBM(Zrc=SvQ{yd>yKhg8w0sSxZMUtp_t$7E ziX0g(KeHsro)#|@knqN7@rGUaP*HAK%vSxtlsBm?Tya)9xUEd%C+(L0eOcV>!n&f| zG4V{I6_sP+%D*@!7BTA!i*m>AoW#!*CCA1K4JY$cpWQR^wgW{8M*c$%ME! zMobVnR-`u9t{wMG2mx@KU^VF60REBCt1lly1iGix41H_JTD!5G$|Gb>d!06oviDN9Y6l!$yVS$ zs@U5)Ijo$Y4i1|VcPC9ddy2?FyFf$C^X@K(%KOcyiVUy=bTn61$Ekn1C^;2LfI0K+ zR9)|q;p%iz?lh6Zl;ZBGVE(iQyKeB}G^NunYe9?YO22MBL#AtYH9{Q>=1-4@O<{Q| zc+cGlo0Qo%?08{GFuiJL*Q8Rxy;VA5Jz{u2RfTmIrh|4fb~Z7c3b5VNL@SH=t7lkg zWOkq2>R6f}pApsCLy53HlM140tzm9hbzaJQy*gZRaoP_!&S35Zy=Q7u8;4chQ^Dyo zwUg|!>2I2es70(JYU06)tRL2B0mkkWn`(A8qDLwidwLubNpkt=am@OQi*iqo7iz@A z7H8NEAzd7dI%8*&R4TXyT)FR&@;^KSDUMjJW{C{5IFqV+mX!k2#lgB+aq9Yhsi4_x z!f90%r6>oWVyb?@4JKi4Hx$ikUth*}hZ#~=2n{)dl&v7D*zxo`T zqP9;mYvsfq#s1)|o0Zm)z@yL8e)Yntqf*|o^TN7o)4><##Zw9ex!-&p z61pQTnXh9l*G_^D=EqYJ`Ju)6T49x@2A!`1(PzkbbI%V~T%YznIX|qsK5Y&RJKj*@ z&AuS4ydfPtc7b+*s&s=F#N!v``P>CzrzL6codsd#lC)_bE?-g-oO)rbu@`<*6n4C^ zB$#;F&heE=1$SMh)wiNb@TYrbc?*gz*Jp{a{+N_M|8kKjVZoAO(XBF7Z=WiFCH>Vf9sda+edXzB%QsxGG$6OWObTDv|!SoT5d_qp`|Y!+oHWs9|Jv1`-Yi{r`6VA=hec;74Rmhv;#Fnv02_yY+8gJ z?A@q}pD6bEC+a_NwA0cNKhglgNA#_Mw3(|Kg?eD=q$cX}Dr( z+CTedks4d8?{9Ww_!r+27rgZr>b&ZfVt>S~C~WlYd$+n4`XiQ!Tx_w=mc><=dK;4z zCZ4&Ccw8NIyU0RY#+$c`%(GX+?})3~ayhEu_GQKXMa$z{{#Z_FaCOcKo^rK)1xhxC zy&)^Zx_i>z!z;s1tJA?xE8`Ku=F{QMxXuYiD^qQo$~F>+YkrieY!h z)6Fi(?#|e~==HnR>Z007w|lgxMpktA=^lNSv}5a-)w-Q(p4YC{n068H-&!p)INY8t z4odFT)hEa1aLc{%#={O-e{Z}b!b!XPRHR9=lm(aGr}CS%D0n~I7uKy!2Yv64n}Km> z-u+>x9_e8H{c$dZ3yN~r=)tj75B)>9!)EKxTw~S$9>u|Cu2ez2Iu)cIP+{Ae`hqDB z=;Sdr|78z^l@F(bHyxM=3kMbDuGMB*D@$Ev{P|uf_EXQ^BbZ#!aQJH$NDz zc(Ww;K_yTH{jLwiWuy9yf5`6PlltUxp#oYu6@2lKW||1=4^0J~AJ*n2!umm};N*w3 z<$kz&P%4NX-Z@BmrMyiKhn*fz`%NFQrkftc{#}od|CFnXgKr+$*^T8XKmDj(qJ|X* z=RT@COVTbRFFmRSC&Sg5BJ-FXRL4KIv-RmzaLZ#m*XdL$*z#B`;1q_bpxxtYjj{$X zGbrr1z9g9Uc!Sn^uRk8HSf38|U8ifAZ^gfN*NOC3<9uaN@(DXFRu_8{o(NYwnGPO! zLXEX{vkP`Sp)#43-%Yo$;;E7#yIw1Ze3}a0UawWsRd$~zmF^t7sh;p;+*#~=i=Nbu z*O)K6JMAX_sh#^^_f*jHDXniKn@jam%DFXK1*@JCDYnLB3z+#oE1uJKT2(#xr6B;&_#`@G1EaQkO{a-Y{o zN!yIqp4WVI9Pjo*Jgn*mru-vbU=>36uYEz&sEP9daJypD`#-**1nRTSFKSFR3Qu`4 z-mVaKSH7roMg@>RUeuDc!+O88GhJ0GnD&wqpM;uV#Y>utcF|TB(}c|8)-Q|nRN{N2 zf>T~rtyGt(6)$TS`LrhZ^JS$Y*D8+bSK^sK8!mfAyU(B`2G74D(pSyO-BNzzjVNHN z(#78Bjp2%oY45g;Vco`b@X5wF7BXJ`t2*Ze=_DwBRV{U5`+Lc&bZzkZtMTl%R`pFf zBzChE7_dpD6>FRKuHF>ZZAyDTZVEfSmJZUd>C8xmt9z${3tm%=FB#SsrGg({ixvF( zb5s5iud~!`-%#x5zhMW^hGKu&8<QJY1#w~@cLA5$Eh_qK|z2K&Ch_HCxZl)~a* z``aQ%+j))zlYS>&NLW@beMd_*R(gH?od$E>Z~AWBwu$c|V^RAD-*xQ>{(M(!sDfkB zdvRM>4`;j=*1eMs9(zw0P+yr3xgK31UZnc|@D z2Rm~uPX%Xw5X)DERVnZB55l_l(!uXLqcC#%f2iGI!qrC?<$kE97~Q&Ceza2_O{M$^ zAJK+_^5S6aN3m*32JJo;IVHATcym7v>pn>P8$L!2nlh<4*yod&E;PXrpJ;^wW|zN& zXJNskeR3Ua75iYX&GF)lO>g+-aK%UI;Ev5Zcc>nz;OouWOuL<%gTjiBOM+>i#

9 zZ{??P#}v*g%KcPF?jCmJcG#j;-(79QGq&h-+|Ay7xJAoW8)=)*R4!3vVbEtm{e>p*tTih9 zB3!X0?Y;L!Shpn|wEa?-8GAzK5BpN2OH|*>>mHugBiBFgON?~eZz}d5;DObuKl?JC zm{=u_2rE7>@h;yQuJ}A1JhD~k_pBM=&#hVq&q_PR+v0@}Q|Ih$Rx2nh4xZnp1I_9J z{x91^iguPC6xL}X6Bgc>3Z~V?x)M{PzAj$xG4S^JD((+@=iIO2qOlv?|CQAx3yXta zzKSbRKPMF&{}^IZ~vy70P+g={uDHT7~rkQr@iZ!piOG;Gyp{K~uZLe*c{gMorMW zUbS|;JHI~8tbRZ$c)niOe|?txUY97Fb83S+?`G5U-Scqee}xuA2h$Do$$Foii{Geon?iQ@*j1{XksO)4L`>DAy=6l+UGjR zkKLi;QWf@aN4(!u4NnE1?9iPlVWrXhpBg?p`KP!$A@Qc4;w_1zflq!4D}OjNd!X1~ zSc)(9r<%O_a?ehWddBf1~Q)dwy4s_266Jl2oi=W7E*m<4r zBp2cDLNXfao;M81Xt*n2gnQmFB;CmlC#0NHU4u?@_-Xuxp1=P9{^OpO^P&X>@iQa- z{w}1T@$UJ57m^<@knl%Hx(Zjm@Z)Cw2#Lr$G5irybAE(PMP@kse-csvze1v`X1jER z;B(x4mJ5J09q@M{6*}8hpw`7B%oULZuEH0&ie4rYgkOoBVo{)xItM}RX;y;B{ z=sFj1iL2m^E(1cI-{cBf=HlJqo;M7M$8Xp8BjkRCtMHu`Z6taTUiwoD+ZU@GVfSgO z{}eJu9(3(_)IBGpJ?q?k!;s>ha5y3N>)kyeygudbpArcV{#O%Qho@bh&$_Z+ba7vH z_nTarzY8hjH5czK7mtuSzU%UN-#!1>-EVev`_#TuBEp5uXp4*Rxyy)<0={tJmoD7u zo)fmBUyK%*OJoL$vA<)K*2eyxO}ZHS`#(9%JtyQcZQOiFy9b1@bhx{37&hXyF^3b< z!T~O$jJrSHJtw61cte0cLh>DIbaakz_an0|;%P1;-fHBJkYQZzGM?!0|4B$em97BZ zp5%`Ze5wnlyD&S$UCeah=`Q5GO%lv;;aM&`$Az^nJlBQix$t}!E^y&Ru3;Cu23_tN zbfs1-8(;iw2m@EU77$XQ#V*5ZTzIVuuXFJT0};8^-4k-T%-s{xH7o3WRxh|-=_20g z!c{H-AqC&EAZG7JumC{au))Wgoi;gj{~&?g{CF&m8`_!~ZVK6Or%Tb3!hEa1Gnx?tgOO zzg)b38Ohq+;tvN9QlY=xJ)ynyG&DS94QLQjLEm%p-{FLefhO+0VMu;W9p2Q-YQpBO z0A5PxkC2Eh-2LB$Jm1?rC!`|#xqCvk!Tt3ea_QqLSmMGXT>eKM^>kMQ@6y ztLZ$K+l4OvMJ~M9r6c6>Qb)!@hZEA4t6Ug4Jo4lSoFT}ap4fu!^X%(Ave88#q=K)y zaFYvPbI)xU=Ka~ij^a1n)3;nP@48qGL!kE^-Y}%^K6W@Emz&*v!;sp1>dM*To^SCa zYae{(fQBLM{?g%uWU$rU6H@TkF8t0tuXoQ0>DAxd{ojQ=`h!tS#(%nqgcNKN+L8n} z?8ym@mvr|HLn@N%a6&G3arcDezpK0dU&5@5kmn-&T}a(_Pw04T?kd*8rQ6%3YZ&e$ z>idC;^-S2`(91C{9w93r-Zl8ka?uQd+W~|u025rq$*!O&uHb2|g)F_5L(#>Y@w0rQ3Yxi?50wDyyyx7c8jBXtBdz{A@R4l=Y-tXxqCt`wRp7t3wiXTi?_ptKe>#4areJE zB7b)S&`at7YLrxhgQW71=k9msCqXdZB&j2|g?q41Qfs)M3){Ga2e{|$9Nrivhjs$$sl)6xT}ba@^w;-a~V!?@us!`H4n!^I;c z-Yl1Xj=T5IBtq0P*R-)3`8n=+t?TmhU3iJhhmiFPw^=mgYL`#s%Ddi$OC5f*yT9F~ zC!`*CxO+k_S1}ZjnD28D?swrDSI~p*{vnr;kc=L7;Uli%kNLWxtatGU>7l3GJs}m} z;O+^zf8LKy4$QumgqK_+Uw4gv*TwzsLdtmG)#U?M=0`5?%`U&KE}w=WQ|)Vqe;v<< z27qnkb{Bz=Mt|eNZ(aDEtLRVe{$DQs|0HCX!aVt!9A}m>bcF@~FDUF^hF?rGa!%fBviZ%hvI^5(%J^PxB z84}IyYf7UIeNA5UIl$1U@CZ{Hy#ui52$M0T(Pn@JN0_|m=p#+WjEEKg-09Z8KWK0xo23Rm0K1P^~xh7gT z0zO8-2f%ev-;?13VA;tgV{VA*0T!POA0ti1+!!q#2_GZj;}nxIOQS)jzz4t@fLkIn z3O-JOk5MLLmPM-phL3`eQ%%O)9+jO6AE&|x!1AccX!roA8ErCVWwah(;%LUlX(nS< zMOCN4$7%2ZaCekn1|I!3V%2QQva-09aOTGUl!U#v-~(U{z*CW#2p<#RW1`8JXQI^r!zaQ=g~^y_qp}M4sDKZE=c6W-@BvU$X)@-; zXg$EhO8A&$GUnx|Y7%@*f)9X=QT}B30GL17WXz^$6Tqy=@G-??%IS|HGIr88S`aSHWNN(!Uw>% zs7Vcc0Myi&jQJ{B4=}L?K2A3ovpuRh9X?Kn4}fo@{4?MKVE!2w)V9|LdV_HO;0T!GG7xPWV>=P}V z4;S;{0-$Bo_k6ejSa!b2m{w6ez~b{68yA?2X%j8IfU$7_V`G8InEYta0{8$}18_iO zE`*N-@NuEZn0C=>fZ-Ry$3-S%+DBy zlQDgxK_PqqtN}POGFQPz2p?COj5#`54KVyF_*i5z=Gdrg5qvCy4}ktrldItapyq0m zF_~yRz{IQJBQhCtTvQdoM+6@LCq(&+;R9g)VwF7rW-W$~YgG2Q20pHV4}hUj;kEDq zu;^NqJpdM53m@01>~S4@Tn8TjBcs08!w0~!>s9suSbRNv+@P|@4e)UTd@NDfV+ni! ztN|DsnH%9_34GkBvIoHM8{y+7l|62PkDK5FpdxCr6g~iIma6OlFmWk-+^n+4&G2zE zd;m;~@^66;fcdwm>;W+A7WlYTWsh6o<5u_psEG=f!3V&iWh#3BELa8~x2f!L8+_ab z9{^`YeQ$>kfMvI<>;bU&cKEnMWsf`H;|};(uCm8+_yAY~FfTGI;A1&_tWenlVE797 zSgEqdO88g_9{?9dP40vbfSNm1_5hf8Cw#0j8Dx)D@UaR$04|U6?}872`FE-80Wj+> z__$kTkGtXHZukIL6cyeB9{`K)QP~4v!9DP?T4j&b@Ua>`0IrMr-U}Z9%kEX#17PvJ z@Nu8Y9{0h=eeiL=${zQ_2f!MDTOzXtKJGWWMdR0)0mvQz!`Hya11ftw03Q#)2f*^E z$y)dTs9CGB2f)O&@bRF^9uLCDgYW@xca;ASd;rXUNM#RzSr5U-!zz0`3?C1}2f&)B z@Dca`SoDa>9smm-fsaR3_IMON9)%BpN20!u!3V&y$5i$JSo|1#Jg&0GyHnDtiD7e*!+%tL(8JKGwqr!1Ga)C*cF2=1G-3046>OA5W?5 z@f3VK1s?z#qx`4g17QBsDtiFTdKx~SQQ6}e_;?0B0N#uWH^2wLq75p004&%5AJ3}n z@hp5i3m*XQM}41z4}fLQsq6u;_&NA^US*Hx;p2JuctK^47vKY64ZxPjya*pJz{iU! zdjJf75k6j0+2bYncnLlLwna@|h7W+6msR!vnD{b$yrQzlEAa6Od;okK z_5fJ?I()ogGTvV%y7>(=Eb8|LguH1o2q14l2*4VEL}cEAkT)UZEfqijhQ9?NZ>s?E zHiWzlApk+tP5P*sAK*+l)fV>MK??MQ`?os}G5CSm&JrzIzX1xa?@2ddv zK7_mvApp&z!Ve$>V9^IEfB-D`075=g0pvpn`4B-51|I;2MERe?2f+N#RR95)^*MZep#sPk@bLwF0Q8Iszl0BfMPI4_ z0nAkL@aeY=@8S@Bz?2YVr+y0MvY=0tmpwZ{Xuw6+pg)k8j}v;Djju zJNN*Y|D6gT0JFY>k9rkA>fxgvJ^+SBh2O&mz@qO}00CI=J$(G20>}^W@dJDSjEwsJ z2p<5;epCSjVDXReu|oxr9q_ROK7LXG~n>o@rLT?LTe z;p2Ds0H}!y|9}sGMSrLO0s#ebQ+MMJ%e z$u3&zl`iV%d?r`i#`Wf6cy%rrO`V8i*h|{_yAat>*Ym92c9*2 z1YT)W6nJ^j4uI>TzKy-oXe+?7#-25N04#3oRH3bu3l;M zGr$^vTOyMOAG^Xwo@Wgo0K@a(V>izlK6ZnT-QWXYdDLWg_yDNc-Lr-dfQh@q#~z+F zeCz=qd%y?4-BErM_yCyS#IuGEfLTr8qp4>NA5Gz-DSQB|i3*#+2f(6co;7>`ENBKF zdwSOJu_t`&2_FEDM17mX2f(uCo;7>`EN%`TdwJIIu@`*o1s^RuYxrmZ9{_6ro{G%g z@X-Q3_V%pd17P^x@Uf3)4Ilf!$3E}@@O;!{U-$s1+1Im%4}gjL!beNb8a`UWM@#qs z*cj#S2Oj|Q_w%gb17OyE@X^Y%hL2Y8(F#5Q-i!)c!w0~k)}A$d04!(?A8kBq_-F$k zZQujo{ittS_yAbe*0Y8WfW>X$Bj2-zk9_#ZhmZX|Yxvk7J^CVP@UcI99N<~Q z2f*+H;Nw8g8a@t$j|1TYU|ZCr9ee=PwDYXt17Ko1_&CV3hL3~b;~@9|_%_OK4<7*Y z+k4jV0WhmQe01=v;iCh5bbt?l9Z_LN_yAbc(X)mRfCU}lqmyS1AD!T%6MO*t7WF+C zJ^+>->{-JHz~Y19qqCRs{_>)mJA1>Tex1GDqMQP60K!KBgmi|G0?!H`T_B_YLb`a? z`T;P!3xpivS?k9k-aP+UPh_{X`Te|IyzsOh$sqnV`QpfQ^TwJsYfZ)*X?WB;{*>a_ z$rWAr3_8EvIDKuC!@PrS(x_`MFW28?Kas|GNIIA_|AtMlMNBQ5I;~=y$g$D(UfzBF zytX2FQT|}BW%NYK%iYV~<8KU?3l%f%Z@H)BM<1oUdrf-n!XocX(|k@x{Xi3Oe(b0p zUdmmYf2jAD@n7gE(wrI`YilsQkC*Fz*h?e;%l_A`SerEIwa@qUK2GGFFjBv9HDaX5 z9*{Fq$%()5+IVfNe%=;O*_oCwZPr#D@4aZeIUU!gPxR&(|JVg0yHMT`TL;=@Iz_Dp zdu_a%FN@NHy@MLZY<4*$`lP$}6Y=uKl#Q7^dD>JNdzHu@QGP$~2jKkpn~GlP?~Rs>@GkOp+`qQd z$=)lTzsEWv#+8VE9Oc#eBc68cY7t#=s@K+E@{CBHmcI78zTW)_{_mi}fkyj7z7W}U zClPC3n&9=grtuwr(QhKZ?;cGyiJ~?lfBvO^piA5<+x+sy-bd?AqS;|LHh}kZk8W}= zWpg;hOYi*M;^J}k$v3$8yESU!C7KY23IKj-4T^} ziDud5?$ItdB5;pZIET9^ zjE;Rv94aCoxR&5&f+P%HJSp*qD+$I{9}zjk_4_tg1_Qbv$MIk1z%*V@a`dIIuiOYO z!R14a4yJEhJY2Bh=81m&)?s~_?_$5WyVFMnuGuE~_T#tqSF$wjNM00m75qMSia}rE z-iiMD!DVx@&Tj&8#!V~0Z4qXvnGFzxt~G}Fu#!mDteOWVX@ zyaxA!OWV{rT^PnL*_o*jK*m|LPvKuS1|A7;R=kg z)7@cwV4FAM89O~(et64ce9-?X7jLYH+-18xD^lb@zWI4i(t4qm-Yzk&^+?Q^=;Pwy zP7faz{2k`72{=GXSbvC;b{OySee2?tI1JgZji=5A4+o?FD@7#Vfqh*>UhwPS=|DNc zVSF>bw+b3^q{H~`eac}+IgD=rAL_899fqqu{0}k4aEwAPPG|f*==$?m*J8dv`;gIr zH^4>YrNPHsJe;U-$%})HJbedqpu=V&LnQS5%i|nY!{+8#INo(99|S&7v8>W13VK@uy;INU-QwMJ;c5(%mxq|T-xXoC1>oUe+XNkyCBX+=#wOCf2 z2%hBW+oI!K#B-d#I7WQA!{{mmF8;{bON{>+F6WELSq`gmh44YwnV#6GhJP9gzJS6VR_!o6Ay&DxGaYsz zW5IQ2w#I?DO5D?dIJhCr#Vjxmo8{tNg1l#}i(r}UuuEC695%;cxLn-FVP`t*a`qR8 zouyb-uD~OYr<($Eobndp{nBCQIP6L`6Hnb`#;%0$Ka@P5hSM7&#EJUm~P;K2v*RtMq+^D<9&#bpj#$s6?vomRIw3^$p~ zV>_)JhL6nop4e%1hr{k-Ryb_AtIXZ_hw;SDw3P|xSmqw*d5r0Ir|b9Ct`Mfx-7dp> znH8Qo7`?|~IOW6zBr|BWOMAbF?MD9Yck$M^y@bKI#$gYzsEjf?$R2R?uXR2o8BhFD?!(t~;cnH13h-4W&u}7%KM6B|` zAKszp&&QpH!{1X5!<{G^4u7%JC|rp?=`b8fkrsENk2-9F!=5A`hdoRG6A^!->l}!; zDKPwvQbPvR^A3B)bqW3Yg2OhTD|%wP@I}|h_$W;{>}7|cl#DYX51^R1w9m7sBSb*& zs}6iY#7e*XZE_gSPN@uqz2-2a-OC*Iy2EgW%D|)oZ#e7~w@9G@z3H%x_^mqhQz+*7AiT-SoxHpThhGTO?`sIimnIm#kj##%3_@HQ>Bi4-v>N)B- zu01~VMc(tpx&uKSf7us##TVJ=t17<9ciK1JIM9%BNrDRyUnCblF1|=(Ut|}2y!aBe zZ5J^=i?s4ZTKghxe35(~-z4gA(A)jq$=&#Cq-^Zs=KMVNs)7tBJ+|W=OsnvCq>Rr z>iT#s!xK&Y`h>_0OrV6wd-N<$U=SglGR|VlI*J300XZUlef7C&Ia@0`>x;MJ^A8uBR#d^0PPH#mpBvZvz7=4oH# z8DFf|n{~d(6TZ6S!FB3gMr4F9QtFEg_eBO%DPJUes;?FSq&cKFgewFm7HZ@o7H7mx zL`wEGBt~RsWMyPyWMPDOG)GiVR8lMgSOC6Digj1@ZBm`>VF0)_DY7gna$C|W`XjAo zj~lQXc_3oc z(Or8b#$;Kz**n=Q@w_?46B+BNVvi!v?!uDA9@;iX75Rg6M7rjv0^d7FtfLvc(lp5t zX@;VoBi8#(PL8^5#nldee75-_b-p@Qn$1GMPRpu;dc$JP;><#hHi_$8-00%<6i=r; zef435zbp1>_GKIrJxf>nBG>pL*ZLyY`RWB1-`wnO$mVDt=;Y{NXrL&K7y;^W?Dl<9 ziM0)0kbk7mF9|vqW0Ll?L%NOemi|rDR#<&o}?HE=Cd`dm)i*;(X#22~I zSLefs#`s)A-9>A~7a#uns*=uw;x`FZQ`r-7^4h}_>E($Od8%!qU7}5P_e8pQB8PY) zg`NnC<2oa9KV4(QI-AACZjL8bmq+Q_FQJMadL4QkdRx5_`Ob)ZVMM+{{{Gg8brw7V z9n4c5>t;`NDApg;A=ICadOv`<-zQOS6 zjCBER1DA%c8j(##ode)M zSe{r9#d~;SwNlhobZ>NMRAH0=?C-cu#sf0OoCO}<%GINJ8za&-DsP$So<*OyEFp3^ z%Z_jTm&v`p$VgN%BXX)ypX1MwjVCfGAySbLtAAou#~zK<978u^ABJp1Jw!W9!PuO! zF~1Bqo*Kz9s6XLZQ7aud7vIA8!Nzckp_DiAF%|ygi8b+I(d&zwFl_%wTG#`zkbayLxh|>Z zdK4Ud&|_&wLqh7t*uBFS`N9|Z(ifTOt5KU5@Q(4-1s|@)@zsaP5AzQpwkS--5j#p! ztpCZ;EHS4gMaq*R>Jdt*uNPA{D1IdW2p)y8fEvkS~n-eys_6e~zNX$OiCo%PWX;k{gnll7r zmxtH?teR*rzg+97)dVZa3{PY_6Af4JMhzHEjL5!54Hvr_HCAAo811P&0{g=RPb-4m z;on=2jp5(1ZK(M8xW!E^EkHjY#AEy6$X6~y><&f9TJHH47ZMkH!RUmV2rvnt?w^tn z8JtixA8Q+@Y0}ADXZ#xAm@1rg+&Wv^i=_D_hlTcX^eon6|XQ5^j}hK9uv4<}`DOGz9@riC#J zgzwwR zxzHC-?J7e>(J|u+jc2cPx?>!N{9yHaPvl>o9)e)FdZLZ0j^5Wr=#B?vhcO)zx{-1? zfd_C7GdSd6sl>%NYdz{YOEznLsay9s_+)Lr!4tXO6B+8xmil@+i1As^G-mr^>*Biy z$m$qUkA91Cw+s=IhlwDK0m2wI0hMaj8)V!)HK#< zevtAFYphe%VE8j9CSh*aM*p)2V}C$wW${G^r2ojAu|Q&UL*y)UJ?>eL&6e{@= z5^OOO;2&@dVu6sHkMqutdai5{E|AmY>9MxPuIZO4bZ*!iCM9dd8^} zY8#obwD9RAx-cFGbW>a@3T6$1!G_Q2!fYHVZh2R?RuveLj&7}L@7Ahz*r%LsNP+ts z?HbhZeAxQyufLe%WK5Uw?*OBNm9^_2m!^Z!G4cle&q~<|Nzsk!&PET8SXr3?agBSq zKBK}^HeN{42RT%taN=@s)T06I%z)lbAWU&IZ$ye+_azZ&V+N=Qt!6cjsiLn~_K$Rh z_j4Ij0hr>$RIJjB{&+P_-GVAo4U~GC$=s2S3@U^vEyQ(8UphQH2qO!H_8V#8-t8 z`)RMi^gj>IG%7#MHu~+Z8Y42zh)i(i)Vbki$PRvMEImvm5axBzWAgU z&xuDGLgEmThEIH9mp>b6lm55!Q0cIA%|%;qt>^E*>WX+zK(vhqEQ6Ir33;6HfNss6 zz)vU|F4tE%IVH;|CZj8yS%vAs>PN+phvP9pMHo~JCPq7hCv*dl0m&fbBV!E*A#s@< zK~1SHJ0U@^{D&Nxbs<642l#fADYTxGOSH6WqFMGm#+MO! z&4@f@6B4?82n14eC|7#}!!;sokZBeq_TJ6k7pWFqjPJ#3&@{V?Q#KiMGeQw^?B zVzIE@%kn`+Xtw+|08=2=4KhRHB@?t=3ZN306|ogy>B2gO9=Xx|!!AyIzV61C+&GJQ z!-%}upkVSP4*!V%ILk*2P!X=FDJDq1=EfJ^Vmy8pS2)g$`-ZKrz^TEche zT>sdNcpfK?TOG%xpco_0FGl2N7e6j2u1uT{{m&=o8u6n?3gY{8abzk$0>%V#sQt}| zr-dLUA|hgB$I|*x6HI z`(RI`lPA)VpO^5xa=NV*%L>}W^-5CcJVWDIN=9hPziEtjm`AAQ9RuUJ4(GpW0 zU-2t;`H?RYKlzbA_Y;}_vE?IP1Y?WkU}v>F(Vbtg`tm}{F`n43{qZG0z5>Xn0kIbF z4-?G4hsS$jk0AIWV8+ug0&*O|mjMT25kophDn~+RmBliOo$q{S^TAex&5zIdVJ+nq z4Gv+>aR)JHdLpyj;mqltNR9jaA3ys>xa5Tk1ZEQKBvWOq&dK!m& zevgC&jd=V_5NqitM1yXqy~dq>EN(zM-9RCnZg4WfGfsBMh;HE-(~nkSw!Oq<9LFaP zjp3eazO;xLj4vwkmBlOQ|I0m4u(0;@44Ey9}rR1yXY=-LN7Qz!>@W3ex}l zcpeFkilR7C5e6yeN%ueWM6+&y3g;!B`gMjTmHG zlp=nS$vZ6ZGfoaM_45u(LyUa*RVHz>M2ZvffP*v&rXUI?eR+xpDM{ahp|+w>NmL14cQD>>*sODt{G9n%!7@wx(9fQw3J%Zy%j$=5E=y*qgZ(?H1 zCJyhdP%&1?I{KfpI(`Agfhy)A-Ywu=0)Cp$D*#xsImg4Q&4A)u58VE0%DpDQHLn4H zgCTe`=y(l)u0qfzPHajd4h&~`Cx8O6CB-Hr9>#_dGKdW$yxPS#GBK(1ElgHE4%}Io zSQA(ka~zHdAM0~lem~AZ#|IiV?lZDmuTeEc>9*Z^wXLp*S`;VveWk2zUz}K;@cy69 zzB?|8t7~{>jwSXI#2IE3P-BI4ca~kSM8SsAlpuBy5m7)??3Jvs#4grjLB$ed7b_|X z7!|u>F9^0g>SJ#y8q4>)_s+12XrAx&pK*6?Ip^Hd&$&xU0R#L%6dVQ2^afFI5zt0H zy9v1159*d6CZ{~d*`}j&Q;;k~X;kDm+n>wQTTXBBFiPOVOa_x`Fj2t~InK81Wm2=s zO~xeI-yFoareNlCaYa**_*a5}}cKea@@iWKL;YNeXaTW0?89)kcFy}iXBPZszvqrrm=@W*&>koXQb%^M_z zv?G%%kUu#Fr8l^dd0*heJPfMANE`)1JF zRVID5l{n5;_T1YSJ6_LE5%@5R!E7)Yxn=0%0}}r$cJ%@2-(YgURX*UXlqOAW9A}%^ zJubRP<2y~rex3#ogN5vu=K~^N8MmGT$08<2T6ARhTQaQn4tYU&n~%Bs=9U7VU^G|^s*wxDaV#>POjnfVINN}QXUZi^4#4vKgNls1 zdiEMHh*WQ`{xvIdZ}RWY%kyF6wiSt?v1c2Q_`7((7bLzY&LEH4_`DTJd=uQ#1|%h< z5_MZ_T;|v%PLgMNfj1dUCWDpZ9#leaUl1(=9T^IaMk)SH%h;(JiHsb#r82#FY1`eY z!Nqy|xPR<(*;cZPNX>|?LJ`nb#%*3aI!)fG|_W7=N${yQ|0&GwurjQJC$Fm&n)OV2MBo{I-fKW69d<%k*fwKD40& z|1Cxbg1JT=SEhgOxiT(V-0tj)E~giiq%28^{LB?I13?wuy5g%q2;iHcPY_5#NHy#h z1ftS`G%IkNt(@k0$w5R4pG9P70ONtyp0gZp7=7pc@w->G&|x(FEF z76SP3xT!6e<+U{#&Ch?=rq4~h6R#;S6UWWRf7*h?^phjjYX?>I|MwvOcgC^pK;o+* zwgaBL z+l=|PXzTWzvBqi$dtW0*2Cl<;J&ca4O2RRD5SgLvgsfS zN8KMGbD<7a{-fpsgc_f`*k$Vlm==2)IkJ<2WHfkiT!;GDt`mrsq4lX- zYMbX*E4_8jqH=bzc~_q!cc5)^YyH?W^hwa)bgmbfTMd_X0?GQlkqwv^H}rJnMe(EP z0HSWq5k`(&i?)@l+qqkXQ|^1%8{$?>G2)|6ASzpoY}+U8I?lXbpGW zTAmr{4OiQyDNAxPL*lR7N4T13%xW_PdmcO7Jm?sG<7F^&TrkFU2FW_y#J11w{VI9R zgO_jZ1C|j#hU0AV`GpaU*Un?x(y7NxxUVya@>3HHerBhaM+V2vz3JQN`y_O!YLFIC?sgGE1e|9h=9P84F*G*_KDw zr2|q_oZbaQ<&4U_NZ;H2TGl;~%7{is_vDh)gsGk0e*&@a@4K?yG(GQ7 z{9{=2A<-eyHWD;dU$~hv5hbhbtIzy5m(2VXvS^ztyFBTgaa-8cXNI6;rzxM|PlS#V zP&?fsTtSj5iC)Zu%u>F6zx2Q5MEe>my)ZirL`DDU58dHn?!AY>$YpwAksc6Gh`Tqc zf$r|b_V|js*MPZuj<~Z2R4Lr&qqHjsg5Br)nqy1-)>1eiJfFRZsxI*HM#Lv?zy7gl z&#w*s-`^^w8q=D(ZS1}fTcZO%=h~^xPO1<<2ZTc<%#45{+F_C*UxD`Dx6nAz0TX*c z)qJgAKzrlcUf?90ZH@`UK@@VD#0Uh425^qzW$yPMRUhY0fN2 zQ2pOlO2-d6P!6I2k}p;m1`^(l1Sh^K^1VS4;##578v^nz^Cw0|dEbgHmAg8$^e>Th zND!i&6qDvI0x}k4`aZw;yd|NX= zdThnU>aNs&=k1O~SZgL~3)j?r{MX&6hxV1KVOkVO%I5>f1t{|GL>qh-1*#I^#~iiL z@JAosB=-E1E@39B;i?}t=>wwT?9V7zq?6kIRI&AkHMhmYaq<2*s1HQxHd!YQAC6`E zf>gMPrUd?kdb%mr9182k0NM~^EAw4O;}&P@_qL<^nxqLh&i3&B;>98L4+c>4NkpF8 zAAos%L6ptkFb4XSV^5RXQK%2RhvLWdae5Vi%OUks>(`x9b7lyg<4V#+mdl~eBf z$6i!~OdbYL&PaC_vfJWGGGTX>1+mnnqwugO0Rv(oh{P62u^`H>!HlVj-kWdjzi@MD zJ3wxDCKme0ZXs+y{c_(#IM3exfQHW|;xl++a6gLW@_r!6Z9`aS-(+kV$2)^k+fss> zh)>gu3Rko?pmPs z7F!JjCtk*$13}_z;ArwNV3oZf@sx^=Ma&$>k{)t{&dik_)={l8V@%L-sxgd25?bQB zfglM#bVe~AqJ#}y&=wCO&*7r@{GE8_Zwr5RLuC+%LRf3G4uXJu>gPMZB+sG)iw_1t zUww#{H~Dm=M!v6{f*ugC{gsQ|8H>*K_uRbA>w70V7Pq@&lOg%OvGTM#6a2C-mQU~M z-sfk55A$O3t9#4uy0a)XC*6P5?=$<7>=Q{(naPilWefo+KRD2E=Br|lVL*i-Zzyww zhJwVKu~IZlt{n9Z`Q%uhApCo!0HLj?0c*ABRpb~e6@3tf%s&%F^$MP9hL za^v(ywVTp6D%t0Q8zzl_03|Dssr8?W&Z(W#C?`kY!>r6*W;^B!g>Ik%FnAbT-t1X5y>IonPTA79!e&PIo$+9(F(?%88* zG)o?ysBxmPU<{`*u&^}>XO5;ylSZs8RMkT_-F^t^QCNNqh|1|G#+>Eu%&%F^*pXO% z6AQQVqR@?ms!yZPHVq`cKm@A`XaR5wpsi^WYs)@NyRN!;wD!ww=ju}_p-LgS4+G%4 zoIvy6W=goal@(`_uJ8$7>jSmgD7Auo}2)pur3yj z7_6_$RNGm}JNcQwf2V!JmQ;mx z{TECGRq-6apbKyKPq8IShb$C$@|$Ml9~*$SiQvSS!6g$R(0X_vJBMY~UTwD?>NA82 zgN3Nx(*xOlZ!h}^E!&@F1sz(b36009lRy-D#iQ3GP=$%H*pGaEItV9Cf&lWFpo)(s zfrnPfs#Mf$;Zla+=CdHm8ABM?mGxfmKpq{^i4ut_&?-2aR0nF}*;6DCC+ud%S=r!- zdwzy0^5miHSc@mkK3RSI-Ibc^Fmv3u`21&(__|nXGDy6(Ok-?186;g-^8Pq@GMI(Q zLvih7P=)rx@cLvB<>28A&uv?Z&Ani1HBDR8#Bq(W9Dzn#jc!4UKJwXi$nX)*IyNPVEm?X+}Cws0m&<9T!gT(czPPx_*&R@I!L|qH4lcQL zcG8|r>&YriOK7Gr|9lFlWi4?@608M{yG|f|6TC7LJTPTAIAO$W2>SnpT>f%|tl5?S z87H0BzJd#dQ#TG;9$0f(m1rANTGf`ncr&PBlYMrQkFsI%To454@wXqh_)XQRQBJ7;4V;L>wiwy!YZim#9#ZhK>+c_1nGZ7h_pGTX0zujIMI?E~~Y zwf0mB4^vWq=%T3?LJAO3 zD>=wtE{|wZqaBVzVJtEB$@2R#K45_5G1AS zBqnX4=O5J=HK2TF8kBc(f6CwHZqKB*sqW-G^`8U6vYYKhQIi z@bp3u<-R|&5gVHX8{aJbFvC8AM}84i4e}z8aTkyIM>3Pl}}GfqIJ#@%ajn zu+hr_$e|wD~=`{3rf#F~R=k$EnzV zF^HD+Q&~BpKGy1*(fLFK8$s%eXQr}JLYr3U#gpPY+lZH~EraHhI8Iw)%`|%D(I)R)IIR2r62w>2-kh9q+1gvvMYLFoetTHuO>JX$}T0`7+MGzjkW5OP7ib6!<4I8Ml4j6ER{I zvxuP;nSp!QsAtb(Y7l7%F&WGT4^QsJEDT=?B2W3!p}2|Y#4d?AZz-tkI|*+t1rOy~ zB6Gid95zHdZRs#tgU8Hq`)8wZ8Hn=z*|eG8*37-*_qLoav@6d{R!U zzm^ZQkNEPN2Z)VV*l~G_IFNj7jZ;>DS@>Zgrmx5+tA>n-S?j?`!$Nxq+BzDE3cO@= z(}!y0Q|2VGWNGw}Vf(XQzWR@y`Wut5$y!q3O2V*J@cE)dA8E@t6eC#@M&$r~hp(Q~ z?WLHq+K#a{_WCk>|IxP{mSfp<_CXrR`r#H1n6{L1avJ^V4cqqVldA5I&s<=~OT$M8 zlj(~5)%35USKzWWb`Cm`gh_<47>@a)G`MvVeTtR7RQxJDxuFmO7Q)R!0itV5=qr7C zgar`MsFlB+MofKJKKVMX!Nhf+$jHZlXuxVRtmCUQ8$ENK7VJDeKKuM}WJL{V<@{P^ zC0;(dwN`rEf2RE;{$7j54f({>k<^L#DgS%~7zLLJx;1eFa}~NcZ4K(O)`PUhSPd%S z#^oEB3M<#VpCN9~!esllnsSggZeX(PeB1rVk1wkS+2=B2`xQi0QtvIY38M5AKF!8E z;*w3^$%kXk7La&q*(-xlH{C9qN%CSNHr@;Yg>=WU21W}Pj;l6<*)n(&QzO47Mo#o! zYSs0ok>kd0VtV{)ud*A*pN`m}X;c%Z>r0)0g(^W&n6w#ZZh-&|OnL5Rwvo@})Av41 zP9se^Bz`n;Tp*T90ZB>R!uC7r>szOTV^h|DNBy9Z0=7tjx+SZputVsdf+JEu6-K1s z#uN}0Okpht>64C)nxE?5QcHMPI4(5>Z;}xwQ<$&3G~w{thpWn-w5LRFr=Y`DPz!C2 z>bwL6k&~^wCy6?ng+d&kL@5J5PLFG;Oa63qR8BE&&5?0+o%Lfg1fSIp_ zA9jNzTT>ZX{pU}*eu}?H3T7%xJ9y)Y9r;tW;liX;e6|Bbi=4{L_r7Bndf)0}BuQPG zY%+6s+MA90i|y>}^jo-=p)zq=!9%W<%1+_LYGzpJj%MN?7%_e)1oDkBV<$+~ z&wD}>EUcE@Za1T0+PS+CYX|T6nWhqGm3#g+EV~Ot;Y~5Dy$e(gY8U@(PLOrgT-|Y4gILH7M_OTsUTVhq%j}a zv36a4&Q0YpA>}1CBKd1V?S+oW5^$2N}m%b$** znR`|Z;%nhQsX!i;c7s`&x}WKWTKwu(qjolVL79cN595A}+)WY0-Oz{OncrBZr4o=wGpte;cx+k&0Q*4ECnly9n1Gclke~=Sqpr zDp1xh$5}BM42?PC0@wYuzY<9%Sy&}wcn0>{3!*YPgLz+#M-6z8Tw@_=p&(3d;kd;a zm`p}&%3v)ulBoi3avnx}R+4%(a@=njsTcNwz$-%!u%qi771E)L@6ZdHkeWCy?Et<= z164SF0Bi2Em%MPzKJXCk9>VMUK$PDfVm)9{SDS7Nb@%#@zEN?<@Q58-V>i6 z2dQN1!|arA9>%c!WZq#m@7Cm;AzOove?zyWy``0L_I{A~dboi+e6T_uN%G)J!HGBEopdnE%Y50inJc!1z8iZcRHG5;s6tZ) zNW$hU49I{et$VSknd1&*;pq%eg|k^GWP-}Op(PW{*6Uf!Ty}73eb)K;b`pPR`j|h$ zn7se%27d>}{2*&gu5#S&D!42YL|$u8D}!eTs9jaWY~qOHJ_ zj6BIMM`D?SU{*p-F}xPe^FDN8cPGN!q%mLRxXoGE?I6@G@%t&}+~#+MlXh^W&?BZ@ zv5U@N-a!yGmcr79pej!fdx`MO6^|aGC3{M0Jq0M-IE%)^AAP?$3lk1QUE%6ETz?o` zgoWqvz`*`Tp<=%9Gcgf$SaIJ` za8lfznPuyo@uX-{-s9SK+UXtD$(OM7F%XrMON@~-+?HHx6e{{@>}uw?vsvhU3{<^6 z1*abaCn5eat~~|;aq|kA@G`(IS*E)A#9)r$2ZMsbeo0 zrMuzpCqU)HvGPecBP%(KE6cweU%qn90TzlE?V_lS{3+THER%;n`ecJ+QI+F<&B1o3 zNP;v6V@^Q;&nhNGRF0!{6up3^QWV0!>?9J}r%Cb20V|I13wcar&9B zZp`XHF0^p>Z{U$~jFVjI22(F-BOU}_Y4wg(;HhGx^pY5HBzb=hlai)kFNVEozFfC9 zs#b53fwA+PJ1Q*1}^e|l)gVAQTNIpWo9sA4IkMy7eJUw-3{_0EE%OufYzLaqkO zgOfc-ly5SaNQE^w2E)&SC@j8(qt1dVXk|0q)hTLHiJMQaiD}ov(BT|Z%)cAP6ag_} ze&;~tnY$$y(t)qtyP~(7%<{%NtkG(i>#4ER|Km)YJwO47&`X zhxf|-J6_MfRwp-V&CUcZfj5tOm7&)t zJq<>K7sn`P{l7GUvK~(nYvzK7-ct7!G(Ks^Q}(}NbPX?W7#e({;W?r=$ZbUr(qWL7 z3!>a2ma*`oRjZ9l1K+OF!crs0QNLUgx@VzV9*FYEKbY2THrhMX;Pm}%I?zniy5_Y~ zM@x6-ixGb;@lV{GM^e36cr*`G;qNN=HV?#-<#XBf*6duYdIMVj*n9B6o`VMW=1w+i zviBd~u9=QOOxqouy6x@3cEM|E1~Hz$Gvue}@{w(Fj6tk!@=I*jX;HpD%B+#>{m4^W z-u^V=o@LQU#-O9)F15JXM=f@Ag(c4b diff --git a/package.json b/package.json index e83fc63..dc09d9c 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "postcss-url": "^10.1.3", "style-loader": "^4.0.0", "to-string-loader": "^1.2.0", - "url-loader": "^4.1.1" + "url-loader": "^4.1.1", + "webpack-remove-empty-scripts": "^1.0.4" } } diff --git a/webpack.config.js b/webpack.config.js index b67934b..5a77ad0 100755 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,6 +1,7 @@ var path = require('path'); var CopyPlugin = require('copy-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts'); var webpack = require('webpack'); var fs = require('fs'); var package = require('./package.json'); @@ -24,7 +25,6 @@ const defines = { module.exports = [ // WebExtension { - devtool: "source-map", entry: { content: './src/content/content.ts', background: './src/background/background.ts', @@ -93,6 +93,7 @@ module.exports = [ extensions: [".ts", ".tsx", ".js", ".json"] }, plugins: [ + new RemoveEmptyScriptsPlugin(), new webpack.DefinePlugin({ ...defines, __ENV_WEBEXTENSION: true, __ENV_USERSCRIPT: false}), new CopyPlugin({ patterns: [ { from: './src/manifest.json', to: 'manifest.json', From 18f31db87ba55879b8ddc51ac0360976da118494 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 1 May 2025 18:54:48 -0400 Subject: [PATCH 38/43] test: GM_fetch not mocked depending on test order --- happydom.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/happydom.ts b/happydom.ts index a51cee6..984722e 100644 --- a/happydom.ts +++ b/happydom.ts @@ -1,4 +1,8 @@ import { GlobalRegistrator } from "@happy-dom/global-registrator"; +import { mock } from "bun:test"; GlobalRegistrator.register(); -Object.assign(global, require('jest-chrome')) \ No newline at end of file +Object.assign(global, require('jest-chrome')) + +// Mock GM_fetch +globalThis.GM_fetch = mock(async () => {}); From 89161cb4e453a7f0e460e1139feacbb32c30b805 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 1 May 2025 18:56:32 -0400 Subject: [PATCH 39/43] fix: typo lol --- src/content/content.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/content/content.ts b/src/content/content.ts index 955f2ea..3f0feac 100644 --- a/src/content/content.ts +++ b/src/content/content.ts @@ -421,8 +421,9 @@ function addStyles() { if(__ENV_USERSCRIPT) { const head = document.head || document.getElementsByTagName('head')[0], style = document.createElement('style'); - head.appendChild(style); - style.innerHTML = styleCss; + head.appendChild(style); + style.innerHTML = require('./style.css'); + } } prepareSchema() From 6c6c94db9e81bc83c7ac5ce6d338541415752a26 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 1 May 2025 18:56:54 -0400 Subject: [PATCH 40/43] refactor: make noisy logs debug only --- src/content/content.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/content/content.ts b/src/content/content.ts index 3f0feac..9d0afae 100644 --- a/src/content/content.ts +++ b/src/content/content.ts @@ -215,7 +215,7 @@ async function inject() { const promises = qualities.filter(x => !excludedQualities.has(x)).map(async (quality) => { const qualifiedName = ((quality != 6 ? itemQualities[quality as unknown as keyof typeof itemQualities].toString() : '') + ' ' + itemName).trim() - // logDebug(`Fetching price for ${qualifiedName}`) + logDebug(`Saving price for ${qualifiedName}`) let data: ItemPriceData | null try { @@ -235,7 +235,7 @@ async function inject() { if(itemSchema[itemIndex].hasAustraliumVariant) { promises.push(fetchPrice(token, `${itemIndex};11;australium`, currentTime).then(data => { updateTime = new Date(data.update) - log(`Saving price for Australium ${itemName}`) + logDebug(`Saving price for Australium ${itemName}`) const priceRow = createPriceRow($T("Australium"), data, keyPrice, exchangeRates, locale, "https://wiki.teamfortress.com/wiki/Australium_weapons") @@ -263,7 +263,7 @@ async function inject() { promises.push(fetchPrice(token, `${itemSchema[itemIndex].festiveVariant};6`, currentTime).then(data => { updateTime = new Date(data.update) - log(`updateTime price for Festive ${itemName}`) + logDebug(`Saving price for Festive ${itemName}`) const priceRow = createPriceRow($T("Unique"), data, keyPrice, exchangeRates, locale) @@ -275,7 +275,7 @@ async function inject() { })) promises.push(fetchPrice(token, `${itemSchema[itemIndex].festiveVariant};11`, currentTime).then(data => { updateTime = new Date(data.update) - log(`Saving price for Strange Festive ${itemName}`) + logDebug(`Saving price for Strange Festive ${itemName}`) const priceRow = createPriceRow($T("Strange"), data, keyPrice, exchangeRates, locale) @@ -351,7 +351,6 @@ async function inject() { itemSchema[itemIndex].botkillerVariants.map((variantIndex) => { const itemName = itemSchema[variantIndex].name - // FIXME: variantName should match wiki display name const variantName = itemName.includes('Mk.II') ? itemName.split(' ')[0] + ' Mk.II' : itemName.split(' ')[0] promises.push(fetchPrice(token, `${variantIndex};11`, currentTime).then(data => { logDebug(`Saving price for ${itemName}`) From 5cb8de7f321b6d97aa67ea6f55537b1d571b3b05 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 1 May 2025 18:57:29 -0400 Subject: [PATCH 41/43] ci: rename test steps --- .gitea/workflows/build.yaml | 4 ++-- .gitea/workflows/release.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index aed693a..efa0685 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -23,9 +23,9 @@ jobs: id: version - name: Install dependencies run: bun install - - name: Test UserScript version + - name: Test UserScript build run: bun test --define __VERSION__='${{ steps.version.outputs.version }}' --define __EXTENSION_NAME='"tf2wikipricing"' --define __ENV_USERSCRIPT=1 --define __ENV_WEBEXTENSION=0 - - name: Test WebExtension version + - name: Test WebExtension build run: bun test --define __VERSION__='${{ steps.version.outputs.version }}' --define __EXTENSION_NAME='"tf2wikipricing"' --define __ENV_USERSCRIPT=0 --define __ENV_WEBEXTENSION=1 - name: Build project run: bun run build diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index adff067..68c2590 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -19,9 +19,9 @@ jobs: id: version - name: Install dependencies run: bun install - - name: Test UserScript version + - name: Test UserScript build run: bun test --define __VERSION__='${{ steps.version.outputs.version }}' --define __EXTENSION_NAME='"tf2wikipricing"' --define __ENV_USERSCRIPT=1 --define __ENV_WEBEXTENSION=0 - - name: Test WebExtension version + - name: Test WebExtension build run: bun test --define __VERSION__='${{ steps.version.outputs.version }}' --define __EXTENSION_NAME='"tf2wikipricing"' --define __ENV_USERSCRIPT=0 --define __ENV_WEBEXTENSION=1 - name: Build project run: bun run build --mode production From 5a1c10178e97e5be9ed1136e5e90977bd860d82e Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 1 May 2025 19:02:46 -0400 Subject: [PATCH 42/43] chore: rename package (again), use display name field --- package.json | 3 ++- webpack.config.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index dc09d9c..af1d843 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { - "name": "TF2 Wiki Pricing", + "name": "tf2wikipricing", + "displayName": "TF2 Wiki Pricing", "version": "0.7.1", "description": "Adds item pricing to the Team Fortress 2 wiki", "author": "rapture.party", diff --git a/webpack.config.js b/webpack.config.js index 5a77ad0..151bb28 100755 --- a/webpack.config.js +++ b/webpack.config.js @@ -14,7 +14,7 @@ function allReplace(str, obj, quote = true) { }; const defines = { - EXTENSION_NAME: package.name, + EXTENSION_NAME: package.displayName, __EXTENSION_NAME: JSON.stringify(package.name), EXTENSION_AUTHOR: package.author, EXTENSION_DESCRIPTION: package.description, @@ -139,7 +139,7 @@ module.exports = [ devtool: false, output: { path: path.resolve(__dirname, 'dist/userscript'), - filename: `tf2wikipricing.user.js` + filename: `${package.name}.user.js` }, resolve: { extensions: [".ts", ".tsx", ".js", ".json", ".css"] From 916c6903252944fec9e23fbbd82cfb6bb8626821 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 1 May 2025 19:02:58 -0400 Subject: [PATCH 43/43] bump version to 0.8.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af1d843..4dda83f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "tf2wikipricing", "displayName": "TF2 Wiki Pricing", - "version": "0.7.1", + "version": "0.8.0", "description": "Adds item pricing to the Team Fortress 2 wiki", "author": "rapture.party", "devDependencies": {