4
\$\begingroup\$

This script takes input from poker tournament pages at pokeratlas dot com, and outputs a CSV file that I can import to Google Calendar, listing each of the tournament events, start time, location, and a link back to pokeratlas for their full detail page.

I've used moment, Q, and cheerio. I learned moment specifically for parsing date/time strings here. I would use "jsdom" for parsing, because I've used it before, but it's a serious bear to install in Windows. So, this is the first time I've used anything jQuery like. I'm learning Q to use in other projects, so I thought I'd include it here, but I can't really find much of a good use for it, since there's very little that is actually asynchronous here.

I'm seeking any input anyone might have as to how to make better use of cheerio, Q, or general node.js code criticism.

var http = require('http');
var moment = require('moment');
var Q = require('q');
var cheerio = require('cheerio');

function loadPage(options) {
    var content = "",
        d = Q.defer();
    var req = http.request(options, function(res) {
        res.setEncoding("utf8");
        res.on("data", function(chunk) {
            content += chunk;
        });
        res.on("end", function() {
            d.resolve(content);
        });
        res.on("error", function(err) {
            d.reject(err);
        });
    });
    req.end();
    return d.promise;
}

function parseSection(section) {
    // each day has a "section" tag, which has a child that has class "header" and it's "id" == "header-(date)" where date is the date of the events contained.
    var date = section.children(".header").attr("id").substr("header-".length);
    // each section has multiple divs classed "tournament-item", which give the details of each individual tournament
    var tournaments = section.find(".tournament-item");
    tournaments.each(function(index, tournament) {
        parseTournament(date, cheerio(tournament));
    });
}

function parseTournament(date, tournament) {
    // TODO: perhaps a future enhancement might be to load each individual tournament's page, and parse details off it, but for now, this works.
    // each tournament-item has a single anchor that links to a page with details about the specific game.
    var tournamentLink = tournament.find("a").attr("href"),
    // each one has a div classed "buy-in" which describes the buy-in for the game
        buyin = tournament.find(".buy-in").text().replace(/[\n\r,]+/g,''),
    // each one has a div classed "venue-name" which gives the readable name for the location
        locationName = tournament.find(".venue-name").text(),
    // locations are often of format ("Name of room", or "Name of room at (name of building or place they are inside)",
    // make some short hand name to stick in the main description.  This is all very arbitrary, but we tend to know
    // where a place is based on it's location name, rather than the name of the room itself.  ie, "Krazy Kopz at Vision Lanes"
    // is not ambiguous, when an event could also be hosted by "Krazy Kopz" at "Joey's" or something else.
    // Without inserting NLP :-) this works fine for my purposes here.
    // So, "Momo's Poker Room" becomes just "Momo's", and "Krazy Kopz at Vision Lanes" becomes just "Vision Lanes".
    // So far, the only WTF i've encountered here are games listed as at "300 Bowl". Which shows as "300". Perhaps
    // adding a minimum length to number of characters before we start splitting would be helpful.
        shortLocName = locationName.indexOf(" at ") > -1 ? locationName.split(" at ").pop() : locationName.split(" ").shift(),
    // each one has a div classed "type" which provides the type of game being played -- NLHE for NoLimitHoldem, PLO for PotLimitOmaha, others are possible
        type = tournament.find(".type").text(),
    // each one has a div classed "time" which provides the start time of the game. guesstimated end times are occasionally on page, but not reliably.
        time = tournament.find(".time").text(),
    // each one has a div classed "structure-info" which provides some free-hand text info about the game, usually how many chips you start with, and how
    // fast the game progresses.  There may be nothing, or there may be other info.
        structure = tournament.find(".structure-info").text().replace(/[\n\r,]+/g, ''),
    // An example summary will read: "Caesars $65 NLH"
        summary = shortLocName + " " + buyin + " " + type,
        outStr = summary + "," + date + ",";

    // time comes in format "3:30pm", but Calendar requires it in format "03:30:00 PM"
    if(time) {
        time = moment(date + " " + time, "YYYY-MM-DD H:mma");
        time = time.format("hh:mm:ss A");
    }

    outStr += String(time) + ",";
    outStr += '"' + structure;
    outStr += "\nDetails: http://www.pokeratlas.com" + tournamentLink;
    outStr += '"';
    outStr += ",";
    outStr += locationName;
    console.log(outStr);
}

function parsePage(content) {
    var $ = cheerio.load(content);
    $('section').each(function(index, section) {
        parseSection($(section));
    });
}

// http://www.pokeratlas.com/poker-tournaments/detroit-toledo/upcoming?page=0
var options = {
    host: "www.pokeratlas.com",
    port: 80,
    path: "/poker-tournaments/detroit-toledo/upcoming?page=0"
};

console.log("Subject,Start Date,Start Time,Description,Location");

loadPage(options)
.then(function(page) {
    parsePage(page);
})
.catch(function(err) {
    console.log("ERROR", err);
})
.done();
\$\endgroup\$
2
\$\begingroup\$

Bearing in mind that this code was written over five years ago, things may have changed drastically with it and you may have learned a lot since then.

Long comment lines

The longest line appears to be this comment which is 159 characters (including whitespace):

// each day has a "section" tag, which has a child that has class "header" and it's "id" == "header-(date)" where date is the date of the events contained.

For readability, use a multi-line comment and split it into multiple lines:

  /* each day has a "section" tag, which has a child that has class "header" and 
     it's "id" == "header-(date)" where date is the date of the events contained.
  */

http.request() vs http.get()

Using http.get() eliminates the need to call req.end();

Since most requests are GET requests without bodies, Node.js provides this convenience method. The only difference between this method and http.request() is that it sets the method to GET and calls req.end() automatically. 1

Promise library

The Q library hasn't been published since 2018 and has failed builds. It isn’t needed to return a promise- this SO post shows an example using the Promise constructor. That could easily be used to eliminate the Dependency of the Q library.

overwriting variables

I see these lines:

if(time) {
   time = moment(date + " " + time, "YYYY-MM-DD H:mma");
   time = time.format("hh:mm:ss A");
}

While the intermediate value (returned by calling moment() is only used once, it isn’t exactly the same as the First value assigned to time because it is a moment object instead of a string. For readability it would be better to call that variable something else, like dateTime.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Things sure have changed a lot since then, eh? Node was a bit stagnant back at that point in history, and my employer at the time was still using a pre-Promise version of node (hence, why we used Q). Javascript and the way people are using it, along with common styles, and best practices are vastly, vastly different now. \$\endgroup\$ – Eric Blade Oct 9 '20 at 17:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.