Creating a Bot/Node.js in more detail

*For a simpler node.js tutorial, see the regular Creating a Bot/Node.js (JavaScript) guide.*

*If you want a closer look at the fb module and the most common API calls you can make, keep reading.*

This guide assumes you can wade your way through the tutorial linked above, and you have some understanding of JavaScript and \*shudder\* callbacks. If not, I highly suggest working through the above tutorial and looking up anything you're stuck on; there's no shame in searching for help when you're stuck, DuckDuckGo and StackOverflow are your friends.

## A look at the FB.api() method

Recall that to do a basic Facebook post, the code looks something like the following:

```
let {FB, FacebookApiException} = require('fb');
let post\_text = "Hello, world!";

FB.api("me/feed", "post", {message: post\_text}, resp => {
 if(!resp || resp.error) {
 console.error((!resp ? 'error occurred, no response object' : resp.error));
 } else {
 console.log(`Your post id is ${resp.id}`);
 }
});

```

The FB.api() call takes four arguments:

* **endpoint** - this is the API node you want to access. To make a post on your page's feed, the endpoint is *me/feed*.
* **method** - this is how you want to interact with the endpoint (typically either 'get', 'post', or 'delete')
* **data** - this is an object that contains data to submit with your API call. For a text post, the required data is a "message" key, whose value is the text of the post to make.
* **callback** - this is the *resp => { ... }* part, which is also shorthand for *function(resp) { ... }*. You can use either syntax depending on what you prefer. This is the code that is run when the api() function finishes its work. It gives you a response object named resp, which contains the results of the API operation.

The callback function is divided into two parts by an if/else block. The first is the error-checking: if the response object is null/undefined, or the response object has an error attribute, then you know than an error has occurred and should handle that appropriately.

The second part, in the else block, is the code that runs if the API operation succeeded. Here, we print the id of the newly-created post. It's useful to store this somewhere if you want to do something with that post later, like read its comments or reacts.

So with that, let's take a look at some common Graph API functions and how to do them in node.js.

## Common Graph API functions

### Making a text post

***Text posts may or may not still be getting flagged as spam for no reason; consider making all your posts image posts but with a blank image that's only 1 pixel in height to get around this.***

This has already been described above and in the simple JS guide, so it won't be restated here. Instead, heed the warning and do an image post instead.

### Making an image post

**Endpoint**: me/photos

**Method**: post

**Data**:

* caption (optional) - a string value that is the text to be posted as the image caption
* source (required) - a FileStream that is created from the image to be uploaded (see the 'fs' module)

**Response**:

* id - the ID of the created image

**Example**

```
const fs = require("fs");

let imageFile = "./resources/bot-logo.png";
let postText = "Hello, world!";

let data = {
 source: fs.createReadStream(imageFile),
 caption: postText
}

FB.api("me/photos", "post", data, resp => {
 // handle it here
})

```

### Making a comment

**Endpoint**: [object id]/comments --- *object id can be a post id, photo id, or a top-level comment id*

**Method**: post

**Data**: At least one of these is required

* message - a string value that is the text to comment
* source - a FileStream that is created from the image to be uploaded for a photo/video comment (yes, this supports video comments)

**Response**:

* id - the ID of the created comment

### Getting reactions

This one is a little verbose, but this implementation works better for collecting all reactions from a post, photo, or something else with a /reactions edge on it.

The problem lies in that the endpoint for getting all the Likes on something is

```
[object id]?fields=reactions.limit(0).type(LIKE).summary(total_count)

```

Then you have to do that for LOVE. And HAHA. And so on. And what about arbitrary new reacts like Pride, Thankful, or Care, which don't have proper ways to fetch them like this? This is a one-way ticket into what many JS devs refer to as *callback hell*, and it is exactly as hellish as it sounds.

Let's not do that. Instead, we'll run a **batch** operation: collect all react counts and grab the 'official' total count in one move, then use the reported total minus the sum of everything else to get the 'other' react counts. Checkmate.

**Endpoint**: *none* (use the empty string "")

**Method**: post

**Data**: This monstrosity, which I'll explain in a sec:

```
let objectid = "your object id here";
let data = {
 batch: [
 { method: 'get', relative\_url: `${objectid}?fields=reactions.limit(0).type(LIKE).summary(total\_count)` },
 { method: 'get', relative\_url: `${objectid}?fields=reactions.limit(0).type(LOVE).summary(total\_count)` },
 { method: 'get', relative\_url: `${objectid}?fields=reactions.limit(0).type(HAHA).summary(total\_count)` },
 { method: 'get', relative\_url: `${objectid}?fields=reactions.limit(0).type(WOW).summary(total\_count)` },
 { method: 'get', relative\_url: `${objectid}?fields=reactions.limit(0).type(SAD).summary(total\_count)` },
 { method: 'get', relative\_url: `${objectid}?fields=reactions.limit(0).type(ANGRY).summary(total\_count)` },
 { method: 'get', relative\_url: `${objectid}?fields=reactions.limit(0).summary(total\_count)` }
 ]
};

```

**Response**: A messy seven-part response, one for each of the batch calls we set in data. We'll get to that too.

#### Data

So this is what we call a batch operation: we're running several different API calls at a time, and each one gets a specific react count. Specifically, we're looking at the **reactions** field on our object, using limit(0) to force it to return all results instead of a paged list you have to go through, then filtering by type(REACT), then getting the total count for that type.

Each one of these API calls returns its own separate response object, and they're all stored in the same response sent to the callback function. So how do we access the returned data?

#### Response

The response object is no longer a single object, but now an array (list) of multiple objects. The first one, resp[0], correlates to the LIKE count. resp[1] is for LOVE, and so on.

Now, to do anything useful with this, we need to convert the response bodies returned to actual usable data.

```
var reacts = {};
reacts.like = JSON.parse(resp[0].body).reactions.summary.total\_count;
reacts.love = JSON.parse(resp[1].body).reactions.summary.total\_count;
reacts.haha = JSON.parse(resp[2].body).reactions.summary.total\_count;
reacts.wow = JSON.parse(resp[3].body).reactions.summary.total\_count;
reacts.sad = JSON.parse(resp[4].body).reactions.summary.total\_count;
reacts.angry = JSON.parse(resp[5].body).reactions.summary.total\_count;
reacts.total = JSON.parse(resp[6].body).reactions.summary.total\_count;
reacts.other = reacts.total - (reacts.like + reacts.love + reacts.haha + reacts.wow + reacts.sad + reacts.angry);

```

Beautiful.

### Getting comments

**Endpoint**: [object-id]/comments

**Method**: get

**Data**:

* order (optional) - either 'chronological' or 'reverse-chronological'

**Response**:

* data - an array of all the comments found

This one is also a little wonky: Facebook API has this really neat/irritating thing called *paging*. The 'neat' part is you don't have to fetch and process the entire set of comments on something if there are a ton of them. The 'irritating' part is that it takes multiple calls to get every comment if you need all of them. The premise is that it gives you part of the results, then a 'paging' endpoint that you make another call to, to continue getting comments.

So this is how I handle getting every comment every time:

```
let data = { order: "reverse-chronological" };
let endpoint = `${postId}/comments`;

FB.api(endpoint, "get", {...data, limit: 0, summary: "total\_count"}, res0 => {
 if(!res0 || res0.error) {
 // handle the error
 } else {
 let total\_count = res0.summary.total\_count;
 FB.api(endpoint, "get", {...data, limit: total\_count}, resp => {
 // handle it here -- if success, comments are in resp.data
 });
 }
});

```

This uses two API calls: the first one is used to get the total number of comments on the post, photo, or whatever; the second is used to fetch that many comments from the object.

If you know a specific amount of comments you want to grab, say you want the most recent 25 comments, you can just use the inner API call as your model

```
FB.api(endpoint, "get", {limit: 25, order: "reverse-chronological"}, resp => {
 // handle it here
});

```

### Get your photo album ids

**Endpoint**: me/albums

**Method**: get

**Data**: none, use the empty object {}

**Response**:

* data: an array of objects representing albums on your Page.

Each album object in the response contains the following fields:

* created\_time
* name
* id

### Adding a photo to an album

*The Graph API cannot be used to create albums; you'll have to make one manually through the site. Get your album id by using the previous API call*

**Endpoint**: /[album id]/photos

**Method**: post

**Data**:

* source (required): a FileStream for the image you want to upload
* message (optional): a string containing the image caption

**Response**:

* id: the photo id

### Creating a video post

This one works a little differently than the others, in that the API call we make isn't going to use the FB.api() method we've been using. This is because the API call must be made to a URL that is different from the usual one, so we need to make the web request ourselves.

So, we need two node modules called 'axios' and 'form-data' to add this support for making direct web requests. Install them to your project folder by running the command *npm i axios form-data* while inside it.

```
const axios = require("axios");
const FormData = require("form-data");
const fs = require("fs");

let url = `https://graph-video.facebook.com/v4.0/me/videos`;

const fd = new FormData();
fd.append("file", fs.createReadStream("/path/to/video.mp4"));
fd.append("access\_token", "put your page token here");
fd.append("description", "put your video description in here");

// optional: add a title to your video
fd.append("title", "My Great Video");

var config = {
 headers: {
 ...fd.getHeaders()
 }
};

axios.post(url, fd, config).then(resp => {
 // handle the success
 console.log(`Your video ID is ${resp.data.id}`);
}).catch(error => {
 // handle the error
})

```

So what we're doing here is:

* storing the data for our request in a FormData object, including our access token,
* getting the proper HTTP request headers from our FormData object, and
* making the request. Success is handled in the then() callback; failure is handled in the catch() callback.

On success, the video id is returned in **resp.data.id**.

Video comments can be done by following the *Making a comment* section and attaching a video file instead of an image.

### Direct API calls (advanced)

If you want to do something beyond the common usage cases mentioned here, you should have already deduced how to do that by now. Just set the endpoint, method, and data according to your needs.

Check out the Full Graph API documentation for more info on what you can do.