• If you enjoy the forum please consider supporting it by signing up for a NES Membership  The benefits pay for the membership many times over.

Target Sports locusts (myself included)

GREED at it's worst. Discusting. And bragging yet.

To be fair, they posted the source code and I'm sure they'd walk you through setting it up if you wanted to do it as well. And honestly, we could all use a utility like this considering the shortage--which has very little to do with a handful of people who can program and more to do with the general sh*tshow conditions of 2020.
 
Lol, so you're pissed because some guy figured out how to (effectively) stand in line ahead of you?

*roiling fist* NO DATS CHEATING!!!! etc. [rofl]

You'd be better off yelling at TS for not limiting purchases or doing a lottery or some other scheme, etc, to more evenly distribute whatever little ammo they do get.

Also as much as I like TS, for those in the know, due to the ammo crisis they're way beyond the pale now as far as pricing goes. Generally speaking you would do better, on average, to shop local on the ground in New England. TS is easily 10-20CPR or more on a lot of stuff -OVER- local prices. The problem with local is availability, but if you made a circuit you could probably scrape up a few boxes here and there.
Great advice. TS briefly had Fed Champion 525rd (Blue Box) for $0.12/rd, and I found it locally (and plentifully) at just over $.06/rd in the last week or so. Just need to be diligent in your search(es).
 
To clarify, I'm not necessarily accusing you guys of being greedy, just more resourceful than me. And I hope the opposing side in our civil unrest doesn't realize the same opportunity.

Since I don't speak l33t, I was just paying a neighbor kid to camp out on TSUSA during his fall break and offering him a 10% finder's fee. But now he's been told to stand down.
Ha, no problem. I was very restrained in what I bought because I did feel a "little" guilty. However, it was all self restraint. TSU would have let me buy all of it so long as the credit card cleared. That's the problem. If it was limited in some reasonable measure, it wouldn't be so bad for everyone. I'm sure there are LOTS of people buying it by the ton and reselling it for a fat profit.
 
Lol, so you're pissed because some guy figured out how to (effectively) stand in line ahead of you?

*roiling fist* NO DATS CHEATING!!!! etc. [rofl]

You'd be better off yelling at TS for not limiting purchases or doing a lottery or some other scheme, etc, to more evenly distribute whatever little ammo they do get.

Also as much as I like TS, for those in the know, due to the ammo crisis they're way beyond the pale now as far as pricing goes. Generally speaking you would do better, on average, to shop local on the ground in New England. TS is easily 10-20CPR or more on a lot of stuff -OVER- local prices. The problem with local is availability, but if you made a circuit you could probably scrape up a few boxes here and there.
That's how I did it during the O years before I started buying online. Three to four shops, two to three times a week and always buying more than I shot is how I got a lot of my stash.
 
Oh, yeah. It was about "Programming"... [rofl2]

I'm not really good at computers, but this is what my hacker friend "4chan" made for me. Provided without warranty or customer support.

Stuff is trickling in once or twice a week. Please don't be a dick and hoard stuff you don't need; it's important for me to conduct prudent inventory replenishment without competing with you clowns.

Code:
(ns fed-primers.core
  (:gen-class))

(use 'clojure.pprint)
(use 'clojure.string)

(def query-interval-seconds (* 5 60))

(def products
  [{:name "Federal small pistol"
    :url "https://www.site-that-does-not-ship-to-mass.com/product/federal-100-small-pistol-primers-1000/"
    :currently-in-stock false
    :last-time-in-stock nil}
   {:name "Federal small pistol (match)"
    :url "https://www.site-that-does-not-ship-to-mass.com/product/federal-gm100m-sm-pstl-match-1000/"
    :currently-in-stock false
    :last-time-in-stock nil}])

(defn in-stock? [url]
  (let [page (str (slurp url))
        first-in-stock-index (index-of page "In stock")
        first-out-of-stock-index (index-of page "Out of stock")]
    (cond (nil? first-in-stock-index) false
          (nil? first-out-of-stock-index) true

          (< first-in-stock-index first-out-of-stock-index) true
          (> first-in-stock-index first-out-of-stock-index) false)))

(defn update-products [products]
  "Returns a mutated copy of the products vector of maps,
   with most recent stock status and last date/time in-stock"
  (map (fn [p]
         (as-> p p
           (assoc p :currently-in-stock
                  (in-stock? (:url p)))
           (assoc p :last-time-in-stock
                  (if (:currently-in-stock p)
                         (str (java.time.LocalDateTime/now))
                         (:last-time-in-stock p)))))
       products))

(defn -main []
  (while true
    (let [products (update-products products)]
      (do
        (println "Query performed at: " (str (java.time.LocalDateTime/now)))
        (println "Next query in " query-interval-seconds " seconds")
        (print-table products)
        (Thread/sleep (* query-interval-seconds 1000))))))
For anyone interested, here it is in C#, the only extra library I used was HtmlAgilityPack. Presented without warranty...e.g. the second they change the web site, this will break.

C#:
using System;
using System.Collections.Generic;
using System.Net;

using HtmlAgilityPack;

namespace ConProductSearch
{
    /// <summary>
    /// Rip the web site, looking for available products.
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            Program me = new Program();
            me.Run();
        }

        private void Run()
        {
            // Each product has a name (for your benefit) and a product code (to search by).  You can figure out the codes easily from each product's url
            // Change this list as you see fit
            DisplayProductAvailability("45 ACP", 70);
            DisplayProductAvailability("9 MM", 51);
            DisplayProductAvailability("40 S&W", 59);
            DisplayProductAvailability("308 Win", 101);
            DisplayProductAvailability("30-06", 105);
            DisplayProductAvailability("6.5 Creedmoor", 776);

            Console.WriteLine();
            Console.WriteLine("Hit enter to exit");
            Console.ReadLine();
        }

        private List<HtmlNode> GetAvailableProducts(int productId)
        {
            List<HtmlNode> availableProducts = new List<HtmlNode>();

            // This is an internal page used by the main product page that populates all the images, price and availability
            String url = "https://www.targetsportsusa.com/ISearch.aspx?PageNumber=0&PageSize=40&PageSort=pv.PricePerRound&ColorFilter=&SizeFilter=&PriceFilter=&TypeFilter=&ManufacturerFilter=&CategoryFilter=" + productId
                + "&GenreFilter=&DistributorFilter=&VectorFilter=&SectionFilter=&LibraryFilter=&stockFilter=false&Filter=%";

            // Load the product page and store all it's html in a dom model
            var html = new HtmlDocument();
            html.LoadHtml(new WebClient().DownloadString(url));

            HtmlNode root = html.DocumentNode;

            HtmlNode ul = root.ChildNodes.FindFirst("ul");

            HtmlNodeCollection products = ul.ChildNodes;

            foreach (HtmlNode productNode in products)
            {
                HtmlNodeCollection productParts = productNode.ChildNodes;

                foreach (HtmlNode productSubNode in productParts)
                {
                    if (productSubNode.Name.ToUpper() == "A")
                    {
                        if (productSubNode.ChildNodes.FindFirst("button").InnerText.ToUpper() == "ADD TO CART")
                        {
                            availableProducts.Add(productSubNode);
                            continue;
                        }
                    }
                }
            }

            return availableProducts;
        }

        private void DisplayProductAvailability(String productName,int productId)
        {          
            // See if is anything available
            List<HtmlNode> availableProducts = GetAvailableProducts(productId);

            if (availableProducts.Count > 0)
            {
                Console.WriteLine("--------------------------------------------------------------");
                Console.WriteLine(productName);
                Console.WriteLine();

                foreach (HtmlNode availableProduct in availableProducts)
                {
                    HtmlNodeCollection avParts = availableProduct.ChildNodes;

                    String name = avParts.FindFirst("h2").InnerText;
                    String productUrl = "";
                    String src = "";

                    HtmlAttributeCollection imageAttributes = avParts.FindFirst("span").ChildNodes.FindFirst("img").Attributes;
                    HtmlAttributeCollection anchorttributes = availableProduct.ParentNode.ChildNodes.FindFirst("a").Attributes;

                    foreach (HtmlAttribute attr in imageAttributes)
                    {
                        if (attr.Name.ToUpper() == "SRC")
                        {
                            src = attr.Value;
                            break;
                        }
                    }

                    foreach (HtmlAttribute attr in anchorttributes)
                    {
                        if (attr.Name.ToUpper() == "HREF")
                        {
                            productUrl = "https://www.targetsportsusa.com" + attr.Value;
                            break;
                        }
                    }

                    Console.WriteLine("   " + name);
                    Console.WriteLine("   " + productUrl);
                    Console.WriteLine("   " + src);
                    Console.WriteLine();
                }
            }
        }
    }
}
 
TSUSA really ought to just cut to the chase and open up an API. Scraping web pages has always irked me for some reason, and the API will save their servers/CDN expenses... The same end will be reached one way or another.

A single multi-resource request to their API, or some stupid scraper refreshing 25 different pages, HTML, JS, and all, every 2 seconds -- their call. 🤷‍♂️
 
TSUSA really ought to just cut to the chase and open up an API. Scraping web pages has always irked me for some reason, and the API will save their servers/CDN expenses... The same end will be reached one way or another.

A single multi-resource request to their API, or some stupid scraper refreshing 25 different pages, HTML, JS, and all, every 2 seconds -- their call. 🤷‍♂️

That would be more convenient. Or they could just raise their prices to what the market will bear, and hoarding will instantly stop.
 
Lol, so you're pissed because some guy figured out how to (effectively) stand in line ahead of you?

*roiling fist* NO DATS CHEATING!!!! etc. [rofl]
It is. Reminds of the bastids who order Dunks on their phone and then waltz right on past all the folks who have any shred of integrity.
 
TSUSA really ought to just cut to the chase and open up an API. Scraping web pages has always irked me for some reason, and the API will save their servers/CDN expenses... The same end will be reached one way or another.

A single multi-resource request to their API, or some stupid scraper refreshing 25 different pages, HTML, JS, and all, every 2 seconds -- their call. 🤷‍♂️
How much simpler maneuver is just to cap/ration purchases per account.
 
Code:
(ns fed-primers.core
  (:gen-class))

(use 'clojure.pprint)
(use 'clojure.string)

(def query-interval-seconds (* 5 60))

(def products
  [{:name "Federal small pistol"
    :url "https://www.site-that-does-not-ship-to-mass.com/product/federal-100-small-pistol-primers-1000/"
    :currently-in-stock false
    :last-time-in-stock nil}
   {:name "Federal small pistol (match)"
    :url "https://www.site-that-does-not-ship-to-mass.com/product/federal-gm100m-sm-pstl-match-1000/"
    :currently-in-stock false
    :last-time-in-stock nil}])

(defn in-stock? [url]
  (let [page (str (slurp url))
        first-in-stock-index (index-of page "In stock")
        first-out-of-stock-index (index-of page "Out of stock")]
    (cond (nil? first-in-stock-index) false
          (nil? first-out-of-stock-index) true

          (< first-in-stock-index first-out-of-stock-index) true
          (> first-in-stock-index first-out-of-stock-index) false)))

(defn update-products [products]
  "Returns a mutated copy of the products vector of maps,
   with most recent stock status and last date/time in-stock"
  (map (fn [p]
         (as-> p p
           (assoc p :currently-in-stock
                  (in-stock? (:url p)))
           (assoc p :last-time-in-stock
                  (if (:currently-in-stock p)
                         (str (java.time.LocalDateTime/now))
                         (:last-time-in-stock p)))))
       products))

(defn -main []
  (while true
    (let [products (update-products products)]
      (do
        (println "Query performed at: " (str (java.time.LocalDateTime/now)))
        (println "Next query in " query-interval-seconds " seconds")
        (print-table products)
        (Thread/sleep (* query-interval-seconds 1000))))))

I tried entering that code into the Google search, and now all I get popping up is porn...

...oh wait, never mind, that is just me. HAHA 😉
 
That would be more convenient. Or they could just raise their prices to what the market will bear, and hoarding will instantly stop.
Can't do that apparently. Raising prices to what the market will bear gets you a forever reputation as a gouger (à la cheaper than dirt).
 
Also posted in main TSUSA thread, quoting here:

Federal American Eagle 147gr back in, apparently over 99k in stock, notified by text 2 minutes ago. But I'm not desperate enough for $0.55/rd.

View attachment 398031
View attachment 398032

 
Back
Top Bottom