Creating a Twitter Bot with Phoenix
Tags: elixir, phoenix, uruguay, movies
Published: 2022-12-13 15:45:53

I started this blog in order to learn how to use Twitter API and also because i love going to the cinema :raised_hands:. Im from Uruguay 🇺🇾, and here we have a cinema called "Movie Center". After checking their request, I started to investigate what we can get from them
<p> </p>
___________________________________
## Investigation 🕵️

After checking their request I found out that I can use that information for the Twitter bot.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fc4n3bkirdvoemunfc8i.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a0zk01lzkgoucjy6jimd.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qhrs5zovst1s9admvka8.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8u6z6byvaqqtjzod5agx.png)

<p> </p>
__________________________________
## Creating the app 👨‍💻

Let’s start by creating our Phoenix app

```bash
mix phx.new movies_uy
cd movies_uy
mix ecto.create

iex -S mix phx.server
```

Now Im going to add the following dependencies that are needed for the bot:
```elixir
# mix.exs
defp deps do
  [
     {:finch, "~> 0.13"},
     {:oauther, "~> 1.1"},
     {:extwitter, "~> 0.12"},
     {:oban, "~> 2.13"}
  ]
end
```
* Finch for doing request to the "Movie Center" API.
* ExTwitter it's a Twitter Client for using Twitter API
* Oban for creating jobs that run whenever we want (in our case once a day)
 
After adding this don't forget to run `mix deps.get`.

<p> </p>
___________________________________
### Config Finch
Let’s configure Finch by adding this, note that you can follow this from the Finch repo too.
```elixir
# lib/movies_uy/application.ex
def start(_type, _args) do
  children = [
    ...
    {Finch, name: MoviesUyFinch}
  ]
end
```

<p> </p>
___________________________________
### Config ExTwitter 🧵
```elixir
# config/config.exs
config :extwitter, :oauth, [
  consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),
  consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET"),
  access_token: System.get_env("TWITTER_ACCESS_TOKEN"),
  access_token_secret: System.get_env("TWITTER_ACCESS_TOKEN_SECRET")
]
```

<p></p>
________________________
### Getting Movies 🎬
Let's add the following code that will search for all the movies from the Movie Center API.

```elixir
# lib/movies_uy/movie_center.ex
defmodule MoviesUy.MovieCenter do
  require Logger

  def fetch_movies do
    movie_url = "https://api.movie.com.uy/api/billboard/cinema/weekly?nextReleases=false"

    request = Finch.build(:get, movie_url) |> Finch.request(MoviesUyFinch)

    with {:ok, %Finch.Response{body: data}} <- request,
     %{"items" => movies} <- Jason.decode!(data) do

      movies
      |> Enum.map(fn %{"content" => %{"title" => title, "id" => id, "urlSlug" => slug}}} ->

        "#{title} - https://www.movie.com.uy/movie/#{id}/#{slug}"
      end)

    else
      _ ->
        Logger.error "Error!"
    end
  end
end
```

> You can also check if this is working correctly by running the following line on your iex 
> ```bash
> $ iex> MoviesUy.MovieCenter.fetch_movies # this should show all the movies from Movie center API
> ```

<p></p>
________________________
### Using ExTwitter 🧵
Now is time to build the messages and post them on Twitter using ExTwitter.

What Im going to do is create a tweet that contains the current date, and the comments of this twitter will be the movies with their current url on Movie Center.

```elixir
# lib/movies_uy/movie_center.ex
defmodule MoviesUy.MovieCenter do
  require Logger

  def fetch_movies do
    movie_url = "https://api.movie.com.uy/api/billboard/cinema/weekly?nextReleases=false"

    request = Finch.build(:get, movie_url) |> Finch.request(MoviesUyFinch)

    with {:ok, %Finch.Response{body: data}} <- request,
         %{"items" => movies} <- Jason.decode!(data),
         %ExTwitter.Model.Tweet{id: tweet_id} <- post_message()
    do
      movies
      |> Enum.map(&post_thread_message(&1, tweet_id))
    else
      error ->
        Logger.error error
    end
  end

  defp post_thread_message(%{"content" => %{"title" => title, "id" => id, "urlSlug" => slug}}, tweet_id) do
    "#{title} - https://www.movie.com.uy/movie/#{id}/#{slug}"
    |> ExTwitter.update(in_reply_to_status_id: tweet_id)
  end

  defp post_message do
    %Date{day: day, month: month, year: year} = Date.utc_today

    "Cartelera de Movie Center del #{day}/#{month}/#{year}"
    |> ExTwitter.update
  end
end
```

We can run this using the same function as before and we should be able to see that is posting correctly on Twitter.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ac2zcfw2g780f2sefgh.png)



<img width="100%" style="width:100%" src="https://media.giphy.com/media/K1YAcmDvKNj0IJF7pF/giphy.gif">
<p> </p>
___________________________________

### Config Oban 📅
It’s time to use Oban, this will allow us to create a worker that runs our code whenever we want, in our case once a day! Lets config Oban (you can also read the docs).

```bash
$ mix ecto.gen.migration add_oban_jobs_table
```

Then open the generated migration and update it:
```elixir
defmodule MoviesUy.Repo.Migrations.AddObanJobsTable do
  use Ecto.Migration

  def up do
    Oban.Migrations.up(version: 11)
  end

  # We specify `version: 1` in `down`, ensuring that we'll roll all the way back down if
  # necessary, regardless of which version we've migrated `up` to.
  def down do
    Oban.Migrations.down(version: 1)
  end
end
```

And finally lets run this
```bash
$ mix ecto.migrate
```

Lets add this
```elixir
#config/config.exs
config :movies_uy, Oban,
  repo: MoviesUy.Repo,
  plugins: [
    Oban.Plugins.Pruner,
    {Oban.Plugins.Cron,
      crontab: [
        {"@daily", MoviesUy.DailyWorker, max_attempts: 1},
      ]}
  ],
  queues: [default: 10]
```

```elixir
# lib/my_app/application.ex
def start(_type, _args) do
  children = [
    ...
    {Oban, Application.fetch_env!(:movies_uy, Oban)}
  ]
end
```

```elixir
# lib/movies_uy/workers/daily_worker.ex
defmodule MoviesUy.DailyWorker do
  use Oban.Worker, queue: :default

  @impl Oban.Worker
  def perform(_) do
    MoviesUy.MovieCenter.fetch_movies()
  end
end
```

Thats it! you can see this Twitter bot working visiting: https://twitter.com/movies_uy

<p> </p>
> Note: you can also create just a mix app and this guide will work too. I used `phx` because Im planning to do another thing in the future with it

<p> </p>
___________________________________

## Things we used here:
* [https://github.com/sneako/finch](https://github.com/sneako/finch)

* [https://github.com/parroty/extwitter](https://github.com/parroty/extwitter)

* [https://github.com/sorentwo/oban](https://github.com/sorentwo/oban)

_____________________________________
## Visit the bot:
Twitter bot: https://twitter.com/movies_uy

## The code:
https://github.com/kexposito/movies-uy