Skip to content

Instantly share code, notes, and snippets.

@ianpreston
Created April 20, 2012 15:36
Show Gist options
  • Save ianpreston/2429681 to your computer and use it in GitHub Desktop.
Save ianpreston/2429681 to your computer and use it in GitHub Desktop.
var sys = require("sys"),
http = require("http"),
querystring = require("querystring"),
htmlparser = require("htmlparser"),
soupselect = require("soupselect"),
irc = require("irc");
/**
* Wrapper for making an HTTP request, reading the data, instantiating a htmlparser.Parser
* and parsing the page. htmlparserHandlerFunction will be passed to htmlparser.DefualtHandler.
*/
var fetchAndParsePage = function(host, path, htmlparserHandlerFunction) {
var request = http.createClient(80, host).request("GET", path, {"host": host});
request.on("response", function(response) {
response.setEncoding("utf8");
var body = "";
response.on("data", function(chunk) {
body += chunk;
});
response.on("end", function() {
var handler = new htmlparser.DefaultHandler(htmlparserHandlerFunction);
var parser = new htmlparser.Parser(handler);
parser.parseComplete(body);
});
});
request.end();
};
/**
* Search CalorieKing's database and call a function with the path to the food that
* is the result of the search. Path is in the format '/foo/bar' relative to www.calorieking.com
*
* arg:searchString = The name of the food to search for
* arg:pathCallback = pathCallback(error, path) will be called when the URL is parsed from the page
*/
var getNutritionFactsPath = function(searchString, pathCallback) {
fetchAndParsePage("www.calorieking.com",
"http://www.calorieking.com/foods/search.php?" + querystring.stringify({"keywords": searchString, "go": "Search", "showresults": "yes"}),
function(error, dom) {
if (error) {
console.log("Error! " + error);
} else {
var foodLinks = soupselect.select(dom, ".food-search-result-name");
if (foodLinks[0])
{
pathCallback(null, foodLinks[0].attribs.href);
} else {
pathCallback("No results for food search '" + searchString + "'", null);
}
}
});
};
/**
* Parses a CalorieKing nutrition facts page and returns relevant information.
*
* arg:nutritionFactsPath = the result of getNutritionFactsPath
* arg:factsCallback = factsCallback(object) will be called with the parsed nutrition facts
*/
var parseFacts = function(nutritionFactsPath, factsCallback) {
fetchAndParsePage("www.calorieking.com",
nutritionFactsPath,
function(error, dom) {
if (error) {
console.log("Error!:" + error);
} else {
var foodNameSpan = soupselect.select(dom, "#heading-food-cat-desc")[0];
factsCallback({"carbs": soupselect.select(dom, "#total_carbohydrate")[0].children[0].raw.replace(""", "'").replace("&", "&"),
"servingSize": soupselect.select(dom, "#units option")[0].children[0].raw.replace(""", "'").replace("&", "&"),
"foodName": foodNameSpan.children[foodNameSpan.children.length-1].raw.replace(""", "'").replace("&", "&").trim()});
}
});
};
/**
* Now we just join an IRC channel and wait for !carbs messages
*/
var main = function() {
var client = new irc.Client("localhost", "carbs-bot", {
"userName": "carbs",
"realName": "carbs-bot",
"channels": ["#carbs-bot"],
"autoRejoin": false});
client.addListener("message", function(from, to, msg) {
if (msg.indexOf("!carbs") == 0)
{
var searchString = msg.substr("!carbs ".length);
getNutritionFactsPath(searchString, function(error, path) {
if (error)
{
client.say(to, "Error: " + error);
} else {
parseFacts(path, function(facts) {
client.say(to, "Nutrition facts for " + facts.foodName);
client.say(to, facts.carbs + " carbs in one serving of " + facts.servingSize);
client.say(to, "Details: " + path);
});
}
});
}
});
};
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment