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
- 13 May 2018: Added
messaging_type: "RESPONSE"
to Facebook Messenger response
Contents
- Introduction
- Prerequisites
- Step 1: Install Swift
- Step 2: Prepare your environment
- Step 3: Meet Vapor
- Step 4: Enable TLS
- Step 5: Create a Facebook Messenger bot
- Step 6: Set up Facebook
- Step 7: Set Supervisor
- Bonus: Advanced remote debugging
- What's next?
All bot's source code is available here on GitHub!
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 [email protected]
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
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
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>
<yourBotName>
with your bot name. Yes, you have to choose a name for your bot now.
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:
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
esc
button and then type :wq
.Now inside this file type:
{
"secret": "XXXXXXXXXXXXXX",
"token": ""
}
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"
}
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",
]
)
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.
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.
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 Button
s 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("messaging_type", "RESPONSE")
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 Element
s, each with 2 Button
s.
We have added this Element
s 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
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
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
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>
program
name inside .conf fileIf 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
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>
p <variable-name>
Or execute a line with:
e <things-to-be-executed>
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!
Websites you should check:
- Swift website
- Swift Version Manager repository
- Swift Package Manifest File Format Reference
- Vapor repository
- Let's Encrypt website
- Facebook Messenger documentation
- Supervisor documentation
- LLDB Tutorial
- Roberto Chiaveri's website
Did you like it?
Would you like more tutorials like this?
Feel free to leave a comment about this tutorial!