#elixirlang 언어로 #telegram 봇 만들기

1 minute read

텔레그램은 훌륭한 메신저이자 클라이언트다. 훌륭한 클라이언트가 된 건 bot api 덕분이다. 간단한 iOS 앱을 만들려고 했는데, 텍스트로 제어가 충분하다고 생각하니 텔레그램 봇으로 만들면 되겠단 생각이 들었다. 혼자 쓸 거라 만들어 본 슬랙(slack) 봇은 과하다.

bot api를 쌩으로 다 호출하기는 괴로우니 텔레그램 bot api wrapper 패키지를 찾아봤다. 다운로드 수가 가장 많은 nadia를 사용하기로했다. 설명이 부실해 이리저리 헤맸다. 다행히 elixir-telegram-bot-boilerplate 프로젝트를 찾아서 시간을 절약했다.

nil

텔레그램에서 봇 계정을 만들고 토큰을 발급받아야 한다. godfather가 아닌 botfather라니 센스 죽인다. 웹 페이지를 기대했는데, 봇을 통해 봇 계정을 만들다니. 봇은 이렇게 쓰는 거에요. 이런 설명도 따로 필요 없겠더라.

$ mix new ping --sup

프로젝트를 만든다.

use Mix.Config

config :nadia,
  token: "BOT TOKEN"

발급받은 토큰을 config.exs 파일에 설정으로 추가한다.

defmodule Ping.MixProject do
  use Mix.Project
  # ...
  def application do
    [
      extra_applications: [:logger, :nadia],
      mod: {Ping.Application, []}
    ]
  end

  defp deps do
    [
      {:nadia, "~> 0.4.4"}
    ]
  end
end

mix.exs 파일에 의존 라이브러리와 시작 애플리케이션을 설정한다.

defmodule Ping.Poller do
  use GenServer
  require Logger

  @interval_ms 1000

  def start_link(args) do
    GenServer.start_link(__MODULE__, args, name: __MODULE__)
  end

  def init(_args) do
    schedule_update()
    {:ok, nil}
  end

  def handle_info(:update, offset) do
    schedule_update()

    ret =
      if offset do
        Nadia.get_updates(offset: offset)
      else
        Nadia.get_updates()
      end

    new_offset = ret |> process_messages

    {:noreply, new_offset + 1}
  end

  defp schedule_update() do
    Process.send_after(self(), :update, @interval_ms)
  end

  defp process_messages({:ok, []}), do: -1

  defp process_messages({:ok, results}) do
    results
    |> Enum.map(fn %{update_id: id} = message ->
      message
      |> process_message

      id
    end)
    |> List.last()
  end

  defp process_message({:error, error}) do
    Logger.warn("error - #{inspect(error)}")
  end

  defp process_message(message) do
    msg = message.message

    if msg do
      text = msg.text
      chat_id = msg.chat.id
      message_id = msg.message_id

      if String.trim(text) == "/ping" do
        Nadia.send_message(chat_id, "**pong**\n`pong`\n_pong_```\npong\n```",
          reply_to_message_id: message_id,
          parse_mode: "Markdown"
        )
      end
    end
  end
end

Ping.Poller 모듈을 lib/ping/poller.ex 파일에 만든다. 1초마다 Nadia.get_updates/1 함수를 불러서 메시지를 가져온다. offset 값을 관리해야 메시지를 중복으로 가져오는 일이 없다. 응답으로 받은 update_id 값에 1을 더해서 요청하면 된다. 메시지에 markdown을 사용할 수 있어서 좀 더 풍부한 응답을 보낼 수 있다.

defmodule Ping.Application do
  use Application

  def start(_type, _args) do
    children = [
      {Ping.Poller, []}
    ]

    opts = [strategy: :one_for_one, name: Ping.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

슈퍼바이저를 만들어 Ping.Poller 모듈에서 크래시가 났을 때, 다시 실행하게 한다.

$ mix do deps.get, run --no-halt

의존 라이브러리를 받고 텔레그램 봇을 실행. 이제 만든 텔레그램 봇 계정과 대화를 시작한다. /ping 명령을 입력해본다.