Skip to content

Commit

Permalink
Major rewrite, version 1.0.0
Browse files Browse the repository at this point in the history
Signed-off-by: petetnt <pete.a.nykanen@gmail.com>
  • Loading branch information
petetnt committed Jul 31, 2016
1 parent 58b9345 commit 6cbce82
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 194 deletions.
7 changes: 7 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
"extends": ["motley"],
"rules": {
"no-console": 0,
"global-require": 0,
}
}
68 changes: 0 additions & 68 deletions .eslintrc.yml

This file was deleted.

3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.config.js
node_modules/
node_modules/
.DS_Store
37 changes: 10 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Ascii-shot

Get your latest Instagram shot as an ASCII version to `stdout`.
:rainbow: Get an users instagram feed as an ASCII version to `stdout`.

![ASCIIfeed](./assets/example.png)
![Ascii-shot](./assets/example.png)

## Installing

Expand All @@ -12,34 +12,17 @@ Install `ascii-shot` with `npm`
npm install ascii-shot -g
```

## Usage (CLI)
## Usage

### ascii-shot
Prints your latest Instagram shot as an ASCII version to `stdout`.

``` bash
ascii-shot
```
Usage
$ ascii-shot <username>
## Setting up
Instagram API is quite restrictive, so you'll need to jump through some hoops.

1. Setup your client here: https://www.instagram.com/developer/
2. Set your `INSTAGRAM_CLIENT_ID`, `INSTAGRAM_CLIENT_SECRET` and `INSTAGRAM_REDIRECT_URI` as environment variables. For example

``` bash
set INSTAGRAM_CLIENT_SECRET=your_secret_here
Examples
$ ascii-shot petetnt
```

When you run `ascii-shot`, it will automatically fetch your `access_token` and show your newest Instagram shot. If it's your first time running the script, it will most likely ask you to authoritize the app.

After you have successfully gained your `access_token`, check the console and save it as an environment variable `INSTAGRAM_ACCESS_TOKEN` to skip further need to get it again. (The tokens, however, might expire at some point so you might need to get another later).

## Why would I want this?
¯\\\_(ツ)_

Like I said, the API is quite restrictive and this is pretty much all ASCII fun you can have without breaking all or some of the rules.

And then use spacebar to fetch more images. Any other key will exit the program.

## License and acknowledgements
MIT Copyright © 2016 Pete Nykänen
## License
MIT
7 changes: 7 additions & 0 deletions VERSIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# 1.0.0

- Complete rewrite using the semi-public API `username/media`. Fixes #1.

# 0.0.1 - 0.0.3

- Legacy version using authenticated API communication.
Binary file modified assets/example.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 18 additions & 3 deletions bin/ascii-shot
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
#!/usr/bin/env node --harmony
'use strict';
require("../lib");
#!/usr/bin/env node
const meow = require("meow");
const cli = meow(`
Usage
$ ascii-shot <username>
Examples
$ ascii-shot petetnt
`);

const username = cli.input[0];

if (!username) {
console.error(" Error: You must pass an username");
cli.showHelp();
} else {
require("../lib/ascii-shot")(username);
}
155 changes: 132 additions & 23 deletions lib/ascii-shot.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,151 @@
const EOL = require("os").EOL;
const fetch = require("node-fetch");
const cheerio = require("cheerio");
const ansi = require('ansi');
const ansi = require("ansi");
const cursor = ansi(process.stdout);

/**
* Takes a HTML-formatted text and parses the containing ASCII image to text.
* Also writes the attached metadata to console.
* @param {String} text - HTML body
* @param {object} image - Object containing related metadata.
* @returns {Promise.resolve} - Resolved promise for chaining.
*/
function _parseAsciiImage(text) {
const $ = cheerio.load(text);
const imageParts = $("font").children();

imageParts.each((index, elem) => {
if (elem.name === "span") {
const $elem = $(elem);
cursor.red().write($elem.text()).reset();
}
function writeInstagramShot(text, image) {
const $ = cheerio.load(text);
const imageParts = $("font").children();

if (elem.name === "br") {
cursor.write(EOL);
}
});
const {
likes,
date,
caption,
} = image;

cursor.write(`${"-".repeat(52)}${EOL}`);
cursor.write(`Caption: ${caption}${EOL}`);
cursor.write(`Created at: ${date}${EOL}`);
cursor.write(`Likes: ${likes}${EOL}`);
cursor.write(`${"-".repeat(52)}${EOL}`);

imageParts.each((index, elem) => {
if (elem.name === "span") {
const $elem = $(elem);
cursor.red().write($elem.text()).reset();
}

if (elem.name === "br") {
cursor.write(EOL);
}
});

return Promise.resolve();
}

/**
* Formats the itemsArray to caption/likes/imageurl format
* @param {Array} itemsArray - Array of instagram items
* @returns {Array} - Array of image urls
*/
const formatItemsArray = (itemsArray) => (
itemsArray.map((item) => {
const {
images,
likes,
caption,
created_time: createdTime,
} = item;

const {
standard_resolution: standardResolution,
low_resolution: lowResolution,
} = images;

let url = null;

if (lowResolution) {
url = lowResolution.url.split("?")[0];
} else if (standardResolution) {
url = standardResolution.url.split("?")[0];
}

return {
likes: likes.count,
caption: caption ? caption.text : "",
date: new Date(createdTime * 1000),
url,
};
}).sort((a, b) => a.date.getTime() - b.date.getTime())
);

/**
* Writes an image to the console
* @param {String} imageUrl - Url of the image to fetch
* @returns {Promise} - Res
*/
const writeImageToConsole = (image) => {
console.log(`Fetching ${image.url}...`);
return fetch(`${image.url}.html`)
.then((res) => res.text())
.then((text) => writeInstagramShot(text, image))
.catch((err) => {
console.error(" Error: Ressor fetching/parsing images:");
console.error(err.message);
});
};

/**
* Fetches the latest Instagram shot of the owner of the access_token
* @param {String} token - access token
*/
const AsciiShot = (token) => {
fetch(`https://api.instagram.com/v1/users/self/media/recent/?access_token=${token}`).then(res => {
return res.json();
}).then(json => {
const image = json.data[0].images.standard_resolution.url.split("?")[0];
return fetch(`${image}.html`);
}).then(res => {
return res.text();
}).then(_parseAsciiImage);
const AsciiShot = (username) => {
fetch(`https://instagram.com/${username}/media`)
.then((res) => res.json())
.then(json => {
const itemsArray = json.items;

if (!json || !itemsArray) {
throw new Error("No items found.");
}

let isFetching = false;

const instagramShots = formatItemsArray(itemsArray);

const fetchNextOrDie = (data) => {
process.stdin.setRawMode(false);

if (isFetching) {
return;
}

if (data && data.toString("hex") !== "20") {
process.exit(0);
}

isFetching = true;

writeImageToConsole(instagramShots.pop()).then(() => {
isFetching = false;
if (instagramShots.length) {
process.stdin.setRawMode(true);
process.stdin.resume();
console.log("");
console.log("");
console.log("Press space to fetch the next one or any other key to exit");
} else {
console.log(`No more images for ${username} :)`);
process.exit(0);
}
});
};

fetchNextOrDie();
process.stdin.on("data", fetchNextOrDie);
})
.catch((err) => {
console.error(` Error: Couldn't fetch images for username ${username}`);
console.error(err.message);
});
};

module.exports = AsciiShot;
1 change: 0 additions & 1 deletion lib/index.js

This file was deleted.

0 comments on commit 6cbce82

Please sign in to comment.