Loading...
Fabrizio Brancati - Blog - Post - SwiftyBot 2 - How to create a Facebook Messenger bot in Swift

SwiftyBot 2 - How to create a Facebook Messenger bot with Swift

07 February 2017 - Reading time: 40 min - Comments


Updates

  • 21 May 2017: Updated for Vapor 2.0







Now SwiftyBot has a new logo, made by Roberto Chiaveri.

This tutorial is to be intended as part 2 of How to create a Telegram bot with Swift.
If you already read the part 1 you can jump to Step 5, otherwise read it all, to configure your server to be Swift and bot ready.

In this tutorial we will create a Facebook Messenger bot, a simple one, that just respond to us with a reversed message and a structured message with Swift.
The result will be a bot like SwiftyBot, you can try it here.
Like previous tutorial, we will start from scratch to get a complete working bot.

We will start by creating the bot source code and then configuring Facebook Messenger Platform to use our bot, you will understand why by reading this tutorial.
Let's begin!


Prerequisites

My VPS is based on OVH with Ubuntu 14.04 and Plesk to manage the environment and my websites.

  • Ubuntu (mine is 14.04) or a macOS computer with Xcode 8.2
  • Facebook account with a Page

Step 0: Connect to your server

If you are using a remote server, we need to connect to it remotely:

                                        ssh user@0.0.0.0
                                    
You have to change "user" and "0.0.0.0" with your username (maybe root) and the IP of your server.

Step 1: Install Swift

If you are on macOS you only need Xcode 8.2.

Otherwise if you are on Ubuntu, we have to install Swift, and we will use swiftenv to do it.

Swift Version Manager (swiftenv) allows you to easily install and switch between multiple versions of Swift.
You can change the global Swift version and set a per-project Swift version.
It uses a .swift-version file in your project folder to identify what Swift version your project needs.


Clone the Git repo:

                                        git clone https://github.com/kylef/swiftenv.git ~/.swiftenv
                                    
You can install it wherever you want. For example on my VPS I've created a "fabrizio" folder where I put all the Swift versions and projects. Change "~" with your favorite folder.

Configure the environment variables and set the autostart:

                                        
                                            echo 'export SWIFTENV_ROOT="$HOME/.swiftenv"' >> ~/.bash_profile
                                            echo 'export PATH="$SWIFTENV_ROOT/bin:$PATH"' >> ~/.bash_profile
                                            echo 'eval "$(swiftenv init -)"' >> ~/.bash_profile
                                        
                                    
Remember to change "$HOME" with your custom folder if you have changed it before.

Maybe after that you will need to disconnect and connect again to your server or even reboot it.
After that we have to install the right version of Swift using swiftenv:

                                        
                                            swiftenv install 3.0.2
                                        
                                    

You can choose to set a global or a per-project version of Swift.
I choose both.
I created a .swift-version file, with the snapshot / preview version of Swift, inside every project folder, and set a global version with:

                                        
                                            swiftenv global 3.0.2
                                        
                                    

The last thing is to check that everything we have made is working:

                                        
                                            swift --version
                                        
                                    

This will print Swift version 3.0.2 if you are on Ubuntu or Apple Swift version 3.0.2 if you are on macOS.

Please note that if you are on Ubuntu 14.04 you may need to update clang.

                                        
                                            sudo apt-get install clang-3.6
                                            sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-3.6 100
                                            sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-3.6 100
                                        
                                    

If you are on macOS you may need to install ctls with brew:

                                        
                                            brew update;
                                            brew tap vapor/homebrew-tap;
                                            brew install ctls;
                                        
                                    

Step 2: Prepare your environment

To build our bot we will use Vapor.

Vapor is a Web Framework for Swift that works on macOS and Ubuntu.
Is the most used web framework for Swift. It provides a beautifully expressive and easy to use foundation for your next website or API.


You can now choose:

  • Step 2.1 You can use Vim, and edit your source files directly on your server.
  • Step 2.2 You can clone my own SwiftyBot repository and get it ready immediately.

Step 2.1: Install Vapor Toolbox

We have to install the Vapor Toolbox to help us create our project. To install it:

                                        
                                            curl -sL toolbox.qutheory.io | bash
                                        
                                    

Go into your favorite folder and create our project by typing:

                                        
                                            vapor new <yourBotName>
                                        
                                    
Replace <yourBotName> with your bot name. Yes, you have to choose a name for your bot now.

Fabrizio Brancati - SwiftyBot Vapor New Terminal Vapor Toolbox has created for us a folder with your chosen name, and has cloned the Vapor basic template repository into your folder.
Last thing, just rename the App folder under Sources folder to your bot name.
You can now jump to Step 3.

Step 2.2: Clone SwiftyBot

We have to clone the SwiftyBot repository.

If you want to use GitHub, just fork it with this button!   

Otherwise go into you favorite folder and type:

                                        git clone https://github.com/FabrizioBrancati/SwiftyBot
                                    

Step 3: Meet Vapor

After Step 2.1 or Step 2.2 your bot folder should look like this: Fabrizio Brancati - SwiftyBot Folders & Files

You can delete Localization, Public and Resources and other files that are not on the image above, because out bot will not use them.

Step 3.1: Config folder

Is this folder you can set your application's configuration settings and custom keys.
More at Vapor - Config Documentation.
Inside Config folder there is a secrets folder.
There you have to create an app.json file, with following code:

                                        
                                            vi app.json
                                        
                                    
To save and exit from vi you can press the esc button and then type :wq.

Now inside this file type:

                                        
                                            {
                                                "secret": "XXXXXXXXXXXXXX",
                                                "token": ""
                                            }
                                        
                                    
Change "XXXXXXXXXXXXXX" with a secret key and leave the value of token blank.

Here we leave the token value blank because we don't have it yet, Facebook will give it to us once verified our webhook.

Inside server.json files (from both development and production folder) we have the server configuration, like host, port and security.

                                        
                                            {
                                                "host": "0.0.0.0",
                                                "port": 1992,
                                                "securityLayer": "none"
                                            }
                                        
                                    
1992 is my birthday year :D

Step 3.2: Package.swift file

The Swift Package Manager is a tool for managing the distribution of Swift code.
It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies.


Here we define our package name and all the bot's dependencies.

                                        
                                            import PackageDescription
                                            
                                            let package = Package(
                                                name: "SwiftyBot",
                                                dependencies: [
                                                    .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 1, minor: 5),
                                                    .Package(url: "https://github.com/FabrizioBrancati/BFKit-Swift.git", majorVersion: 2, minor: 2) // Will help us with development 
                                                ],
                                                exclude: [
                                                    "Config",
                                                    "Database",
                                                    "Localization",
                                                    "Public",
                                                    "Resources",
                                                    "Tests",
                                                ]
                                            )
                                        
                                    
BFKit-Swift is optional, it will help you to develop faster with some useful functions ready to be used. You can remove it if you want.

At line 5 we have the dependencies array where we will put all bot's dependencies.
A target’s dependencies are modules that are required by code in the package.
As you can notice, we have Vapor 1.5.x and BFKit-Swift 2.2.x (The last one is optional, it will help you to develop faster with some useful functions ready to be used).


Step 4: Enable TLS

Please note that if you are on macOS you have to enable TLS/HTTPS by yourself, a self signed certificate is not valid for Facebook Messenger Platform.

To receive messages from Facebook Messenger we need to enable HTTPS for your domain.

In this step, we will use Let's Encrypt to enable TLS on our server with Apache or nginx as a reverse proxy, only using Plesk.
I will show you how to use Plesk since my VPS has it by default, but you can do everything without it.


Fabrizio Brancati - Let's Encrypt Logo

Let’s Encrypt is a free, automated, and open certificate authority (CA), run for the public’s benefit. It is a service provided by the Internet Security Research Group (ISRG).
They give people the digital certificates they need in order to enable HTTPS (SSL/TLS) for websites, for free, in the most user-friendly way they can.
They do this because they want to create a more secure and privacy-respecting Web.



You can find more at Let's Encrypt's website.

Step 4.1: Install Let's Encrypt (also known as Certbot)

To install Let's Encrypt extension you need to open your Plesk panel and go to Extensions > Extensions Catalog.
Search for Let's Encrypt (current version is 1.9) and install it.
Return to Plesk home Websites & Domains and you will now have a Let's Encrypt section under your domain.
Click on it and request a certificate with your email.

Step 4.2: Use Apache / nginx as reverse proxy

You can now choose to use Apache or nginx and if you want to use Plesk or not.
I will now show you how to configure Plesk with Apache and nginx reverse proxy, but you can do it without Plesk, just by editing .conf files.

To enable reverse proxy to our bot, go to Apache & nginx Settings section of your domain.

Step 4.2.1: Apache reverse proxy

In the Additional Apache Directives > Additional directives for HTTPS add this code:

            						    
                                            ProxyPass         /  http://localhost:1992/
                                            ProxyPassReverse  /  http://localhost:1992/
                                            ProxyRequests     Off
                                            ProxyPreserveHost On
                                            <Proxy http://localhost:1992/*>
                                            	Order deny,allow
                                            	Allow from all
                                            </Proxy>
                                        
                                    

Plesk uses Apache with nginx as a reverse proxy, so this directive will work over nginx.
More info here.

Step 4.2.2: Nginx reverse proxy

The following configuration is for nginx:

In the Additional Nginx Directives > Additional Nginx Directives add this code:

            						    
                                            location ~ / {
                                            	proxy_pass http://localhost:1992;
                                            	proxy_set_header Host $host;
                                            	proxy_set_header X-Real-IP $remote_addr;
                                            	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                                            	proxy_connect_timeout 3s;
                                            	proxy_read_timeout 10s;
                                            }
                                        
                                    

Step 5: Create the Facebook Messenger bot

If you are on macOS to edit the bot's source code, you can use Atom or Xcode with the following Terminal line:

                                        
                                            swift package generate-xcodeproj
                                        
                                    

Or with Vapor Toolbox if you have installed it at Step 2.1:

                                        
                                            vapor xcode
                                        
                                    

If you have Models, Middleware and Controllers folders, delete all, because we don't need them in this tutorial.

This is the initial code for our main.swift file:

                                        
                                            /// Import Vapor frameworks.
                                            import Vapor
                                            import HTTP
                                            
                                            /// Bot errors enum.
                                            enum BotError: Swift.Error {
                                                /// Missing Facebook Messenger secret key in Config/secrets/app.json.
                                                case missingAppSecrets
                                                /// Missing URL in Facebook Messenger structured message button.
                                                case missingURL
                                                /// Missing payload in Facebook Messenger structured message button.
                                                case missingPayload
                                            }
                                            
                                            /// Create the Droplet.
                                            let droplet: Droplet = try Droplet()
                                            
                                            /// Read Facebook Messenger secret key from Config/secrets/app.json.
                                            let messengerSecret = droplet.config["app", "messenger", "secret"]?.string ?? ""
                                            let messengerToken = droplet.config["app", "messenger", "token"]?.string ?? ""
                                            
                                            guard messengerSecret != "" && messengerToken != "" else {
                                                /// Show errors in console.
                                                droplet.console.error("Missing secret or token keys!")
                                                droplet.console.error("Add almost one in Config/secrets/app.json")
                                            
                                                /// Throw missing secret key error.
                                                throw BotError.missingAppSecrets
                                            }
                                            
                                            /// Setting up the GET request with Facebook Messenger secret key.
                                            /// With a secret path to be sure that nobody else knows that URL.
                                            /// This is the Step 2 of Facebook Messenger Quick Start guide:
                                            /// https://developers.facebook.com/docs/messenger-platform/guides/quick-start#setup_webhook
                                            droplet.get("messenger", messengerSecret) { request in
                                                /// Check for "hub.mode", "hub.verify_token" & "hub.challenge" query parameters.
                                                guard request.data["hub.mode"]?.string == "subscribe" && request.data["hub.verify_token"]?.string == messengerSecret, let challenge = request.data["hub.challenge"]?.string else {
                                                    throw Abort(.badRequest, reason: "Missing Messenger verification data.")
                                                }
                                                
                                                /// Create a response with the challenge query parameter to verify the webhook.
                                                return Response(status: .ok, headers: ["Content-Type": "text/plain"], body: challenge)
                                            }
                                            
                                            /// Run the Droplet.
                                            try droplet.run()
                                        
                                    

Here we configured a Facebook Messenger secret (line 19), that can be anything you want (I created a random base64 string) and we also configured a Facebook Messenger token (line 20) that we don't have yet.
Facebook will give us the token once it has configured the webhook, but we will see this at Step 6.

We have set a get request to set up the webhook (line 35).
Facebook require it to properly configure and authorize the webhook, he send us a challenge query parameter (line 38) that we have to send back to him (line 43).

Step 5.1: Simple messages

Now we can set up our Messenger structure to better handling its messages and responses.
To handle simple Facebook Messenger messages add this code just before try droplet.run() (line 46):

                                        
                                            /// Struct to help with Facebook Messenger messages.
                                            struct Messenger {
                                                /// Creates a standard Facebook Messenger message.
                                                ///
                                                /// - Parameter message: Message text to be sent.
                                                /// - Returns: Returns the created Node ready to be sent.
                                                static func message(_ message: String) -> Node {
                                                    /// Create the Node.
                                                    return [
                                                        "text": message.makeNode(in: nil)
                                                    ]
                                                }
                                            }
                                        
                                    

Messenger (line 2) struct will help us creating a simple message, it returns a Node instance (line 7) that is part of Vapor.
message(_:) function returns a Node instance (line 7) that is part of Vapor.
With that (and some other code that will follow), we will be able to send simple message with our bot.

Now we can handle the Facebook Messeger POST webhook call, add this code after the previous Facebook Messenger GET request but before the try droplet.run() function call:

                                        
                                            /// Setting up the POST request with Facebook Messenger secret key.
                                            /// With a secret path to be sure that nobody else knows that URL.
                                            /// This is the Step 5 of Facebook Messenger Quick Start guide:
                                            /// https://developers.facebook.com/docs/messenger-platform/guides/quick-start#receive_messages
                                            droplet.post("messenger", messengerSecret) { request in
                                                /// Check that the request comes from a "page".
                                                guard request.json?["object"]?.string == "page" else {
                                                    /// Throw an abort response, with a custom message.
                                                    throw Abort(.badRequest, reason: "Message not generated by a page.")
                                                }
                                                
                                                /// Prepare the response message text.
                                                var response: Node = ["text": "Unknown error."]
                                                /// Prepare the response bytes.
                                                var responseData: Bytes = []
                                                /// Entries from request JSON.
                                                let entries: [JSON] = request.json?["entry"]?.array ?? []
                                                
                                                /// Iterate over all entries.
                                                for entry in entries {
                                                    /// Page ID of the entry.
                                                    let pageID: String = entry.object?["id"]?.string ?? "0"
                                                    /// Messages from entry.
                                                    let messaging: [JSON] = entry.object?["messaging"]?.array ?? []
                                                    
                                                    /// Iterate over all messaging objects.
                                                    for event in messaging {
                                                        /// Message of the event.
                                                        let message: [String: JSON] = event.object?["message"]?.object ?? [:]
                                                        /// Postback of the event.
                                                        let postback: [String: JSON] = event.object?["postback"]?.object ?? [:]
                                                        /// Sender of the event.
                                                        let sender: [String: JSON] = event.object?["sender"]?.object ?? [:]
                                                        /// Sender ID, it is used to make a response to the right user.
                                                        let senderID: String = sender["id"]?.string ?? ""
                                                        /// Text sent to bot.
                                                        let text: String = message["text"]?.string ?? ""
                                                        
                                                        /// Check if is a postback action.
                                                        if !postback.isEmpty {
                                                            /// Get payload from postback.
                                                            let payload: String = postback["payload"]?.string ?? "No payload provided by developer."
                                                            /// Set the response message text.
                                                            response = Messenger.standardMessage(payload)
                                                        /// Check if the message object is empty.
                                                        } else if message.isEmpty {
                                                            /// Set the response message text.
                                                            response = Messenger.standardMessage("Webhook received unknown event.")
                                                        /// Check if the message text is empty
                                                        } else if text.isEmpty {
                                                            /// Set the response message text.
                                                            response = Messenger.standardMessage("I'm sorry but your message is empty 😢")
                                                        /// The user wants to buy something.
                                                        } else {
                                                            /// Set the response message text.
                                                            response = Messenger.standardMessage(text.reversed(preserveFormat: true))
                                                        }
                                                        
                                                        /// Creating the response JSON data bytes.
                                                        /// At Step 6 of Facebook Messenger Quick Start guide, using Node.js demo, they told you to send back the "recipient.id", but the correct one is "sender.id".
                                                        /// https://developers.facebook.com/docs/messenger-platform/guides/quick-start#send_text_message
                                                        responseData = try JSON(["recipient": ["id": senderID.makeNode()], "message": response]).makeBytes()
                                                        
                                                        /// Calling the Facebook API to send the response.
                                                        let facebookAPICall = try droplet.client.post("https://graph.facebook.com/v2.8/me/messages", headers: ["Content-Type": "application/json"], query: ["access_token": messengerToken], body: Body.data(responseData))
                                                    }
                                                }
                                                
                                                /// Sending an HTTP 200 OK response is required.
                                                /// https://developers.facebook.com/docs/messenger-platform/webhook-reference#response
                                                /// The header is added just to mute a Vapor warning.
                                                return Response(status: .ok, headers: ["Content-Type": "application/json"])
                                            }
                                        
                                    

Here we checked that the message comes from a page (line 7), then we iterate over all entries (line 20) because may be batched and they will be multiple.
We then iterate over all messaging objects (line 27) and we extract all the message info.

Then we checked if it is a postback action (line 40), if the message object is empty (line 46) and if the message text is empty (line 50).

After that we need to create the JSON response data (line 62), but we will use the sender.id instead of the recipient.id as the Facebook Messenger Quick Start guide says.
We converted all the JSON to bytes to be able to send with a POST call.

To sending a simple message, we have to call the Facebook API to send him the response (line 65).
We have to call the https://graph.facebook.com/v2.8/me/messages URL with our access token as query parameter and our JSON response as body.

The last thing is to send back an HTTP 200 OK response to Facebook Messenger (line 72), because as they said here, "Failing to do so may cause your webhook to be unsubscribed by the Messenger Platform".

Step 5.2: Structured messages

Structured Messages are templates that support different kinds of use cases.
For example the Button Template allows you to send text and buttons, and the Generic Template allows you to define an image, title, subtitle and buttons.

Structured Messages has some actions like share, buy, call.
We will see the most simple use of it: URL and postback actions, like Step 7 of Facebook Messenger Quick Start guide.

To help us to create a structured message we will add 2 structs: Element and Button.

Every Element is a card that our bot will send to the user.
Every Button is a button inside an Element.
You can see an example at the following image.

Fabrizio Brancati - SwiftyBot - Facebook Messenger Element

Start with Element struct and this code inside Messenger struct:

                                        
                                            /// Facebook Messenger structured message element.
                                            struct Element: NodeRepresentable {
                                                /// Element title.
                                                var title: String
                                                /// Element subtitle.
                                                var subtitle: String
                                                /// Element item URL.
                                                var itemURL: String
                                                /// Element image URL.
                                                var imageURL: String
                                                /// Element Button array.
                                                var buttons: [Button]
                                            }
                                        
                                    

Every Element has a title, subtitle, itemURL, imageURL and buttons.
We have made Element conforming to NodeRepresentable protocol (line 2), but what does it means?
Vapor requires a Node to create a JSON, by conforming our Element struct to NodeRepresentable protocol, we are telling to Vapor that he can convert our struct to a Node.

To specify to Vapor how to convert our Element to a Node, NodeRepresentable protocol require a makeNode(context:) function (line 6) to be added to our Element struct:

                                        
                                            /// Element conforms to NodeRepresentable, turn the convertible into a node.
                                            ///
                                            /// - Parameter context: Context beyond Node.
                                            /// - Returns: Returns the Element Node representation.
                                            /// - Throws: Throws NodeError errors.
                                            public func makeNode(in context: Context?) throws -> Node {
                                                /// Create the Node.
                                                return try Node(node: [
                                                    "title": title.makeNode(in: nil),
                                                    "subtitle": subtitle.makeNode(in: nil),
                                                    "item_url": itemURL.makeNode(in: nil),
                                                    "image_url": imageURL.makeNode(in: nil),
                                                    "buttons": buttons.makeNode(in: nil)
                                                    ]
                                                )
                                            }
                                        
                                    

You can notice that we have created an array of Buttons inside the Element struct and inside the makeNode(context:) function we have buttons.makeNode() (line 13), that inform Vapor how to create it as a Node.

Now we have to create the Button struct inside the Element struct:

                                        
                                            /// Button of Facebook Messenger structured message element.
                                            struct Button: NodeRepresentable {
                                                /// Button type of Facebook Messenger structured message element.
                                                ///
                                                /// - webURL: Web URL type.
                                                /// - postback: Postback type.
                                                enum `Type`: String {
                                                    case webURL = "web_url"
                                                    case postback = "postback"
                                                }
                                                
                                                /// Set all its property to get only.
                                                /// https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AccessControl.html#//apple_ref/doc/uid/TP40014097-CH41-ID18
                                                /// Button type.
                                                private(set) var type: Type
                                                /// Button title.
                                                private(set) var title: String
                                                /// Button payload, postback type only.
                                                private(set) var payload: String?
                                                /// Button URL, webURL type only.
                                                private(set) var url: String?
                                            }
                                        
                                    

Here we have created a Button struct with type, title, payload and webURL.
Every variable is declared read-only (private(set) at line 15 - 17 - 19 - 21) because we want to create every button with its init function.
We have also created a Type enum to describe button type: webURL or postback (line 8 - 9).

Next step is to add the init function to the Button struct:

                                        
                                            /// Creates a Button for Facebook Messenger structured message element.
                                            ///
                                            /// - Parameters:
                                            ///   - type: Button type.
                                            ///   - title: Button title.
                                            ///   - payload: Button payload.
                                            ///   - url: Button URL.
                                            /// - Throws: Throws NodeError errors.
                                            init(type: Type, title: String, payload: String? = nil, url: String? = nil) throws {
                                                /// Set Button type.
                                                self.type = type
                                                /// Set Button title.
                                                self.title = title
                                                
                                                /// Check what Button type is.
                                                switch type {
                                                /// Is a webURL type, so se its url.
                                                case .webURL:
                                                    /// Check if url is nil.
                                                    guard let url = url else {
                                                        throw BotError.missingURL
                                                    }
                                                    self.url = url
                                                /// Is a postback type, so se its payload.
                                                case .postback:
                                                    /// Check if payload is nil.
                                                    guard let payload = payload else {
                                                        throw BotError.missingPayload
                                                    }
                                                    self.payload = payload
                                                }
                                            }
                                        
                                    

With this function we can create a Button by defining its type, title, payload or url.

Since we have declared Button as NodeRepresentable (like Element) we have to add the makeNode(context:) function:

                                        
                                            /// Button conforms to NodeRepresentable, turn the convertible into a node.
                                            ///
                                            /// - Parameter context: Context beyond Node.
                                            /// - Returns: Returns the Button Node representation.
                                            /// - Throws: Throws NodeError errors.
                                            public func makeNode(in context: Context?) throws -> Node {
                                                /// Create the Node with type and title.
                                                var node: Node = [
                                                    "type": type.rawValue.makeNode(in: nil),
                                                    "title": title.makeNode(in: nil)
                                                ]
                                                
                                                /// Extends the Node with url or payload, depends on Button type.
                                                switch type {
                                                /// Extends with url property.
                                                case .webURL:
                                                    node["url"] = url?.makeNode(in: nil) ?? ""
                                                /// Extends with payload property.
                                                case .postback:
                                                    node["payload"] = payload?.makeNode(in: nil) ?? ""
                                                }
                                                
                                                /// Create the Node.
                                                return try Node(node: node)
                                            }
                                        
                                    

Not much to explain here, as for Element we have specified to Vapor how to convert our struct to a Node.

Now we can create structured messages, let's do this by adding another function to our Messenger struct:

                                        
                                            /// Creates a structured Facebook Messenger message.
                                            ///
                                            /// - Parameter elements: Elements of the structured message.
                                            /// - Returns: Returns the representable Node.
                                            /// - Throws: Throws NodeError errors.
                                            static func structuredMessage(elements: [Element]) throws -> Node {
                                                /// Create the Node.
                                                return try ["attachment":
                                                    ["type": "template",
                                                     "payload":
                                                        ["template_type": "generic",
                                                         "elements": elements.makeNode(in: nil)
                                                        ]
                                                    ]
                                                ]
                                            }
                                        
                                    

This function can throws because of Element struct, it is a NodeRepresentable so it must have makeNode(context:) function that may throws an error when a new Node is created.

The last step is to make our bot answers with a structured message, let's do it by replacing our droplet.post("messenger", messengerSecret) function:

                                        
                                            /// Setting up the POST request with Facebook Messenger secret key.
                                            /// With a secret path to be sure that nobody else knows that URL.
                                            /// This is the Step 5 of Facebook Messenger Quick Start guide:
                                            /// https://developers.facebook.com/docs/messenger-platform/guides/quick-start#receive_messages
                                            droplet.post("messenger", messengerSecret) { request in
                                                /// Check that the request comes from a "page".
                                                guard request.json?["object"]?.string == "page" else {
                                                    /// Throw an abort response, with a custom message.
                                                    throw Abort(.badRequest, reason: "Message not generated by a page.")
                                                }
                                                
                                                /// Prepare the response message text.
                                                var response: Node = ["text": "Unknown error."]
                                                /// Prepare the response bytes.
                                                var responseData: Bytes = []
                                                /// Entries from request JSON.
                                                let entries: [JSON] = request.json?["entry"]?.array ?? []
                                                
                                                /// Iterate over all entries.
                                                for entry in entries {
                                                    /// Page ID of the entry.
                                                    let pageID: String = entry.object?["id"]?.string ?? "0"
                                                    /// Messages from entry.
                                                    let messaging: [JSON] = entry.object?["messaging"]?.array ?? []
                                                    
                                                    /// Iterate over all messaging objects.
                                                    for event in messaging {
                                                        /// Message of the event.
                                                        let message: [String: JSON] = event.object?["message"]?.object ?? [:]
                                                        /// Postback of the event.
                                                        let postback: [String: JSON] = event.object?["postback"]?.object ?? [:]
                                                        /// Sender of the event.
                                                        let sender: [String: JSON] = event.object?["sender"]?.object ?? [:]
                                                        /// Sender ID, it is used to make a response to the right user.
                                                        let senderID: String = sender["id"]?.string ?? ""
                                                        /// Text sent to bot.
                                                        let text: String = message["text"]?.string ?? ""
                                                        
                                                        /// Check if is a postback action.
                                                        if !postback.isEmpty {
                                                            /// Get payload from postback.
                                                            let payload: String = postback["payload"]?.string ?? "No payload provided by developer."
                                                            /// Set the response message text.
                                                            response = Messenger.message(payload)
                                                        /// Check if the message object is empty.
                                                        } else if message.isEmpty {
                                                            /// Set the response message text.
                                                            response = Messenger.message("Webhook received unknown event.")
                                                        /// Check if the message text is empty
                                                        } else if text.isEmpty {
                                                            /// Set the response message text.
                                                            response = Messenger.message("I'm sorry but your message is empty 😢")
                                                        /// The user wants to buy something.
                                                        } else if text.lowercased().range(of: "sell") || text.lowercased().range(of: "buy") || text.lowercased().range(of: "shop") {
                                                            do {
                                                                /// Create all the elements in elements object of the Facebook Messenger structured message.
                                                                /// First Element: BFKit-Swift
                                                                let BFKitSwift = try Messenger.Element(title: "BFKit-Swift", subtitle: "BFKit-Swift is a collection of useful classes, structs and extensions to develop Apps faster.", itemURL: "https://github.com/FabrizioBrancati/BFKit-Swift", imageURL: "https://github.fabriziobrancati.com/bfkit/resources/banner-swift.png", buttons: [
                                                                    Messenger.Element.Button(type: .webURL, title: "Open in GitHub", url: "https://github.com/FabrizioBrancati/BFKit-Swift"),
                                                                    Messenger.Element.Button(type: .postback, title: "Call Postback", payload: "BFKit-Swift payload.")])
                                                                /// Second Element: BFKit
                                                                let BFKit = try Messenger.Element(title: "BFKit", subtitle: "BFKit is a collection of useful classes and categories to develop Apps faster.", itemURL: "https://github.com/FabrizioBrancati/BFKit", imageURL: "https://github.fabriziobrancati.com/bfkit/resources/banner-objc.png", buttons: [
                                                                    Messenger.Element.Button(type: .webURL, title: "Open in GitHub", url: "https://github.com/FabrizioBrancati/BFKit"),
                                                                    Messenger.Element.Button(type: .postback, title: "Call Postback", payload: "BFKit payload.")])
                                                                /// Third Element: SwiftyBot
                                                                let SwiftyBot = try Messenger.Element(title: "SwiftyBot", subtitle: "How to create a Telegram & Facebook Messenger bot with Swift using Vapor on Ubuntu / macOS", itemURL: "https://github.com/FabrizioBrancati/SwiftyBot", imageURL: "https://github.fabriziobrancati.com/swiftybot/resources/swiftybot-banner.png", buttons: [
                                                                    Messenger.Element.Button(type: .webURL, title: "Open in GitHub", url: "https://github.com/FabrizioBrancati/SwiftyBot"),
                                                                    Messenger.Element.Button(type: .postback, title: "Call Postback", payload: "SwiftyBot payload.")])
                                                                
                                                                /// Create the elements array.
                                                                var elements: [Messenger.Element] = []
                                                                /// Add BFKit-Swift to elements array.
                                                                elements.append(BFKitSwift)
                                                                /// Add BFKit to elements array.
                                                                elements.append(BFKit)
                                                                /// Add SwiftyBot to elements array.
                                                                elements.append(SwiftyBot)
                                                            
                                                                /// Create a structured message to sell something to the user.
                                                                response = try Messenger.structuredMessage(elements: elements)
                                                            } catch {
                                                                /// Throw an abort response, with a custom message.
                                                                throw Abort(.badRequest, reason: "Error while creating elements.")
                                                            }
                                                        /// The message object and its text are not empty, and the user does not want to buy anything, so create a reversed message text.
                                                        } else {
                                                            /// Set the response message text.
                                                            response = Messenger.message(text.reversed(preserveFormat: true))
                                                        }
                                                        
                                                        /// Creating the response JSON data bytes.
                                                        /// At Step 6 of Facebook Messenger Quick Start guide, using Node.js demo, they told you to send back the "recipient.id", but the correct one is "sender.id".
                                                        /// https://developers.facebook.com/docs/messenger-platform/guides/quick-start#send_text_message
                                                        var responseData: JSON = JSON()
                                                        
                                                        try responseData.set("recipient", ["id": senderID])
                                                        try responseData.set("message", response)
                                                        
                                                        /// Calling the Facebook API to send the response.
                                                        let facebookAPICall: Response = try droplet.client.post("https://graph.facebook.com/v2.8/me/messages", query: ["access_token": messengerToken], ["Content-Type": "application/json"], Body.data(responseData.makeBytes()))
                                                    }
                                                }
                                                
                                                /// Sending an HTTP 200 OK response is required.
                                                /// https://developers.facebook.com/docs/messenger-platform/webhook-reference#response
                                                /// The header is added just to mute a Vapor warning.
                                                return Response(status: .ok, headers: ["Content-Type": "application/json"])
                                            }
                                        
                                    

Here we have added a check on payback in postback response, we have also created 3 Elements, each with 2 Buttons.
We have added this Elements to our request to Facebook Messenger.

This is all bot's source code.
May be not the latest version of bot's code, because SwiftyBot code continuously evolves.

You can check 2.0.0 tag version for code used in this tutorial.   

Now you just have to build and serve our bot. Type in shell:

                                        
                                            swift build --configuration release
                                            ./.build/release/<your-bot-name> serve --env=production
                                        
                                    
You can write swift build --configuration release in its shot form: swift build -c release

Step 6: Set up Facebook

For this step you need a Facebook account, go here if you don't have one.
Now go to Facebook Quick Start Guide and follow it from Step 1 to Step 4.
We have already prepared our webhook call, to be Facebook Messenger ready!

When you are at Step 3, of Facebook Quick Start Guide, remember to copy your token and to put it into our Config/secrets/app.json file.
If you don't remember this Step, go back to Step 3.


Step 7: Set Supervisor

At this point we have almost finish with the bot.
But there is one more thing.
What if your server shut down or your bot crashes?
Obviously your bot will stop respond to your messages, and you will need to access to your server to make it restart.
Supervisor can do it for you!

Supervisor is a client / server system that allows its users to monitor and control a number of processes on UNIX-like operating systems.
In other words, will ensure that your process (the bot in this case) is always running, even if the server reboot or the process crashes.

Now we have to install it.

On Ubuntu:

            						    
                                            apt-get install supervisor
                                        
                                    

On macOS:

            						    
                                            brew install distribute
                                            sudo easy_install pip
                                            sudo pip install supervisor
                                        
                                    
If you have not brew installed, check it here.

Now we have to set Supervisor for our bot.
We have to create the configuration file where we will set how to start, and restart, our bot.

On Ubuntu create a file at /etc/supervisor/conf.d/<your-bot-name>.conf with vi:

            						    
                                            vi <your-bot-name>.conf
                                        
                                    

This will create the configuration file on your server.
Now in this file type:

            						    
            						        [program:<your-bot-name>]
            						        command=/<your-bot-route>/<your-bot-folder>/.build/release/<your-bot-name> serve --env=production
            						        directory=/<your-bot-route>/<your-bot-folder>
            						        user=www-data
            						        autostart=true
            						        autorestart=true
                                        
                                    
To save and exit from vi you can press the esc button and then type :wq.

The Supervisor folder may be different, you can find the includes path at /etc/supervisor/supervisor.conf.

Now we have to inform Supervisor about our bot.
To enter the Supervisor UI type:

            						    
            						        supervisorctl
                                        
                                    

Then type the following commands:

            						    
            						        reread
            						        add <your-bot-name>
            						        start <your-bot-name>
                                        
                                    
Same name of your .conf file and program name inside .conf file

If you need to restart your bot type:

            						    
            						        restart <your-bot-name>
                                        
                                    

Bonus: Advanced remote debugging

Sometimes you may need to debug your bot, but you don't have your computer and you can connect only remotely to your server or maybe, your bot crashes only on Linux.
First thing to do is to build our bot in debug mode:

            						    
            						        swift build -c debug
                                        
                                    

Then enter into your bot .build folder and then into debug folder, LLDB can help us:

            						    
            						        lldb
            						        target create <your-bot-name>
            						        run <your-bot-name>
                                        
                                    

Now you can run your bot and when it crashes you can get the backtrace just with:

            						    
            						        bt
                                        
                                    

Now you will be able to see which line of code causes your crash!

Sometimes see when your bot crashes may not be useful, so let's set a breakpoint after previous target create command:

            						    
            						        breakpoint set --file main.swift --line 49
                                        
                                    
You can write it as breakpoint set -f main.swift -l 49

Then you can run our bot with LLDB and see its code stops at line 49.

At this point you can print a variable with:

            						    
            						        print <variable-name>
                                        
                                    
You can write it as p <variable-name>

Or execute a line with:

            						    
            						        e <things-to-be-executed>
                                        
                                    
You can write it as e <things-to-be-executed>

For example you can run e "test".reversed() and it will produce (String) $R1 = "tset".
But what is this $R1?
LLDB has created for us an alias to this instruction, so if you write:

            						    
            						        e $R1.reversed()
                                        
                                    

The out will be the original string (String) $R2 = "test", but with a different alias.
LLDB will create for us an alias for every line that we will execute with it.

If you want to continue with the execution of our bot you can write:

            						    
                                            thread continue   # Continue executing the program, until next breakpoint, if exist
                                            thread step-in    # Enter into the function
                                            thread step-over  # Go to next line
                                            thread step-out   # Exit from the function
                                        
                                    

At last, sometimes your bot process may be persistent and it doesn't want to kill himself.
So you may need a task manager, I recommend htop.
You can install it on Ubuntu with:

            						    
            						        apt-get install htop
                                        
                                    

What's next?

That's all folks!

If you want to see full source code, you can check it here.

You have now a complete working bot made with Swift!
Now you can expand it with some other features or completely change it with whatever you want.

If you want you can try talking with SwiftyBot just by searching for it on Facebook or click here!

You can even talk to it with Telegram just by searching for it on Telegram or click here.

I will update my SwiftyBot with some other features, just remember to check future updates on GitHub repository!




Fabrizio Brancati - SwiftyBot 2 Icon




Websites you should check:




Did you like it?
Would you like more tutorials like this?
Feel free to leave a comment about this tutorial!





comments powered by Disqus

Interested in working with me?

Feel free to write me for any kind of information!

Send