diff --git a/README.md b/README.md index 62b3d61..57028c6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # BackpackDecalSpotter -Finds decaled items on Backpack.tf \ No newline at end of file +Finds decaled items on Backpack.tf. Relies on legacy soon-to-be-deprecated endpoints. Should be hosted to avoid "Access to Image from origin 'null' has been blocked by CORS policy" errors when fetching accounts with decaled item listings. \ No newline at end of file diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..8acd187 Binary files /dev/null and b/favicon.ico differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..dff5e64 --- /dev/null +++ b/index.html @@ -0,0 +1,20 @@ + + + + Backpack Decal Spotter + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 0000000..f19ac14 --- /dev/null +++ b/script.js @@ -0,0 +1,117 @@ +var checkable_accounts = { '76561198311319887': 'Weapon Crusher' }; +decaled_objects = { 'Conscientious Objector': '3.66', 'Clan Pride': '2.11', 'Flair!': '4.22', 'Photo Badge': '2.44' } +backpack_page_limit = 3; + +function fetchAccounts() { + var deferred = new $.Deferred(); + for (const [item, price] of Object.entries(decaled_objects)) { + handleItem(item, price); + } + deferred.resolve(checkable_accounts); + return deferred.promise() +} + +function handleItem(item, price) { + for (let i = 1; i <= backpack_page_limit; i++) { + $.ajax({ + dataType: 'html', + url: 'https://backpack.tf/classifieds', + data: 'page=' + i + '&item=' + encodeURIComponent(item) + '&quality=6&tradable=1&craftable=1&australium=-1&killstreak_tier=0&numeric=price&comparison=lt&value=' + price + '&low=' + price, + async: false, + tryCount: 0, + retryLimit: 3, + success: function (msg) { + if ($('.col-md-6:first .alert', msg).length > 0) { + return false; + } + $('.col-md-6:first .user-link', msg).each(function () { + checkable_accounts[$(this).attr('data-id')] = $(this).attr('data-name'); + }); + }, + error: function (response, status, error) { + if (response.status == 429) { + this.tryCount++; + if (this.tryCount <= this.retryLimit) { + setTimeout(() => { + $.ajax(this); + }, 3000); + return; + } + } + } + }); + } +} + +function handleAccounts() { + for (const [steamid64, name] of Object.entries(checkable_accounts)) { + handleAccount(steamid64, name); + } +} + +function handleAccount(steamid64, name) { + var url = "https://backpack.tf/_inventory/" + steamid64; + $.ajax({ + dataType: 'json', + url: url, + async: false, + tryCount: 0, + retryLimit: 3, + success: function (msg) { handleHTML(steamid64, name, msg['html']); }, + error: function (response, status, error) { + if (response.status == 429) { + this.tryCount++; + if (this.tryCount <= this.retryLimit) { + setTimeout(() => { + $.ajax(this); + }, 3000); + return; + } + } + } + }); +} + +function handleHTML(steamid64, name, html_string) { + html = $.parseHTML(html_string); + var decaled_items = $('div.decal', html); + if (decaled_items.length == 0) { + var empty_accounts = $('#empty-accounts'); + empty_accounts.append('' + name + '; '); + empty_accounts.fadeIn(); + return false; + } + var inventory = $('
'); + inventory.append('

' + name + '

'); + decaled_items.each(function () { + var url = "https://next.backpack.tf/item/" + $(this).parent().attr('data-id'); + var a = $(''); + a.append($(this).parent()); + inventory.append(a); + }); + var inventories = $('.inventory'); + if (inventories.length > 0) { + var child_count = inventory.children().length; + inventory.attr('data-items', child_count); + var last_div = null; + inventories.each(function () { + if (child_count > $(this).children().length) { + return false; + } + last_div = $(this); + }); + if (last_div) { + last_div.after(inventory); + } else { + $('body').prepend(inventory); + } + } else { + $('#empty-accounts').before(inventory); + } +} + +$(document).ready(function () { + $.when(fetchAccounts()).done(function () { + handleAccounts(); + }); +}); \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..f3c8bee --- /dev/null +++ b/style.css @@ -0,0 +1,119 @@ +body { + background-color: #111; + color: #eee; + font-family: Verdana, Geneva, sans-serif; + font-size: 0.85em; + margin: 0px; + text-align: justify; + text-justify: inter-word; + word-wrap: break-word; + word-break: break-all; + white-space: normal; + text-align: center; +} + +div { + display: inline-block; +} + +h3 { + text-align: center; + padding: 0.5em; +} + +h4 { + color: #555; + text-align: center; + padding: 5em; +} + +li { + display: inline-block; + width: 140px; + height: 140px; + position: relative; + border-radius: 3px; + margin: 10px; + border: 3px solid; + margin-bottom: 38px; +} + +.decal { + width: 128px; + height: 128px; + margin: auto; + z-index: 2; + position: absolute; + top: 6px; + left: 6px; +} + +.item-icon { + width: 24px; + height: 24px; + bottom: -38px; + left: -3px; + background-size: 24px; + z-index: 1; + position: absolute; + border-radius: 3px; + border: 3px solid; +} + +.q-440-6, +.q-440-6 .item-icon, +.q-440-6 .bottom-right { + background-color: rgb(119, 101, 3); + border-color: rgb(181, 154, 5); +} + +.q-440-11, +.q-440-11 .item-icon , +.q-440-11 .bottom-right { + background-color: rgb(110, 56, 26); + border-color: rgb(144, 73, 34); +} + +.nocraft { + border-style: dashed; +} + +.top-left { + display: none; +} + +.bottom-right { + position: absolute; + width: 105px; + height: 24px; + bottom: -38px; + left: 32px; + border-radius: 3px; + border: 3px solid; + margin: auto; +} + +.bottom-right span { + text-align: center; + display: block; + margin-top: 3px; +} + +a, +a:link, +a:visited, +a:hover, +a:active { + text-decoration: none; + color: #eee; +} + +h4 a, +h4 a:link, +h4 a:visited, +h4 a:hover, +h4 a:active { + text-decoration: none; + color: #555; + font-style: italic; +} \ No newline at end of file