From 568b2eaabdc21f621a5d64635aff7faf83731cce Mon Sep 17 00:00:00 2001 From: xenticore Date: Fri, 21 Mar 2025 13:53:45 -0400 Subject: [PATCH] Initial commit --- .gitea/workflows/ci.yaml | 21 + .gitignore | 175 ++++++ README.md | 0 bun.lockb | Bin 0 -> 153680 bytes declarations.d.ts | 2 + package.json | 43 ++ src/content/GM_fetch/index.js | 350 +++++++++++ src/content/content.ts | 611 ++++++++++++++++++++ src/content/log.ts | 20 + src/content/pricing/pricestf.ts | 67 +++ src/content/storage.ts | 28 + src/content/style.css | 26 + src/icons/icon-48.png | Bin 0 -> 4897 bytes src/icons/icon-96.png | Bin 0 -> 13654 bytes src/manifest.json | 19 + src/resources/box-solid.svg | 1 + src/resources/btn_backpacktf.png | Bin 0 -> 5762 bytes src/resources/btn_generic.png | Bin 0 -> 5053 bytes src/resources/btn_manncostore.png | Bin 0 -> 4373 bytes src/resources/btn_stntradingeu.png | Bin 0 -> 3690 bytes src/resources/square-steam-brands-solid.svg | 1 + src/strings/en.js | 24 + src/strings/es.js | 24 + src/strings/it.js | 16 + src/strings/ja.js | 24 + src/userscript_header.js | 18 + tsconfig.json | 14 + webpack.config.js | 120 ++++ 28 files changed, 1604 insertions(+) create mode 100644 .gitea/workflows/ci.yaml create mode 100644 .gitignore create mode 100644 README.md create mode 100755 bun.lockb create mode 100644 declarations.d.ts create mode 100644 package.json create mode 100644 src/content/GM_fetch/index.js create mode 100644 src/content/content.ts create mode 100644 src/content/log.ts create mode 100644 src/content/pricing/pricestf.ts create mode 100644 src/content/storage.ts create mode 100644 src/content/style.css create mode 100644 src/icons/icon-48.png create mode 100644 src/icons/icon-96.png create mode 100755 src/manifest.json create mode 100644 src/resources/box-solid.svg create mode 100644 src/resources/btn_backpacktf.png create mode 100644 src/resources/btn_generic.png create mode 100644 src/resources/btn_manncostore.png create mode 100644 src/resources/btn_stntradingeu.png create mode 100644 src/resources/square-steam-brands-solid.svg create mode 100644 src/strings/en.js create mode 100644 src/strings/es.js create mode 100644 src/strings/it.js create mode 100644 src/strings/ja.js create mode 100644 src/userscript_header.js create mode 100644 tsconfig.json create mode 100755 webpack.config.js diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml new file mode 100644 index 0000000..79eed0a --- /dev/null +++ b/.gitea/workflows/ci.yaml @@ -0,0 +1,21 @@ +name: CI + +on: [push, pull_request] + +jobs: + build: + runs-on: debian-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4.1.2 + - name: Install dependencies + run: bun install + - name: Build project + run: bun run build + - 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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b1ee42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..6acbca9a4c4d3d5dbea77d793b4fba1274e82250 GIT binary patch literal 153680 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

B2r(VoDg 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 -1) ? upcased : method + } + + function Request(url, options) { + options = options || {} + this.url = url + + this.credentials = options.credentials || 'omit' + this.headers = new Headers(options.headers) + this.method = normalizeMethod(options.method || 'GET') + this.mode = options.mode || null + this.referrer = null + + if ((this.method === 'GET' || this.method === 'HEAD') && options.body) { + throw new TypeError('Body not allowed for GET or HEAD requests') + } + this._initBody(options.body) + } + + function decode(body) { + var form = new FormData() + body.trim().split('&').forEach(function(bytes) { + if (bytes) { + var split = bytes.split('=') + var name = split.shift().replace(/\+/g, ' ') + var value = split.join('=').replace(/\+/g, ' ') + form.append(decodeURIComponent(name), decodeURIComponent(value)) + } + }) + return form + } + + function headers(responseHeaders) { + var head = new Headers() + var pairs = responseHeaders.trim().split('\n') + pairs.forEach(function(header) { + var split = header.trim().split(':') + var key = split.shift().trim() + var value = split.join(':').trim() + head.append(key, value) + }) + return head + } + + Body.call(Request.prototype) + + function Response(bodyInit, options) { + if (!options) { + options = {} + } + + this._initBody(bodyInit) + this.type = 'default' + this.url = null + this.status = options.status + this.ok = this.status >= 200 && this.status < 300 + this.statusText = options.statusText + this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers) + this.url = options.url || '' + } + + Body.call(Response.prototype) + + self.Headers = Headers; + self.Request = Request; + self.Response = Response; + + self.GM_fetch = function(input, init) { + // TODO: Request constructor should accept input, init + var request + if (Request.prototype.isPrototypeOf(input) && !init) { + request = input + } else { + request = new Request(input, init) + } + + return new Promise(function(resolve, reject) { + var xhr_details = {}; + var _parsedRespHeaders; + + function responseURL(finalUrl, rawRespHeaders, respHeaders) { + if (finalUrl) { + return finalUrl; + } + + // Avoid security warnings on getResponseHeader when not allowed by CORS + if (/^X-Request-URL:/m.test(rawRespHeaders)) { + return respHeaders.get('X-Request-URL') + } + + return; + } + + xhr_details.method = request.method; + + xhr_details.url = request.url; + + xhr_details.synchronous = false; + + xhr_details.onload = function(resp) { + var status = resp.status + if (status < 100 || status > 599) { + reject(new TypeError('Network request failed')) + return + } + + var rawRespHeaders = resp.responseHeaders; + _parsedRespHeaders = headers(rawRespHeaders); + + var options = { + status: status, + statusText: resp.statusText, + headers: _parsedRespHeaders, + url: responseURL(resp.finalUrl, rawRespHeaders, _parsedRespHeaders) + } + var body = resp.responseText; + resolve(new Response(body, options)) + } + + xhr_details.onerror = function() { + reject(new TypeError('Network request failed')) + } + + xhr_details.headers = {}; + request.headers.forEach(function(value, name) { + xhr_details.headers[name] = value; + }); + + if(typeof request._bodyInit !== 'undefined') { + xhr_details.data = request._bodyInit; + } + + GM_xmlhttpRequest(xhr_details); + + /* + // need to see if there's any way of doing this + if (request.credentials === 'include') { + xhr.withCredentials = true + } + + GM_xmlhttpRequest has a responseType param, but this didn't seem to work, at least in TamperMonkey + if ('responseType' in xhr && support.blob) { + xhr.responseType = 'blob' + } + */ + }) + } + self.GM_fetch.polyfill = true +})(); \ No newline at end of file diff --git a/src/content/content.ts b/src/content/content.ts new file mode 100644 index 0000000..aa983e7 --- /dev/null +++ b/src/content/content.ts @@ -0,0 +1,611 @@ +import { getStorageValue, setStorageValue } from './storage' +import styleCss from './style.css' + +declare function GM_fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise +import './GM_fetch' +import { logDebug, log, logError } from './log' +import { getPricesToken, priceUsingPricesTF } from './pricing/pricestf' +const semver = require('semver') +// Globals +import itemQualities from 'tf2-static-schema/static/qualities.json'; +var itemSchema: { [key: string]: {name: string, tradable: Boolean}; } | null; + +declare const __VERSION__: string; + +// Constants +const storage_lastUpdateTime = 'tf2wikipricing_lastUpdate'; +const storage_schema = 'tf2wikipricing_schema'; +const storage_version = 'tf2wikipricing_version'; +const storage_priceprefix = 'tf2wikipricing_sku_'; +const conversion_ref_usd = 0.0265; +const defindex_key = 5021; +const defindex_metal_refined = 5002; +const defindex_metal_reclaimed = 5001; +const defindex_metal_scrap = 5000; + +/** Pricing data for a given TF2 item. */ +class ItemPriceData { + /** Item SKU. */ + sku: string + /** Date updated. */ + update: Date + /** TTL in milliseconds. */ + ttl: number + /** Price in keys. */ + keys: number + /** Price in refined metal. */ + metal: number + /** Steam Community Market price. */ + scmPrice: number + + toString(): string { + return `Price for ${this.sku}, fetched ${this.update} (expires ${this.update.getTime() + this.ttl})\n` + + JSON.stringify({ + keys: this.keys, + metal: this.metal, + scmPrice: this.scmPrice + }) + } +} + +class SteamMarketSearchResult { + name: string + sell_price: number +} + +/** Exclude these from the pricelist. */ +const excludedQualities = new Set([ + 15, // Decorated + 5, // Unusual +]); + +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 +} + +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; +} + +// Helper functions +function findFirstElement(selector: string): HTMLElement | null { + const elements = document.querySelectorAll(selector); + return elements.length > 0 ? elements[0] as HTMLElement : null; +} + +function findFirstChildElement(selector: string, root: Element): HTMLElement | null { + const elements = root.querySelectorAll(selector); + return elements.length > 0 ? elements[0] as HTMLElement : null; +} + +function getKeyByValue(object: any, value: string) { + return Object.keys(object).find(key => object[key] === value); +} + +function extractPageTitleFromURL(url: string): string { + var split = url.substring(url.indexOf("/wiki/") + "/wiki/".length); + if (split.indexOf('/') != -1) { + // Remove language suffix (/es) + split = split.substring(0, split.indexOf('/')); + } + return decodeURIComponent(split.replaceAll('_', ' ')); +} + +function extractLocaleFromURL(url: string): string { + var split = url.substring(url.indexOf("/wiki/") + "/wiki/".length); + if (split.indexOf('/') != -1) { + // Remove language suffix e.g. `/es` + return split.substring(split.indexOf('/') + 1); + } else { + return 'en'; + } +} + +function isDateAfterOneDay(date1: Date, date2: Date): boolean { + var diff = date2.getTime() - date1.getTime(); + var diffDays = Math.round(diff / (1000 * 3600 * 24)); + return diffDays > 1; +} + +function getItemIndexByName(name: string) { + for (const [defindex, value] of Object.entries(itemSchema)) { + if (value['name'] == name) { + return parseInt(defindex) + } + } + return null +} + +function getTradableStatusByDefindex(defindex: number) { + return itemSchema[defindex.toString()].tradable +} + +function getTradableStatusByName(name: string) { + for (const [defindex, value] of Object.entries(itemSchema)) { + if (value['name'] == name) { + return value.tradable + } + } + return true +} + +// Main function +async function inject() { + const itemInfobox = findFirstElement('.item-infobox'); + if (!itemInfobox) { + // Not an item page + return; + } + const locale = extractLocaleFromURL(document.URL); + var itemIndex: number | null = null; + var itemName: string | null = null; + + // Try using buy buttons, if they exist + const buyButton = findFirstChildElement('.btn_buynow', itemInfobox); + const marketButton = findFirstChildElement('.btn_buynow_market', itemInfobox); + + if(buyButton) { + const link = (buyButton.parentElement as HTMLLinkElement); + if(link && link.href) { + itemIndex = parseInt(link.href.replace('https://store.steampowered.com/buyitem/440/', '')); + } + } + + if(!itemIndex && marketButton) { + const link = (marketButton.parentElement as HTMLLinkElement); + if(link && link.href) { + itemIndex = parseInt(link.href.replace('https://steamcommunity.com/market/search/?q=appid:440+prop_def_index:', '')); + } + } + + // Try using item name + const header = findFirstChildElement('.infobox-header', itemInfobox); + if (!itemIndex && header) { + // Get from document.body + const canonical = document.querySelector('link[rel="canonical"]'); + if (canonical && canonical instanceof HTMLLinkElement) { + const url = canonical.href; + if (url.indexOf("/wiki/") != -1) { + itemName = extractPageTitleFromURL(url); + } + } + + const url = document.URL; + + if (itemName && !itemIndex) { + itemIndex = getItemIndexByName(itemName) + } + } + + if (!itemIndex) { + // Cannot continue without index + logError(itemName ? `Could not find defindex for ${itemName}` : `Could not determine item defindex or name`); + return; + } + + if(!itemName) { + // Get name from index + itemName = (itemSchema[itemIndex.toString()] as any).name; + } + + log(`Starting lookup for ${itemName} (defindex ${itemIndex})`); + + if(getTradableStatusByDefindex(itemIndex) == false) { + log(`${itemName} is not tradable, exiting`) + return; + } + + var qualities: number[] = [] + + const firstQualityTag = findFirstChildElement('.quality-tag', itemInfobox); + + for (const qualityTag of Array.from(firstQualityTag.parentElement.children)) { + const link = findFirstChildElement('a', qualityTag) as HTMLLinkElement; + if (!link) { + continue; + } + const qualityName = extractPageTitleFromURL(link.href); + const quality = parseInt(getKeyByValue(itemQualities, qualityName)); + if (!quality) { + continue; + } + qualities.push(quality); + } + + /// Create buttons + + // Item infobox is a table with the following layout: + // + // th.infobox-header (Item Name) + // tr (3D item viewer/2D preview image) + // tr (buy buttons if applicable) + // ... <- We want to insert our button here. + // th.infobox-header (Basic Information) + // ... + + var storeButtons: HTMLTableRowElement[] = []; + + // backpack.tf button + storeButtons.push(createStoreButton("backpack.tf", new URL(`https://backpack.tf/classifieds?item=${encodeURIComponent(itemName)}`))); + + // mannco.store button + storeButtons.push(createStoreButton("mannco.store", new URL(`https://mannco.store/tf2?&search=${encodeURIComponent(itemName)}&page=1`))); + + // marketplace.tf button + // Disabled due to requiring login + // storeButtons.push(createStoreButton("marketplace.tf", new URL(`https://marketplace.tf/browse/tf2?sterm=${encodeURIComponent(itemName)}`))); + + // stntrading.eu button + storeButtons.push(createStoreButton("stntrading.eu", new URL(`https://stntrading.eu/item/tf2/${encodeURIComponent(itemName)}`))); + + const headers = itemInfobox.querySelectorAll("th.infobox-header"); + storeButtons.reverse().forEach(element => { + if (marketButton) { + marketButton.closest("tr").insertAdjacentElement('afterend', element); + } else if (buyButton) { + buyButton.closest("tr").insertAdjacentElement('afterend', element); + } else if (headers.length > 2) { + headers[1].closest("tr").insertAdjacentElement('beforebegin', element); + } else { + itemInfobox.children[0].appendChild(element); + } + }); + + /// Create price infobox + const priceInfoboxHeadingRow = document.createElement("tr") + const priceInfoboxHeading = document.createElement("th") + priceInfoboxHeading.className = "infobox-header" + priceInfoboxHeading.colSpan = 2 + priceInfoboxHeading.innerText = $T("Community Pricing") + priceInfoboxHeadingRow.appendChild(priceInfoboxHeading); + headers[1].closest("tr").insertAdjacentElement('beforebegin', priceInfoboxHeadingRow); + + // Create progress bar + const priceProgressRow = document.createElement("tr") + const priceProgressData = document.createElement("td") + priceProgressData.colSpan = 2 + const priceProgress = document.createElement("progress"); + priceProgress.id = "tf2wikipricing"; + priceProgress.style.width = "75%" + priceProgress.style.marginLeft = "12.5%" + priceProgressData.appendChild(priceProgress); + priceProgressRow.appendChild(priceProgressData); + priceInfoboxHeadingRow.insertAdjacentElement('afterend', priceProgressRow); + + var token: string | null; + + // Steam Community Market + // TODO: Change this to lazy-load, so that it doesn't make network requests when we have cached data. + // var steamMarketResults = await getSteamResults(itemName) + // logDebug(JSON.stringify(steamMarketResults)) + + // Fetch prices.tf access token + // https://api2.prices.tf/auth/access + try { + // throw new Error('dont wanna') + token = await getPricesToken(); + } catch (err) { + log('Failed to get an access token for prices.tf: ' + err); + } + + var updateTime: Date | null = null; + + interface PriceRow { + quality: number + row: HTMLTableRowElement + } + var priceRows: PriceRow[]= []; + + // Get current key price + const keyPrice = await fetchKeyPrice(token); + + var 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; + const cached = await getStorageValue(storage_priceprefix + sku, null) + if (cached != null && 'keys' in cached && 'metal' in cached) { + data = cached + } + if (!data || 'update' in data && 'ttl' in data && Date.now() > (new Date(data.update).getTime() + data.ttl)) { + data = new ItemPriceData() + data.sku = sku + data.update = new Date() + data.ttl = (5 * 60 * 1000) // Cache results for 5 minutes + + // logDebug(JSON.stringify(steamMarketResults)) + // const scmResult = steamMarketResults.find((x: object) => { x['name' as keyof object] === qualifiedName}) + // logDebug(JSON.stringify(scmResult)) + // if(scmResult) { + // data.scmPrice = scmResult.sell_price / 100 + // } + + try { + const response = await priceUsingPricesTF(token, itemIndex, quality) + if (response) { + data.keys = response.keys + data.metal = response.metal + } + } catch (error) { + log(`Received ${error} error while pricing ${sku} using prices.tf`) + } + + if ('keys' in data && 'metal' in data) { + await setStorageValue(storage_priceprefix + sku, data) + } + logDebug(JSON.stringify(data)); + updateTime = new Date(data.update) + } else { + logDebug(`Using cached data for ${sku}, expires ${new Date(data.update).getTime() + data.ttl}`); + updateTime = new Date(data.update) + } + */ + var data: ItemPriceData | null + try { + data = await fetchPrice(token, itemIndex, quality, currentTime); + updateTime = new Date(data.update) + } catch { + log(`${qualifiedName} is unpriced or unavailable, skipping...`) + } + + const priceRow = document.createElement("tr"); + + const priceLabel = document.createElement("td"); + priceLabel.className = "infobox-label"; + const priceLabelLink = document.createElement("a"); + const qualityName = itemQualities[quality as unknown as keyof typeof itemQualities].toString() + priceLabelLink.href = locale === 'en' ? `https://wiki.teamfortress.com/wiki/${qualityName}` : `https://wiki.teamfortress.com/wiki/${qualityName}/${locale}` + priceLabelLink.innerText = $T(qualityName) + priceLabel.appendChild(priceLabelLink); + priceLabel.innerHTML += ':' + priceRow.appendChild(priceLabel); + + const priceData = document.createElement("td"); + const priceLink = document.createElement("span"); + const priceString = data ? formatPrice(data.keys, data.metal, keyPrice.metal).trim() : $T('Data unavailable') + priceLink.innerHTML = priceString // + `
$${data.scmPrice}` + priceData.appendChild(priceLink); + priceRow.appendChild(priceData); + + priceRows.push({quality: quality, row: priceRow}) + }) + Promise.all(promises).then(() => { + priceRows.sort((a, b) => { + // Sort 6 first always, then numerically + if (a.quality === 6) { + return -1; + } else if (b.quality === 6) { + return 1; + } else { + return a.quality == b.quality ? a.quality < b.quality ? -1 : 1 : 0; + } + }).reverse().forEach((element) => { + priceInfoboxHeadingRow.insertAdjacentElement('afterend', element.row); + }) + if(!updateTime) { updateTime = new Date() } + + // Footer row + const row = document.createElement("tr"); + + const label = document.createElement("td"); + label.colSpan = 2; + label.style.fontSize = "85%"; + const updateText = $T("Updated %@.", locale).replace('%@', updateTime.toLocaleString(locale, { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', timeZoneName: 'short' })) + const attributionText = $T("Trade prices sourced from %@. Currency conversions are approximate.", locale).replace('%@', '
prices.tf'); + label.innerHTML = `${updateText}
${attributionText}`; + row.appendChild(label); + + priceProgressRow.insertAdjacentElement('afterend', row); + priceProgressRow.remove() + }) +} + +function createStoreButton(storeName: string, url: URL) { + const button = document.createElement("tr") + var source = `

` + source = source.replace("{link}", url.toString()) + source = source.replace("{title}", $T("View listings on %@").replace('%@', storeName)) + button.innerHTML = source + return button +} + +async function fetchKeyPrice(token: string) { + return fetchPrice(token, defindex_key, 6, new Date(), 86400000) +} + +/** + * Fetch a price for a given SKU, using cached values if available. + * @param token prices.tf access token. + * @param update Date retrieved. + * @param ttl Time to cache results in milliseconds. 30 minutes by default. + */ +async function fetchPrice(token: string, defIndex: number, quality: number, update: Date = new Date(), ttl: number = 30 * 60 * 1000): Promise { + return new Promise(async (resolve, reject) => { + const sku = defIndex.toString() + ";" + quality.toString(); + var data: ItemPriceData | null + + const cached = await getStorageValue(storage_priceprefix + sku, null) + if (cached != null && 'keys' in cached && 'metal' in cached) { + data = cached + } + + 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) + } + data = new ItemPriceData() + data.sku = sku + data.update = update + data.ttl = ttl + + try { + const response = await priceUsingPricesTF(token, defIndex, quality) + if (response) { + data.keys = response.keys + data.metal = response.metal + } + } catch (error) { + log(`Received ${error} error while pricing ${sku} using prices.tf`) + reject(error) + } + + if ('metal' in data && 'keys' in data) { + await setStorageValue(storage_priceprefix + sku, data) + } + } else { + logDebug(`Using cached price data for ${sku}`) + } + resolve(data) + }) +} + +async function getSteamResults(itemName: string) { + logDebug(`Making network request to Steam for ${itemName}`) + const response = await GM_fetch(`https://steamcommunity.com/market/search/render?appid=440&norender=true&count=10&query=${encodeURIComponent(itemName)}`, { + method: 'get', + headers: new Headers({ + 'Accept': 'application/json' + }) + }) + if (response.status === 200) { + const json = await response.json(); + return json['results'] + } + return [] +} + +function toFixed(num: number, fixed: number) { + var re = new RegExp('^-?\\d+(?:\.\\d{0,' + (fixed || -1) + '})?'); + return num.toString().match(re)[0]; +} + +var pageLocale: string = 'en' + +function formatPrice(keys: number, metal: number, keyPrice: number) { + const pureMetal = (keys * keyPrice) + metal; + const formattedKeys = +(keys + (metal / keyPrice)).toFixed(2) + + var output: string = '' + if(keys > 0) { + output += (formattedKeys == 1.0 ? $T("%@ key") : $T("%@ keys")).replace('%@', formattedKeys.toLocaleString(pageLocale)) + } else { + output += `${(+toFixed(metal, 2)).toLocaleString(pageLocale)} ref` + } + const currencyFormatter = new Intl.NumberFormat(pageLocale, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); + + // Round price up to nearest cent + const price = Math.ceil(pureMetal * conversion_ref_usd * 100) / 100 + output += ` (US$${currencyFormatter.format(price)})` + return output; +} + +async function prepareSchema() { + var needsUpdate: Boolean = false + + const storedVersion: string | null = await getStorageValue(storage_version, null) + if(!storedVersion || !semver.valid(storedVersion)) { + log(`Cache is from an unknown version of the extension. Updating for version ${__VERSION__}`); + needsUpdate = true + } else if(semver.valid(storedVersion) && semver.lt(storedVersion, __VERSION__)) { + log(`Cache is from a previous version (${storedVersion}) of the extension. Updating for version ${__VERSION__}`); + needsUpdate = true + } else { + itemSchema = await getStorageValue(storage_schema, null); + } + + const update = await getStorageValue(storage_lastUpdateTime, null) + if (update) { + log(`Item schema updated at ${new Date(update)}`); + const lastUpdateTime = new Date(update); + if (!itemSchema || isDateAfterOneDay(lastUpdateTime, new Date())) { + needsUpdate = true + } + } + + if(needsUpdate) { + log("Item Schema out of Date. Rebuilding..."); + const url = "https://raw.githubusercontent.com/danocmx/node-tf2-static-schema/master/static/items.json" + const response = await GM_fetch(url); + if (response.ok) { + await setStorageValue(storage_lastUpdateTime, new Date().getTime()); + + var cacheItems = {} + + var responseItems: any[] = await response.json() + // We want to keep the keys `defindex`, `item_name`, and `attributes` + responseItems.forEach((item: any) => { + const defindex = item['defindex'] + const name = item['item_name'] + var tradable: Boolean = true + try { + if(item['attributes'] != null) { + if(item['attributes'].find((attribute: {}) => (attribute as any)['class'] == "cannot_trade")) { + tradable = false + } + } + } catch(error) { + logError(error) + log(item) + } + (cacheItems as any)[defindex.toString()] = { "name": name, "tradable": tradable } + }); + + await setStorageValue(storage_schema, (cacheItems)); + itemSchema = cacheItems + await setStorageValue(storage_version, __VERSION__); + logDebug(`Item schema updated at ${new Date()}`) + } else { + logError("Could not fetch item schema."); + } + } +} + +function addStyles() { + const head = document.head || document.getElementsByTagName('head')[0], + style = document.createElement('style'); + head.appendChild(style); + style.innerHTML = styleCss; +} + +prepareSchema().then(function () { + if (!itemSchema) { + logError("No item schema ready, exiting."); + return; + } + pageLocale = extractLocaleFromURL(document.URL) + addStyles(); + inject(); + // TODO: Purge expired price data +}); \ No newline at end of file diff --git a/src/content/log.ts b/src/content/log.ts new file mode 100644 index 0000000..23ababe --- /dev/null +++ b/src/content/log.ts @@ -0,0 +1,20 @@ +declare var __PRODUCTION: boolean; +declare var __EXTENSION_NAME: string; +const logHeader = `[${__EXTENSION_NAME}] `; + +/** `console.debug` with header; automatically NO-OP on production build */ +function logDebug(message?: any, ...optionalParams: any[]): void { + if(process.env.NODE_ENV !== 'production') console.debug(logHeader + message, optionalParams); +} + +/** `console.log` with header */ +function log(message?: any, ...optionalParams: any[]): void { + console.log(logHeader + message, optionalParams) +} + +/** `console.error` with header */ +function logError(message?: any, ...optionalParams: any[]): void { + console.error(logHeader + message, optionalParams) +} + +export { log, logDebug, logError } \ No newline at end of file diff --git a/src/content/pricing/pricestf.ts b/src/content/pricing/pricestf.ts new file mode 100644 index 0000000..4f83674 --- /dev/null +++ b/src/content/pricing/pricestf.ts @@ -0,0 +1,67 @@ +declare function GM_fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise +import '../GM_fetch' + +async function getPricesToken(): Promise { + return new Promise((resolve, reject) => { + GM_fetch('https://api2.prices.tf/auth/access', { + method: 'post', + headers: new Headers({ + 'Accept': 'application/json' + }) + }) + .then((response) => { + if (response.status != 200) { + reject(response.status) + } + return response.json() + }) + .then((responseData) => resolve(responseData['accessToken'])) + }) +} + +class PricesResponse { + keys: number + metal: number +} + +/** + * Price the given item using https://prices.tf + * @return + */ +async function priceUsingPricesTF(token: string, defIndex: number, quality: number): Promise { + // prices.tf + // https://api2.prices.tf/prices/${sku} + // Authorization: Bearer ${token} + return new Promise(async (resolve, reject) => { + if (!token) { + reject(401) + } + const sku = defIndex + ";" + quality; + // logDebug(`Making network request to prices.tf for ${sku}`) + var 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 && quality === 6) { + // Try uncraftable variant + response = await GM_fetch(`https://api2.prices.tf/prices/${encodeURIComponent(sku + ';uncraftable')}`, { + method: 'get', + headers: new Headers({ + 'Accept': 'application/json', + 'Authorization': `Bearer ${token}` + }) + }) + } + if (response.status === 200) { + const json = await response.json() + resolve({ keys: json['sellKeys'], metal: json['sellHalfScrap'] / 18.0 }) + } else { + reject(response.status) + } + }) +} + +export { getPricesToken, priceUsingPricesTF } \ No newline at end of file diff --git a/src/content/storage.ts b/src/content/storage.ts new file mode 100644 index 0000000..d14fea8 --- /dev/null +++ b/src/content/storage.ts @@ -0,0 +1,28 @@ +declare var __ENV_USERSCRIPT: boolean; +declare var __ENV_WEBEXTENSION: boolean; + +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 { + return new Promise((resolve) => { + resolve(defaultValue); + }); + } +} + +function setStorageValue(name: string, value: any): Promise { + if(__ENV_USERSCRIPT) { + return GM.setValue(name, value); + } else if(__ENV_WEBEXTENSION) { + return browser.storage.local.set({name, value}); + } else { + return new Promise((resolve, reject) => { + reject(); + }); + } +} + +export { getStorageValue, setStorageValue } diff --git a/src/content/style.css b/src/content/style.css new file mode 100644 index 0000000..c86c29d --- /dev/null +++ b/src/content/style.css @@ -0,0 +1,26 @@ +[class^="btn_buynow_addon_"] { + padding: 6px 4px 4px 28px; + background-position: right 0; + position: relative; + display: block; + left: -1px; + height: 14px; +} +[class^="btn_buynow_addon_"]:hover { + background-position: 0 -24px; +} +[class^="btn_buynow_addon_"], [class^="btn_buynow_addon_"] span { + background: url('../resources/btn_generic.png') no-repeat; + color: #FFF; + line-height: 100%; + font-size: 90%; +} +.btn_buynow_addon_backpacktf, .btn_buynow_addon_backpacktf span { + background: url('../resources/btn_backpacktf.png') no-repeat; +} +.btn_buynow_addon_manncostore, .btn_buynow_addon_manncostore span { + background: url('../resources/btn_manncostore.png') no-repeat; +} +.btn_buynow_addon_stntradingeu, .btn_buynow_addon_stntradingeu span { + background: url('../resources/btn_stntradingeu.png') no-repeat; +} \ No newline at end of file diff --git a/src/icons/icon-48.png b/src/icons/icon-48.png new file mode 100644 index 0000000000000000000000000000000000000000..425ceb8f13f9a7f5ec28440fdf6847ed923f4e2e GIT binary patch literal 4897 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 \ No newline at end of file diff --git a/src/resources/btn_backpacktf.png b/src/resources/btn_backpacktf.png new file mode 100644 index 0000000000000000000000000000000000000000..29a20edff061d677c5e1d442191ec75942f9dee0 GIT binary patch literal 5762 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 literal 0 HcmV?d00001 diff --git a/src/resources/btn_generic.png b/src/resources/btn_generic.png new file mode 100644 index 0000000000000000000000000000000000000000..c23441dc4aa3a7aab75ddef9bb17a63bf9990551 GIT binary patch literal 5053 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 dc`%Isk~Z@jjpo4KY~&;5$pg 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 laLm{zkHUR \ No newline at end of file diff --git a/src/strings/en.js b/src/strings/en.js new file mode 100644 index 0000000..36dd116 --- /dev/null +++ b/src/strings/en.js @@ -0,0 +1,24 @@ +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 + "%@ 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" +} \ No newline at end of file diff --git a/src/strings/es.js b/src/strings/es.js new file mode 100644 index 0000000..788f147 --- /dev/null +++ b/src/strings/es.js @@ -0,0 +1,24 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "Ver ofertas en %@", + + // Itembox header + "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) + + // Price strings + "Data unavailable": "Datos no disponibles", // sourced from AppleGlot + "%@ key": "%@ llave", + "%@ keys": "%@ llaves", + + // Item quality names, all sourced from TF2 wiki + "Normal": "de Calidad Normal", + "Genuine": "de Calidad Genuina", + "Vintage": "de Calidad Clásica", + "Unique": "de Calidad Única", + "Strange": "de Calidad Rara", + "Collector's": "de Coleccionista", + "Haunted": "de Calidad Embrujada" +} \ No newline at end of file diff --git a/src/strings/it.js b/src/strings/it.js new file mode 100644 index 0000000..d4a3463 --- /dev/null +++ b/src/strings/it.js @@ -0,0 +1,16 @@ +module.exports = { + "View listings on %@": "Voir les offres sur %@", + "Community Pricing": "Community Pricing", + "Updated %@": "Updated %@", + "From %@": "From %@", + "Data unavailable": "Data unavailable", + "%@ key": "%@ key", + "%@ keys": "%@ keys", + "Normal": "Normale", + "Genuine": "Autentico", + "Vintage": "Vintage", + "Unique": "Unico", + "Strange": "Strano", + "Collector's": "Da collezione", + "Haunted": "Stregato" +} \ No newline at end of file diff --git a/src/strings/ja.js b/src/strings/ja.js new file mode 100644 index 0000000..43315d1 --- /dev/null +++ b/src/strings/ja.js @@ -0,0 +1,24 @@ +module.exports = { + // Generic button text, %@ is always a URL (eg. backpack.tf) + "View listings on %@": "%@で検索結果を見る", + + // Itembox header + "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) + + // Price strings + "Data unavailable": "データがありません", // sourced from AppleGlot + "%@ key": "%@ key", + "%@ keys": "%@ keys", + + // Item quality names, all sourced from TF2 wiki + "Normal": "ノーマル", + "Genuine": "ジェニュイン", + "Vintage": "ビンテージ", + "Unique": "専用", + "Strange": "ストレンジ", + "Collector's": "Collector's", + "Haunted": "Haunted" +} \ No newline at end of file diff --git a/src/userscript_header.js b/src/userscript_header.js new file mode 100644 index 0000000..f8f443a --- /dev/null +++ b/src/userscript_header.js @@ -0,0 +1,18 @@ +// ==UserScript== +// @name EXTENSION_NAME +// @description EXTENSION_DESCRIPTION +// @version EXTENSION_VERSION +// @match *://wiki.teamfortress.com/wiki/* +// @run-at document-start +// @inject-into content +// @connect steamcommunity.com +// @domain steamcommunity.com +// @connect prices.tf +// @domain prices.tf +// @grant GM.setValue +// @grant GM_setValue +// @grant GM.getValue +// @grant GM_getValue +// @grant GM.xmlhttpRequest +// @grant GM_xmlhttpRequest +// ==/UserScript== \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..92c656e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "outDir": "./dist/", + "noImplicitAny": true, + "module": "es6", + "target": "es6", + "jsx": "react", + "lib": ["dom", "es2021"], + "allowJs": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true + } +} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js new file mode 100755 index 0000000..5e87d44 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,120 @@ +var path = require('path'); +var CopyPlugin = require('copy-webpack-plugin'); +var webpack = require('webpack'); +var fs = require('fs'); +var package = require('./package.json'); + +function allReplace(str, obj, quote = true) { + for (const x in obj) { + str = str.replace(new RegExp(x, 'g'), quote ? `"${obj[x]}"` : obj[x]); + } + return str; +}; + +const defines = { + EXTENSION_NAME: package.name, + __EXTENSION_NAME: JSON.stringify(package.name), + EXTENSION_AUTHOR: package.author, + EXTENSION_DESCRIPTION: package.description, + EXTENSION_VERSION: package.version, + __VERSION__: JSON.stringify(package.version), +} + +module.exports = [ + /* + // WebExtension + { + entry: { + content: './src/content/content.ts' + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + { + test: /\.(png|jpg|gif|svg)$/i, + use: [ + { + loader: 'url-loader', + options: { + limit: true, + }, + }, + ], + }, + ], + }, + optimization: { + minimize: true + }, + output: { + path: path.resolve(__dirname, "dist/extension"), + filename: "[name]/[name].js" + }, + resolve: { + extensions: [".ts", ".tsx", ".js", ".json", ".css"] + }, + plugins: [ + new webpack.DefinePlugin({__ENV_WEBEXTENSION: true, __ENV_USERSCRIPT: false}), + new CopyPlugin({ patterns: [ + { from: './src/manifest.json', to: 'manifest.json', + transform(content, absoluteFrom) { + return allReplace(content.toString(), defines) + }, + } + ]}), + new CopyPlugin({ patterns: [ + { from: './src/icons', to: 'icons/[file]'}, + ]}), + ], + }, + */ + // Userscript + { + entry: { + content: './src/content/content.ts' + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + { + test: /\.css$/i, + use: ['style-loader', 'css-loader'] + }, + { + test: /\.(jpe?g|png|ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/, + type: 'asset/inline' + }, + ], + }, + optimization: { + minimize: true, + }, + devtool: false, + output: { + path: path.resolve(__dirname, 'dist/userscript'), + filename: `${package.name}.user.js` + }, + resolve: { + extensions: [".ts", ".tsx", ".js", ".json", ".css"] + }, + plugins: [ + new webpack.DefinePlugin({ ...defines, __ENV_USERSCRIPT: true, __ENV_WEBEXTENSION: false }), + new webpack.BannerPlugin({ + raw: true, + banner: () => { + const header = fs.readFileSync(path.resolve(__dirname, 'src/userscript_header.js'), 'utf8') + return allReplace(header, defines, false) + }, + stage: webpack.Compilation.PROCESS_ASSETS_STAGE_REPORT + }), + ], + } +];