Just a silly Elixir application to tweet some content and play a little bit with Twitter's public API.
In this example, we're going to create an Elixir application which will contain a series of simple endpoints in order to be able to interact with the bot. Our goal will be to tweet content in our account, get tweets about a topic, and retweet a tweet.
We'll need to install in our computer two main things:
- Erlang: it's the only prerequisite to be able to install Elixir. You can download and install the latest version in its download page.
- Elixir: our beloved functional language. You can follow the instructions in its install guidelines.
Once we have these things installed, you can check that all went well by executing these instructions in your terminal and seeing if you get something like:
user@computer:$ erl -v
Erlang/OTP 20 [erts-9.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V9.2 (abort with ^G)
1> _
user@computer:$ elixir -v
Erlang/OTP 20 [erts-9.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:10] [hipe] [kernel-poll:false]
Elixir 1.8.1 (compiled with Erlang/OTP 20)
If you see something similar to these examples, we'll ready to start, so let's go!
The first step we need to take is to create the structure of our Elixir application. For that, we're going to use the mix new
task provided by Elixir. This task will create our new project, specifying a name and an option if we need to. In our case, we're going to specify the --sup
option, in order to generate an OTP application skeleton including a supervision tree. Normally an app is generated without a supervisor and without the app callback.
You can check the complete documentation of mix new
here.
So, just type in your terminal:
user@computer: mix new elixir_twitter_bot --sup
and there you have it! Your first Elixir application 😄
To build our endpoints, we're going to use Plug, using Erlang's Cowboy HTTP server, and Jason as our JSON parser.
- Plug: a specification for composable modules between web applications and connection adapters for different web servers in the Erlang VM.
- Cowboy: a small, fast and modern HTTP server for Erlang/OTP. It's a fault tolerant "server for the modern web" supporting HTTP/2, providing a suite of handlers for Websockets and interfaces for long-lived connections.
- Jason: a blazing fast JSON parser and generator in pure Elixir.
So, in order to add these dependencies, we need to add it to our mix.exs
file:
defp deps do
[
# This will add Plug and Cowboy
{:plug_cowboy, "~> 2.0"},
# This will add Jason
{:jason, "~> 1.1"}
]
end
and use mix deps.get
task to fetch all our dependencies:
user@computer:/elixir_twitter_bot$ mix deps.get
You can check the complete documentation of mix deps.get
here.
To start the Plug.Cowboy
module, we need to add its config information to our supervisor tree inside lib/elixir_twitter_bot/application.ex
:
def start(_type, _args) do
# List all child processes to be supervised
children = [
# Use Plug.Cowboy.child_spec/3 to register our endpoint as a plug
Plug.Cowboy.child_spec(
scheme: :http,
plug: ElixirTwitterBot.Endpoint,
options: [port: 4000]
)
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: ElixirTwitterBot.Supervisor]
Supervisor.start_link(children, opts)
end
As the first step to complete our application, we're going to create a new module and implement a simple endpoint in order to check if everything its ok. Inside lib/elixir_twitter_bot
folder, create a file endpoint.ex
and, with the help of Plug's official documentation, create an endpoint /ping
, and return just a Pong! response.
At the end, your code should look something like this:
defmodule ElixirTwitterBot.Endpoint do
@moduledoc """
A Plug responsible for logging request info, parsing request body's as JSON,
matching routes, and dispatching responses.
"""
use Plug.Router
use Plug.ErrorHandler
# This module is a Plug, that also implements it's own plug pipeline, below:
# Using Plug.Logger for logging request information
plug(Plug.Logger)
# Responsible for matching routes
plug(:match)
# Using Jason for JSON encoding.
# Note, order of plugs is important, by placing this _after_ the 'match' plug,
# we will only parse the request AFTER there is a route match.
plug(Plug.Parsers, parsers: [:json], json_decoder: Jason)
# Responsible for dispatching responses
plug(:dispatch)
# A simple route to test that the server is up.
# Note, all routes must return a connection as per the Plug spec.
get "/ping" do
conn
|> put_resp_content_type("application/json")
|> send_resp(200, Jason.encode!(%{:message => "Pong!"}))
end
end
To check if everything its ok, start the application by using mix run
task:
user@computer:/elixir_twitter_bot$ mix run --no-halt
Then open a browser and go to http://localhost:4000/ping
. You should get the right response.
To complete the base of our application, we're going to add two functions to handle our application' errors, and to generate the response for the user:
defp handle_errors(conn, %{
kind: _kind,
reason: _reason,
stack: _stack
}) do
conn
|> put_resp_content_type("application/json")
|> send_resp(
conn.status,
Jason.encode!(%{:message => "Unexcepted error"})
)
end
defp generate_response({:ok, result}, conn) do
conn
|> put_resp_content_type("application/json")
|> send_resp(200, Jason.encode!(result))
end
defp generate_response({:error, message}, conn) do
conn
|> put_resp_content_type("application/json")
|> send_resp(500, Jason.encode!(%{:message => message}))
end
We need to add ExTwitter as our third dependency in mix.exs
file:
defp deps do
[
# This will add Plug and Cowboy
{:plug_cowboy, "~> 2.0"},
# This will add Jason
{:jason, "~> 1.1"},
# This will add ExTwitter
{:extwitter, "~> 0.9"}
]
end
Then, go to the Twitter developers site and create a new application to get the authorization credentials to interact with Twitter's API, and add this information to congif/config.exs
:
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config
config :extwitter, :oauth,
consumer_key: "",
consumer_secret: "",
access_token: "",
access_token_secret: ""
Let's add the first endpoint to tweet some content to our Twitter's account. For this, add to the main module of the application (lib/elixir_twitter_bot.ex
) a new function, which will receive a conn
as an argument, and return the status of the request to the API. Your code should look something like this:
defmodule ElixirTwitterBot do
@moduledoc """
Main module of the ElixirTwitterBot. Here we have some simple functions to
interact with Twitter's public API through ExTwitter library.
"""
alias ExTwitter.API.Tweets
def update_status(%Plug.Conn{body_params: %{"status" => status}}) do
case Tweets.update(status) do
%ExTwitter.Model.Tweet{id: id} ->
{:ok, %{id: id, message: "New status posted succesfully!"}}
_ ->
{:error,
"Something wrong happened. The status couldn't be posted right now."}
end
end
end
Then add to you endpoint module the new endpoint to use this new function:
# Endpoint to post a new tweet to our Twitter's account
post "/update" do
conn
|> ElixirTwitterBot.update_status()
|> generate_response(conn)
end
To check if everything its ok, start the application by using mix run
task:
user@computer:/elixir_twitter_bot$ mix run --no-halt
Then make a POST
request to http://localhost:4000/update
, sending any text on the status
parameter. You should get the right response and see a new tweet in your account 😄
Repeat the same steps, but this time using the ExTwitter.API.Search.search/2
function from ExTwitter:
def search(%Plug.Conn{query_params: %{"query" => query}}) do
case Search.search(query) do
tweets when is_list(tweets) ->
results =
Enum.map(tweets, fn %{id: id, text: text} ->
%{id: id, text: text}
end)
{:ok, %{results: results}}
_ ->
{:error,
"Something wrong happened. The search couldn't be done right now."}
end
end
and in the endpoint module:
# Endpoint to search tweets by a query
get "/search" do
conn
|> ElixirTwitterBot.search()
|> generate_response(conn)
end
Repeat the same steps, but this time using the ExTwitter.API.Tweets.retweet/2
function from ExTwitter:
def retweet(%Plug.Conn{body_params: %{"tweet_id" => tweet_id}}) do
case Tweets.retweet(tweet_id) do
%ExTwitter.Model.Tweet{id: id} ->
{:ok, %{id: id, message: "Tweet retweeted succesfully!"}}
_ ->
{:error,
"Something wrong happened. The tweet couldn't be retweeted right now."}
end
end
and in the endpoint module:
# Endpoint to retweet a tweet to our Twitter's account
post "/retweet" do
conn
|> ElixirTwitterBot.retweet()
|> generate_response(conn)
end
- Erlang documentation.
- Elixir documentation.
- Mix documentation.
- Plug documentation.
- ExTwitter documentation.
This project was developed by dreamingechoes. It adheres to its code of conduct and contributing guidelines, and uses an equivalent license.