A Java Framework for Building Bots on Facebook's Messenger Platform.
To add a dependency using Maven, use the following:
<dependency>
<groupId>com.clivern</groupId>
<artifactId>racter</artifactId>
<version>1.0.5</version>
</dependency>
To add a dependency using Gradle, use the following:
dependencies {
compile 'com.clivern:racter:1.0.5'
}
To add a dependency using Scala SBT, use the following:
libraryDependencies += "com.clivern" % "racter" % "1.0.5"
After adding the package as a dependency, Please read the following steps:
In order to cofigure the package create config.properties
file with the following data
app_id=App ID Goes Here
verify_token=Verify Token Goes Here
page_access_token=Page Access Token Goes Here
logging_level=debug
logging_file_path=src/main/java/resources/
logging_file_format=current_date
logging_log_type=file
logging_current_date_format=yyyy-MM-dd
logging_append=true
logging_buffered=false
Then import all required classes
import com.clivern.racter.BotPlatform;
import com.clivern.racter.receivers.*;
import com.clivern.racter.receivers.webhook.*;
import com.clivern.racter.senders.*;
import com.clivern.racter.senders.templates.*;
import java.util.HashMap;
import java.util.Map;
import java.io.IOException;
then pass the config.properties
file to the bot platform instance
BotPlatform platform = new BotPlatform("config.properties");
or Configure it manually
Map<String, String> options = new HashMap<String, String>();
options.put("app_id", "App ID Goes Here");
options.put("verify_token", "Verify Token Goes Here");
options.put("page_access_token", "Page Access Token Goes Here");
options.put("logging_level", "tarce or debug or info or warning or error");
options.put("logging_file_path", "src/main/java/resources/");
options.put("logging_file_format", "current_date or app");
options.put("logging_log_type", "file or console or both");
options.put("logging_current_date_format", "yyyy-MM-dd");
options.put("logging_append", "true or false");
options.put("logging_buffered", "true or false");
BotPlatform platform = new BotPlatform(options);
Create a route to verify your verify token, Facebook will perform a GET request to this route URL with some URL parameters to make sure that verify token is correct.
BotPlatform platform = new BotPlatform("config.properties");
String hubMode = // Get hub.mode query parameter value from the current URL
String hubVerifyToken = // Get hub.verify_token query parameter value from the current URL
String hubChallenge = // Get hub.challenge query parameter value from the current URL
platform.getVerifyWebhook().setHubMode(hubMode);
platform.getVerifyWebhook().setHubVerifyToken(hubVerifyToken);
platform.getVerifyWebhook().setHubChallenge(hubChallenge);
if( platform.getVerifyWebhook().challenge() ){
// Set Response to be hubChallenge value and status code is 200 like
// response.status(200);
// return ( hubChallenge != null ) ? hubChallenge : "";
}
// Set Response to be 'Verification token mismatch' and status code is 403 like
// response.status(403);
// return "Verification token mismatch";
So let's say we use Spark Java Framework for our bot, Our route and callback will look like the following:
import static spark.Spark.*;
import com.clivern.racter.BotPlatform;
import com.clivern.racter.receivers.*;
import com.clivern.racter.receivers.webhook.*;
import com.clivern.racter.senders.*;
import com.clivern.racter.senders.templates.*;
import java.util.HashMap;
import java.util.Map;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException
{
// Verify Token Route
get("/", (request, response) -> {
BotPlatform platform = new BotPlatform("config.properties");
platform.getVerifyWebhook().setHubMode(( request.queryParams("hub.mode") != null ) ? request.queryParams("hub.mode") : "");
platform.getVerifyWebhook().setHubVerifyToken(( request.queryParams("hub.verify_token") != null ) ? request.queryParams("hub.verify_token") : "");
platform.getVerifyWebhook().setHubChallenge(( request.queryParams("hub.challenge") != null ) ? request.queryParams("hub.challenge") : "");
if( platform.getVerifyWebhook().challenge() ){
response.status(200);
return ( request.queryParams("hub.challenge") != null ) ? request.queryParams("hub.challenge") : "";
}
response.status(403);
return "Verification token mismatch";
});
}
}
Also if we use Spring Boot Framework for our bot, Our route and callback will look like the following:
package com.racter.example;
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;
import com.clivern.racter.BotPlatform;
import com.clivern.racter.receivers.webhook.*;
import com.clivern.racter.senders.*;
import com.clivern.racter.senders.templates.*;
import java.util.HashMap;
import java.util.Map;
import java.io.IOException;
import com.mashape.unirest.http.exceptions.UnirestException;
@Controller
@EnableAutoConfiguration
public class Main {
@RequestMapping(method = RequestMethod.GET, value = "/")
@ResponseBody
String verifyToken(@RequestParam(value="hub.mode", defaultValue="") String hub_mode, @RequestParam(value="hub.verify_token", defaultValue="") String hub_verify_token, @RequestParam(value="hub.challenge", defaultValue="") String hub_challenge ) throws IOException {
BotPlatform platform = new BotPlatform("src/main/java/resources/config.properties");
platform.getVerifyWebhook().setHubMode(hub_mode);
platform.getVerifyWebhook().setHubVerifyToken(hub_verify_token);
platform.getVerifyWebhook().setHubChallenge(hub_challenge);
if( platform.getVerifyWebhook().challenge() ){
return ( hub_challenge != "" ) ? hub_challenge : "";
}
return "Verification token mismatch";
}
}
Also if we use Play Framework for our bot, Our route and callback will look like the following:
package controllers;
import play.mvc.*;
import play.mvc.Http.RequestBody;
import com.clivern.racter.BotPlatform;
import com.clivern.racter.receivers.webhook.*;
import com.clivern.racter.senders.*;
import com.clivern.racter.senders.templates.*;
import java.util.HashMap;
import java.util.Map;
import java.io.IOException;
import com.mashape.unirest.http.exceptions.UnirestException;
/**
* Home Controller
*/
public class HomeController extends Controller {
protected String basePath = System.getProperty("user.dir");
public Result verifyToken() throws IOException
{
BotPlatform platform = new BotPlatform(this.basePath + "/conf/racter.properties");
platform.getVerifyWebhook().setHubMode(request().getQueryString("hub.mode"));
platform.getVerifyWebhook().setHubVerifyToken(request().getQueryString("hub.verify_token"));
platform.getVerifyWebhook().setHubChallenge(request().getQueryString("hub.challenge"));
if( platform.getVerifyWebhook().challenge() ){
return ( request().getQueryString("hub.challenge") != null ) ? ok(request().getQueryString("hub.challenge")) : ok();
}
return ok("Verification token mismatch");
}
}
In order to receive and parse messages, You will need to create another route that receives post requests from Facebook. Our Route should contain a code look like the following:
String body = // Get current Request Body
BotPlatform platform = new BotPlatform("config.properties");
platform.getBaseReceiver().set(body).parse();
HashMap<String, MessageReceivedWebhook> messages = (HashMap<String, MessageReceivedWebhook>) platform.getBaseReceiver().getMessages();
for (MessageReceivedWebhook message : messages.values()) {
String user_id = (message.hasUserId()) ? message.getUserId() : "";
String page_id = (message.hasPageId()) ? message.getPageId() : "";
String message_id = (message.hasMessageId()) ? message.getMessageId() : "";
String message_text = (message.hasMessageText()) ? message.getMessageText() : "";
String quick_reply_payload = (message.hasQuickReplyPayload()) ? message.getQuickReplyPayload() : "";
Long timestamp = (message.hasTimestamp()) ? message.getTimestamp() : 0;
HashMap<String, String> attachments = (message.hasAttachment()) ? (HashMap<String, String>) message.getAttachment() : new HashMap<String, String>();
}
So let's say we use Spark Java Framework for our bot, Our route should look like the following:
import static spark.Spark.*;
import org.pmw.tinylog.Logger;
import com.clivern.racter.BotPlatform;
import com.clivern.racter.receivers.*;
import com.clivern.racter.receivers.webhook.*;
import com.clivern.racter.senders.*;
import com.clivern.racter.senders.templates.*;
import java.util.HashMap;
import java.util.Map;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException
{
// Verify Token Route
get("/", (request, response) -> {
BotPlatform platform = new BotPlatform("config.properties");
platform.getVerifyWebhook().setHubMode(( request.queryParams("hub.mode") != null ) ? request.queryParams("hub.mode") : "");
platform.getVerifyWebhook().setHubVerifyToken(( request.queryParams("hub.verify_token") != null ) ? request.queryParams("hub.verify_token") : "");
platform.getVerifyWebhook().setHubChallenge(( request.queryParams("hub.challenge") != null ) ? request.queryParams("hub.challenge") : "");
if( platform.getVerifyWebhook().challenge() ){
response.status(200);
return ( request.queryParams("hub.challenge") != null ) ? request.queryParams("hub.challenge") : "";
}
response.status(403);
return "Verification token mismatch";
});
post("/", (request, response) -> {
String body = request.body();
BotPlatform platform = new BotPlatform("config.properties");
platform.getBaseReceiver().set(body).parse();
HashMap<String, MessageReceivedWebhook> messages = (HashMap<String, MessageReceivedWebhook>) platform.getBaseReceiver().getMessages();
for (MessageReceivedWebhook message : messages.values()) {
String user_id = (message.hasUserId()) ? message.getUserId() : "";
String page_id = (message.hasPageId()) ? message.getPageId() : "";
String message_id = (message.hasMessageId()) ? message.getMessageId() : "";
String message_text = (message.hasMessageText()) ? message.getMessageText() : "";
String quick_reply_payload = (message.hasQuickReplyPayload()) ? message.getQuickReplyPayload() : "";
Long timestamp = (message.hasTimestamp()) ? message.getTimestamp() : 0;
HashMap<String, String> attachments = (message.hasAttachment()) ? (HashMap<String, String>) message.getAttachment() : new HashMap<String, String>();
// Use Logger To Log Incoming Data
Logger.info("User ID#:" + user_id);
Logger.info("Page ID#:" + page_id);
Logger.info("Message ID#:" + message_id);
Logger.info("Message Text#:" + message_text);
Logger.info("Quick Reply Payload#:" + quick_reply_payload);
for (String attachment : attachments.values()) {
Logger.info("Attachment#:" + attachment);
}
return "ok";
}
// ..
// Other Receive Webhooks Goes Here
// ..
return "No Messages";
});
}
}
Also if we use Spring Boot Framework for our bot, Our route and callback will look like the following:
package com.racter.example;
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;
import org.pmw.tinylog.Logger;
import com.clivern.racter.BotPlatform;
import com.clivern.racter.receivers.webhook.*;
import com.clivern.racter.senders.*;
import com.clivern.racter.senders.templates.*;
import java.util.HashMap;
import java.util.Map;
import java.io.IOException;
import com.mashape.unirest.http.exceptions.UnirestException;
@Controller
@EnableAutoConfiguration
public class Main {
@RequestMapping(method = RequestMethod.GET, value = "/")
@ResponseBody
String verifyToken(@RequestParam(value="hub.mode", defaultValue="") String hub_mode, @RequestParam(value="hub.verify_token", defaultValue="") String hub_verify_token, @RequestParam(value="hub.challenge", defaultValue="") String hub_challenge ) throws IOException {
BotPlatform platform = new BotPlatform("src/main/java/resources/config.properties");
platform.getVerifyWebhook().setHubMode(hub_mode);
platform.getVerifyWebhook().setHubVerifyToken(hub_verify_token);
platform.getVerifyWebhook().setHubChallenge(hub_challenge);
if( platform.getVerifyWebhook().challenge() ){
return ( hub_challenge != "" ) ? hub_challenge : "";
}
return "Verification token mismatch";
}
@RequestMapping(method = RequestMethod.POST, value = "/")
@ResponseBody
String webHook(@RequestBody String body) throws IOException, UnirestException {
BotPlatform platform = new BotPlatform("config.properties");
platform.getBaseReceiver().set(body).parse();
HashMap<String, MessageReceivedWebhook> messages = (HashMap<String, MessageReceivedWebhook>) platform.getBaseReceiver().getMessages();
for (MessageReceivedWebhook message : messages.values()) {
String user_id = (message.hasUserId()) ? message.getUserId() : "";
String page_id = (message.hasPageId()) ? message.getPageId() : "";
String message_id = (message.hasMessageId()) ? message.getMessageId() : "";
String message_text = (message.hasMessageText()) ? message.getMessageText() : "";
String quick_reply_payload = (message.hasQuickReplyPayload()) ? message.getQuickReplyPayload() : "";
Long timestamp = (message.hasTimestamp()) ? message.getTimestamp() : 0;
HashMap<String, String> attachments = (message.hasAttachment()) ? (HashMap<String, String>) message.getAttachment() : new HashMap<String, String>();
// Use Logger To Log Incoming Data
Logger.info("User ID#:" + user_id);
Logger.info("Page ID#:" + page_id);
Logger.info("Message ID#:" + message_id);
Logger.info("Message Text#:" + message_text);
Logger.info("Quick Reply Payload#:" + quick_reply_payload);
for (String attachment : attachments.values()) {
Logger.info("Attachment#:" + attachment);
}
return "ok";
}
// ..
// Other Receive Webhooks Goes Here
// ..
return "No Messages";
}
}
Also if we use Play Framework for our bot, Our route and callback will look like the following:
package controllers;
import play.mvc.*;
import play.mvc.Http.RequestBody;
import org.pmw.tinylog.Logger;
import com.clivern.racter.BotPlatform;
import com.clivern.racter.receivers.webhook.*;
import com.clivern.racter.senders.*;
import com.clivern.racter.senders.templates.*;
import java.util.HashMap;
import java.util.Map;
import java.io.IOException;
import com.mashape.unirest.http.exceptions.UnirestException;
/**
* Home Controller
*/
public class HomeController extends Controller {
protected String basePath = System.getProperty("user.dir");
public Result verifyToken() throws IOException
{
BotPlatform platform = new BotPlatform(this.basePath + "/conf/racter.properties");
platform.getVerifyWebhook().setHubMode(request().getQueryString("hub.mode"));
platform.getVerifyWebhook().setHubVerifyToken(request().getQueryString("hub.verify_token"));
platform.getVerifyWebhook().setHubChallenge(request().getQueryString("hub.challenge"));
if( platform.getVerifyWebhook().challenge() ){
return ( request().getQueryString("hub.challenge") != null ) ? ok(request().getQueryString("hub.challenge")) : ok();
}
return ok("Verification token mismatch");
}
public Result webHook() throws IOException, UnirestException
{
String body = request().body().asJson().toString();
BotPlatform platform = new BotPlatform(this.basePath + "/conf/racter.properties");
platform.getBaseReceiver().set(body.asText()).parse();
HashMap<String, MessageReceivedWebhook> messages = (HashMap<String, MessageReceivedWebhook>) platform.getBaseReceiver().getMessages();
for (MessageReceivedWebhook message : messages.values()) {
String user_id = (message.hasUserId()) ? message.getUserId() : "";
String page_id = (message.hasPageId()) ? message.getPageId() : "";
String message_id = (message.hasMessageId()) ? message.getMessageId() : "";
String message_text = (message.hasMessageText()) ? message.getMessageText() : "";
String quick_reply_payload = (message.hasQuickReplyPayload()) ? message.getQuickReplyPayload() : "";
Long timestamp = (message.hasTimestamp()) ? message.getTimestamp() : 0;
HashMap<String, String> attachments = (message.hasAttachment()) ? (HashMap<String, String>) message.getAttachment() : new HashMap<String, String>();
Logger.info("User ID#:" + user_id);
Logger.info("Page ID#:" + page_id);
Logger.info("Message ID#:" + message_id);
Logger.info("Message Text#:" + message_text);
Logger.info("Quick Reply Payload#:" + quick_reply_payload);
for (String attachment : attachments.values()) {
Logger.info("Attachment#:" + attachment);
}
return ok("ok");
}
// ..
// Other Receive Webhooks Goes Here
// ..
return ok("No Messages");
}
}
Let's create an empty message first and fill it with the required data. We can get a new message container from Bot Platform Instance:
MessageTemplate message_tpl = platform.getBaseSender().getMessageTemplate();
// Let's start to fill the required data here
// ..
Here's some of the usage cases:
// get the message.getUserId() from the message received
// To send a seen mark
message_tpl.setRecipientId(message.getUserId());
message_tpl.setSenderAction("mark_seen");
platform.getBaseSender().send(message_tpl);
// To send a typing on
message_tpl.setRecipientId(message.getUserId());
message_tpl.setSenderAction("typing_on");
platform.getBaseSender().send(message_tpl);
// To send a typing off
message_tpl.setRecipientId(message.getUserId());
message_tpl.setSenderAction("typing_off");
platform.getBaseSender().send(message_tpl);
// To send text message
message_tpl.setRecipientId(message.getUserId());
message_tpl.setMessageText("Hello World");
message_tpl.setNotificationType("REGULAR");
platform.getBaseSender().send(message_tpl);
// To send an image
message_tpl.setRecipientId(message.getUserId());
message_tpl.setAttachment("image", "http://techslides.com/demos/samples/sample.jpg", false);
message_tpl.setNotificationType("SILENT_PUSH");
platform.getBaseSender().send(message_tpl);
// To send file attachment
message_tpl.setRecipientId(message.getUserId());
message_tpl.setAttachment("file", "http://techslides.com/demos/samples/sample.pdf", false);
message_tpl.setNotificationType("NO_PUSH");
platform.getBaseSender().send(message_tpl);
// To send a video
message_tpl.setRecipientId(message.getUserId());
message_tpl.setAttachment("video", "http://techslides.com/demos/samples/sample.mp4", false);
platform.getBaseSender().send(message_tpl);
// To send an audio
message_tpl.setRecipientId(message.getUserId());
message_tpl.setAttachment("audio", "http://techslides.com/demos/samples/sample.mp3", false);
platform.getBaseSender().send(message_tpl);
// To send a quick text reply with payload buttons
message_tpl.setRecipientId(message.getUserId());
message_tpl.setMessageText("Select a Color!");
message_tpl.setQuickReply("text", "Red", "text_reply_red_click", "");
message_tpl.setQuickReply("text", "Green", "text_reply_green_click", "");
message_tpl.setQuickReply("text", "Black", "text_reply_black_click", "");
platform.getBaseSender().send(message_tpl);
// To send a quick text reply with payload buttons (Button with images)
message_tpl.setRecipientId(message.getUserId());
message_tpl.setMessageText("Select a Color!");
message_tpl.setQuickReply("text", "Red", "text_reply_red_click", "http://static.wixstatic.com/media/f0a6df_9ae4c70963244e16ba0d89d021407335.png");
message_tpl.setQuickReply("text", "Green", "text_reply_green_click", "http://static.wixstatic.com/media/f0a6df_9ae4c70963244e16ba0d89d021407335.png");
message_tpl.setQuickReply("text", "Black", "text_reply_black_click", "http://static.wixstatic.com/media/f0a6df_9ae4c70963244e16ba0d89d021407335.png");
platform.getBaseSender().send(message_tpl);
// To send location reply
message_tpl.setRecipientId(message.getUserId());
message_tpl.setMessageText("Please share your location!");
message_tpl.setQuickReply("location", "", "", "");
platform.getBaseSender().send(message_tpl);
Please note that to respond to custom payloads, Please do the following:
// To get the payload value, Use this code
// String quick_reply_payload = (message.hasQuickReplyPayload()) ? message.getQuickReplyPayload() : "";
// as shown before
if( quick_reply_payload.equals("text_reply_red_click") ){
message_tpl.setRecipientId(message.getUserId());
message_tpl.setMessageText("Red Clicked");
platform.getBaseSender().send(message_tpl);
}else if( quick_reply_payload.equals("text_reply_green_click") ){
message_tpl.setRecipientId(message.getUserId());
message_tpl.setMessageText("Green Clicked");
platform.getBaseSender().send(message_tpl);
}else if( quick_reply_payload.equals("text_reply_black_click") ){
message_tpl.setRecipientId(message.getUserId());
message_tpl.setMessageText("Black Clicked");
platform.getBaseSender().send(message_tpl);
}
Let's create an empty message first and fill it with the required data. We can get a new message container from Bot Platform Instance:
ButtonTemplate button_message_tpl = platform.getBaseSender().getButtonTemplate();
// Let's start to fill the required data here
// ..
Here's some of the usage cases:
// get the message.getUserId() from the message received
// To send a web url button
button_message_tpl.setRecipientId(message.getUserId());
button_message_tpl.setMessageText("Click Below!");
button_message_tpl.setButton("web_url", "Take the Hat Quiz", "https://m.me/petershats?ref=take_quiz", "", "");
platform.getBaseSender().send(button_message_tpl);
// To send a postback button
button_message_tpl.setRecipientId(message.getUserId());
button_message_tpl.setMessageText("Click Below!");
button_message_tpl.setButton("postback", "Bookmark Item", "", "DEVELOPER_DEFINED_PAYLOAD", "");
platform.getBaseSender().send(button_message_tpl);
// To send a phone number button
button_message_tpl.setRecipientId(message.getUserId());
button_message_tpl.setMessageText("Click Below!");
button_message_tpl.setButton("phone_number", "Call Representative", "", "+15105551234", "");
platform.getBaseSender().send(button_message_tpl);
// To send account link button
button_message_tpl.setRecipientId(message.getUserId());
button_message_tpl.setMessageText("Click Below!");
button_message_tpl.setButton("account_link", "", "https://www.example.com/authorize", "", "");
platform.getBaseSender().send(button_message_tpl);
// To send account unlink button
button_message_tpl.setRecipientId(message.getUserId());
button_message_tpl.setMessageText("Click Below!");
button_message_tpl.setButton("account_unlink", "", "", "", "");
platform.getBaseSender().send(button_message_tpl);
Let's create an empty list message first and fill it with the required data. We can get a new list message container from Bot Platform Instance:
ListTemplate list_message_tpl = platform.getBaseSender().getListTemplate();
// Let's start to fill the required data here
// ..
Let's create an empty generic message first and fill it with the required data. We can get a new generic message container from Bot Platform Instance:
GenericTemplate generic_message_tpl = platform.getBaseSender().getGenericTemplate();
// Let's start to fill the required data here
// ..
Let's create an empty receipt message first and fill it with the required data. We can get a new receipt message container from Bot Platform Instance:
ReceiptTemplate receipt_message_tpl = platform.getBaseSender().getReceiptTemplate();
// Let's start to fill the required data here
// ..
Here's some of the usage cases:
// get the message.getUserId() from the message received
// To send a receipt message
receipt_message_tpl.setRecipientId(message.getUserId());
receipt_message_tpl.setRecipientName("Stephane Crozatier");
receipt_message_tpl.setOrderNumber("12345678902");
receipt_message_tpl.setCurrency("USD");
receipt_message_tpl.setPaymentMethod("Visa 2345");
receipt_message_tpl.setOrderUrl("http://petersapparel.parseapp.com/order?order_id=123456");
receipt_message_tpl.setTimestamp("1428444852");
receipt_message_tpl.setElement("Classic White T-Shirt", "100% Soft and Luxurious Cotton", "2", "50", "USD", "https://image.spreadshirtmedia.com/image-server/v1/products/1001491830/views/1,width=800,height=800,appearanceId=2,version=1473664654/black-rap-nation-t-shirt-men-s-premium-t-shirt.png");
receipt_message_tpl.setElement("Classic Gray T-Shirt", "100% Soft and Luxurious Cotton", "2", "50", "USD", "https://static1.squarespace.com/static/57a088e05016e13b82b0beac/t/584fe89720099e4b5211c624/1481631899763/darts-is-my-religion-ally-pally-is-my-church-t-shirt-maenner-maenner-t-shirt.png");
receipt_message_tpl.setAddress("1 Hacker Way", "", "Menlo Park", "94025", "CA", "US");
receipt_message_tpl.setSummary("75.00", "4.95", "6.19", "56.14");
receipt_message_tpl.setAdjustment("New Customer Discount", "20");
receipt_message_tpl.setAdjustment("$10 Off Coupon", "10");
platform.getBaseSender().send(receipt_message_tpl);
For almost all supported features you can take a look at
examples/
folder for working examples.
Also check the following tutorials:
For transparency into our release cycle and in striving to maintain backward compatibility, Racter is maintained under the Semantic Versioning guidelines and release process is predictable and business-friendly.
See the Releases section of our GitHub project for changelogs for each release version of Racter. It contains summaries of the most noteworthy changes made in each release.
If you have any suggestions, bug reports, or annoyances please report them to our issue tracker at https://github.com/clivern/racter/issues
If you discover a security vulnerability within Racter, please send an email to hello@clivern.com
We are an open source, community-driven project so please feel free to join us. see the contributing guidelines for more details.
© 2019, Clivern. Released under Apache License, Version 2.0.
Racter is authored and maintained by @Clivern.