Comparing version 1.0.0 to 1.1.0
{ | ||
"name": "hacka-news", | ||
"version": "1.0.0", | ||
"description": "Hacker News command line reader.", | ||
"version": "1.1.0", | ||
"description": "npm module for loading and organizing content from the official Hacker News API.", | ||
"main": "src/hacka-news.js", | ||
"preferGlobal": "true", | ||
"bin" : { | ||
"hacka-news" : "./bin/index.js" | ||
}, | ||
"dependencies": { | ||
"colors": "^1.1.2", | ||
"commander": "^2.9.0", | ||
"request": "^2.60.0" | ||
}, | ||
"devDependencies": {}, | ||
"scripts": { | ||
@@ -29,5 +22,5 @@ "test": "echo \"Error: no test specified\" && exit 1" | ||
"viewer", | ||
"command", | ||
"line", | ||
"cli" | ||
"request", | ||
"api", | ||
"module" | ||
], | ||
@@ -34,0 +27,0 @@ "author": "James Cote", |
# hacka-news | ||
----------------- | ||
A command line interface (CLI) for [Hacker News](https://news.ycombinator.com/) that utilizes [their official API](https://github.com/HackerNews/API) and node.js. | ||
A node module that utilizes the [Hacker News](https://news.ycombinator.com/) [official API](https://github.com/HackerNews/API) and powers [my command line app (of similar name)](https://github.com/Coteh/hacka-news-cli). | ||
## Features | ||
- Print out stories from HN feeds (top, new, ask, show, and job) | ||
- View info for individual story specified by ID | ||
- View stories by headline only or with verbosity | ||
- Display url for a specified post | ||
- Save/load HN stories to/from favourites, which are stored as a local JSON state in program's directory | ||
- Print out list of stories that have been favourited | ||
- Returns results from HN feeds (top, new, ask, show, and job) | ||
- Returns stories in either parsed or unparsed JSON | ||
- Return a story url for post of ID | ||
@@ -21,6 +18,3 @@ ## Installation | ||
## Future Additions | ||
- Filter feed results by tags or url | ||
- Open browser window for story | ||
- Story caching: Save stories from feeds to cache | ||
- Render HTML page for stories | ||
- Save to Pocket integration | ||
- Filter feed results by story url or some other method of organization | ||
- More soon... |
var request = require('request'); | ||
var fs = require('fs'); | ||
var cli_display = require('./cli-display') | ||
var HACKA_API_URL = "https://hacker-news.firebaseio.com/v0/"; | ||
var HACKA_URL = "https://news.ycombinator.com/"; | ||
var MAX_LIST_STORIES = 500; //The official HN API only stores up to 500 top/new stories | ||
var savedIDS = []; | ||
var hackaAPIURL = "https://hacker-news.firebaseio.com/v0/"; | ||
var amtOfFeedStories = 10; | ||
var jsonObject = null; | ||
var writeJSON = function(serialized, callback){ | ||
fs.writeFile("saved-posts.json", serialized, function(err) { | ||
if (err){ | ||
console.log("ERROR: Could not save file."); | ||
return; | ||
} | ||
callback(err); | ||
}); | ||
} | ||
var getAmountOfFeedStories = function(){ | ||
return amtOfFeedStories; | ||
}; | ||
var openSavedPostsJSON = function(){ | ||
var data = fs.readFileSync("saved-posts.json", {encoding: 'utf-8'}); | ||
jsonObject = JSON.parse(data); | ||
if (jsonObject.ids != null){ | ||
setSavedPostIDS(jsonObject.ids); | ||
} | ||
} | ||
var getURL = function(id){ | ||
return HACKA_URL + "item?id=" + id; | ||
}; | ||
var getSavedPostID = function(index){ | ||
if (isNaN(index)){ | ||
throw -1; | ||
}else if (index >= savedIDS.length){ | ||
throw -2; | ||
} | ||
return savedIDS[index]; | ||
} | ||
var setAPIURL = function(url){ | ||
hackaAPIURL = url; | ||
}; | ||
var setSavedPostIDS = function(savedList){ | ||
for (var i = 0; i < savedList.length; i++){ | ||
savedIDS.push(savedList[i]); | ||
} | ||
} | ||
var savePostID = function(postID){ | ||
openSavedPostsJSON(); | ||
savedIDS.push(postID); | ||
jsonObject.ids = savedIDS; | ||
var jsonSerialized = JSON.stringify(jsonObject); | ||
writeJSON(jsonSerialized, function(err){ | ||
console.log("Successfully added post of ID " + postID + " to favourites."); | ||
}); | ||
} | ||
var unsavePostID = function(postID){ | ||
openSavedPostsJSON(); | ||
var index = savedIDS.indexOf(postID); | ||
if (index <= -1){ | ||
console.log("Could not find a post of this ID saved in your favourites."); | ||
return; | ||
} | ||
savedIDS.splice(index, 1); | ||
jsonObject.ids = savedIDS; | ||
var jsonSerialized = JSON.stringify(jsonObject); | ||
writeJSON(jsonSerialized, function(err){ | ||
console.log("Successfully removed post of ID " + postID + " from favourites."); | ||
}); | ||
} | ||
var requestFeedStoryIDs = function(storyType, callback){ | ||
request(HACKA_API_URL + storyType + "stories.json?print=pretty", function(error, response, body) { | ||
if (error){ | ||
console.log("ERROR: Couldn't load " + storyType + " stories."); | ||
return; | ||
} | ||
ids = JSON.parse(body); | ||
callback(ids); | ||
}); | ||
} | ||
request(hackaAPIURL + storyType + "stories.json?print=pretty", function(error, response, body) { | ||
if (error){ | ||
console.log("ERROR: Couldn't load " + storyType + " stories."); | ||
return; | ||
} | ||
ids = JSON.parse(body); | ||
callback(ids); | ||
}); | ||
}; | ||
var requestStory = function(id, callback){ | ||
request(HACKA_API_URL + "item/" + id + ".json?print=pretty", function (error, response, body) { | ||
if (error){ | ||
console.log("ERROR: Couldn't load story."); | ||
return; | ||
} | ||
callback(body); | ||
}); | ||
} | ||
request(hackaAPIURL + "item/" + id + ".json?print=pretty", function (error, response, body) { | ||
if (error){ | ||
console.log("ERROR: Couldn't load story."); | ||
return; | ||
} | ||
callback(body); | ||
}); | ||
}; | ||
var requestStoryParsed = function(id, callback){ | ||
requestStory(id, function(hnJsonStr){ | ||
var parsedStory = JSON.parse(hnJsonStr); | ||
injectStoryExtras(parsedStory, callback); | ||
// callback(parsedStory); | ||
}); | ||
} | ||
requestStory(id, function(hnJsonStr){ | ||
var parsedStory = JSON.parse(hnJsonStr); | ||
injectStoryExtras(parsedStory, callback); | ||
// callback(parsedStory); | ||
}); | ||
}; | ||
var injectStoryExtras = function(parsedStory, callback) { | ||
parsedStory.commentsUrl = getURL(parsedStory.id); //injecting comments url address into the node | ||
if (parsedStory.type == "comment"){ | ||
requestRootParent(parsedStory, function(rootParent){ | ||
parsedStory.rootParent = rootParent; | ||
callback(parsedStory); | ||
}); | ||
}else if (parsedStory.type == "poll"){ | ||
requestPollOptions(parsedStory, function(pollOptArr){ | ||
parsedStory.partNodes = pollOptArr; | ||
callback(parsedStory); | ||
}); | ||
}else{ | ||
callback(parsedStory); | ||
} | ||
} | ||
parsedStory.commentsUrl = getURL(parsedStory.id); //injecting comments url address into the node | ||
if (parsedStory.type == "comment"){ | ||
requestRootParent(parsedStory, function(rootParent){ | ||
parsedStory.rootParent = rootParent; | ||
callback(parsedStory); | ||
}); | ||
}else if (parsedStory.type == "poll"){ | ||
requestPollOptions(parsedStory, function(pollOptArr){ | ||
parsedStory.partNodes = pollOptArr; | ||
callback(parsedStory); | ||
}); | ||
}else{ | ||
callback(parsedStory); | ||
} | ||
}; | ||
var requestRootParent = function(childNode, callback){ | ||
if (childNode == null){ | ||
console.log("ERROR: Child node is null."); | ||
return; | ||
} | ||
//Get root parent of post (which, for a comment, should be the article they commented on) | ||
var rootParent = childNode; | ||
while (rootParent.parent != null){ | ||
rootParent = rootParent.parent; | ||
// console.log(rootParent); | ||
} | ||
requestStoryParsed(rootParent, function(parsed){ | ||
callback(parsed); | ||
}); | ||
} | ||
if (childNode == null){ | ||
console.log("ERROR: Child node is null."); | ||
return; | ||
} | ||
//Get root parent of post (which, for a comment, should be the article they commented on) | ||
var rootParent = childNode; | ||
while (rootParent.parent != null){ | ||
rootParent = rootParent.parent; | ||
// console.log(rootParent); | ||
} | ||
requestStoryParsed(rootParent, function(parsed){ | ||
callback(parsed); | ||
}); | ||
}; | ||
var requestPollOptions = function(pollNode, callback){ | ||
if (pollNode == null){ | ||
console.log("ERROR: Poll node is null."); | ||
return; | ||
} | ||
var pollOptNodes = new Array(); | ||
for (var i = 0; i < pollNode.parts.length; i++){ | ||
//Get each pollopt | ||
requestStoryParsed(pollNode.parts[i], function(parsedPollOpt){ | ||
pollOptNodes.push(parsedPollOpt); | ||
if (pollOptNodes.length >= pollNode.parts.length){ | ||
callback(pollOptNodes); | ||
} | ||
}); | ||
} | ||
} | ||
if (pollNode == null){ | ||
console.log("ERROR: Poll node is null."); | ||
return; | ||
} | ||
var pollOptNodes = new Array(); | ||
for (var i = 0; i < pollNode.parts.length; i++){ | ||
//Get each pollopt | ||
requestStoryParsed(pollNode.parts[i], function(parsedPollOpt){ | ||
pollOptNodes.push(parsedPollOpt); | ||
if (pollOptNodes.length >= pollNode.parts.length){ | ||
callback(pollOptNodes); | ||
} | ||
}); | ||
} | ||
}; | ||
var requestGroup = function(idList, callback){ | ||
var loadedList = []; | ||
var loadedCount = 0; | ||
var expectedCount = idList.length; | ||
for (var i = 0; i < idList.length; i++){ | ||
loadedList.push(null); | ||
(function(index){ | ||
requestStoryParsed(idList[index], function(hnJson){ | ||
loadedList[index] = hnJson; | ||
loadedCount++; | ||
if (loadedCount >= expectedCount){ | ||
callback(loadedList); | ||
} | ||
}); | ||
})(i); | ||
} | ||
} | ||
var getURL = function(id){ | ||
return HACKA_URL + "item?id=" + id; | ||
} | ||
var printFavourites = function(flags){ | ||
requestGroup(savedIDS, function(loadedContents){ | ||
for (var i = 0; i < loadedContents.length; i++){ | ||
if (loadedContents[i] == null){ | ||
console.log("ERROR: Story couldn't load."); | ||
continue; | ||
} | ||
cli_display.displayContent(loadedContents[i], flags); | ||
var loadedList = []; | ||
var loadedCount = 0; | ||
var expectedCount = idList.length; | ||
for (var i = 0; i < idList.length; i++){ | ||
loadedList.push(null); | ||
(function(index){ | ||
requestStoryParsed(idList[index], function(hnJson){ | ||
loadedList[index] = hnJson; | ||
loadedCount++; | ||
if (loadedCount >= expectedCount){ | ||
callback(loadedList); | ||
} | ||
}); | ||
})(i); | ||
} | ||
}); | ||
} | ||
}; | ||
var printFeed = function(storyType, flags){ | ||
requestFeedStoryIDs(storyType, function(ids){ | ||
if (ids == null){ | ||
console.log("ERROR: Stories from " + storyType + " could not load."); | ||
return; | ||
} else if (ids.error) { | ||
console.log("ERROR: " + ids.error + "."); | ||
return; | ||
var fetchTopID = function(index, callback) { | ||
if (index < 0 || index >= MAX_LIST_STORIES){ | ||
throw -1; | ||
} | ||
var prunedIDs = ids.slice(0, amtOfFeedStories); | ||
requestGroup(prunedIDs, function(loadedContents){ | ||
for (var i = 0; i < loadedContents.length; i++){ | ||
if (loadedContents[i] == null){ | ||
console.log("ERROR: Story couldn't load."); | ||
continue; | ||
requestFeedStoryIDs("top", function(ids){ | ||
if (ids == null){ | ||
console.log("ERROR: Top stories did not load."); | ||
return; | ||
} | ||
cli_display.displayContent(loadedContents[i], flags); | ||
} | ||
callback(ids[index]); | ||
}); | ||
}); | ||
} | ||
}; | ||
var printStory = function(id, flags){ | ||
requestStoryParsed(id, function(hnJson){ | ||
cli_display.displayContent(hnJson, flags); | ||
}); | ||
} | ||
var fetchTopID = function(index, callback) { | ||
if (index < 0 || index >= MAX_LIST_STORIES){ | ||
throw -1; | ||
} | ||
requestTopStoryIDs(function(ids){ | ||
if (ids == null){ | ||
console.log("ERROR: Top stories did not load."); | ||
return; | ||
} | ||
callback(ids[index]); | ||
}); | ||
} | ||
var fetchTopURL = function(index, callback) { | ||
fetchTopID(index, function(id){ | ||
callback(getURL(id)); | ||
}); | ||
} | ||
fetchTopID(index, function(id){ | ||
callback(getURL(id)); | ||
}); | ||
}; | ||
module.exports = { | ||
openSavedPostsJSON, | ||
getSavedPostID, | ||
setSavedPostIDS, | ||
savePostID, | ||
unsavePostID, | ||
getURL, | ||
printFavourites, | ||
printFeed, | ||
printStory, | ||
fetchTopID, | ||
fetchTopURL | ||
} | ||
getAmountOfFeedStories, | ||
getURL, | ||
setAPIURL, | ||
requestFeedStoryIDs, | ||
requestStory, | ||
requestStoryParsed, | ||
requestGroup, | ||
fetchTopID, | ||
fetchTopURL | ||
}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
1
0
6955
5
133
20
1
- Removedcolors@^1.1.2
- Removedcommander@^2.9.0
- Removedcolors@1.4.0(transitive)
- Removedcommander@2.20.3(transitive)