Packages

Toolbox that help to do trivial things faster. I have more free time for life

Current section

Files

Jump to
magik lib telegram_noti.ex
Raw

lib/telegram_noti.ex

defmodule Magik.TelegramNoti do
@moduledoc """
This module provide some helper functions that help to send message to a telegram conversation
## Config
config :magik, :telegram_noti,
bot_token: "your bot token".
conversations: [
default: "default_chat_id",
admin: "other chat id"
],
mode: :prod # or :dev
- `bot_token`: your Telegram bot tokent
- `conversations`: keyword list of name and chat_id. There must be at least 1 conversation which is `:default`. `default` is used if you don't specify conversation name in the function call.
Then you are ready to send message to your Telegram conversation
"""
require Logger
@doc """
This macro help you to catch exceptions and then send to your Telegram conversation using `send_error/4`
## Options
- `to`: conversation name from your config. Default is `:default`
- `args`: argument list that passed to function, this is sent to telegram chat for dev to debug easier. If not speficied, arguments for current function call are used
- `label`: label for this error/function. If not specified, current function name is used.
**Example**
...
require Magik.TelegramNoti
def do_something(args) do
Magik.TelegramNoti.watch [to: :admin] do
# your logic code here
end
end
...
"""
defmacro watch(opts \\ [], do: block) do
quote do
try do
unquote(block)
rescue
err ->
conversation = Keyword.get(unquote(opts), :to, :default)
label = Keyword.get(unquote(opts), :label)
label =
if label do
label
else
{mod_name, func_name, arity, _} =
Process.info(self(), :current_stacktrace)
|> elem(1)
|> Enum.fetch!(2)
"#{mod_name}.#{func_name}/#{arity}"
end
Magik.TelegramNoti.send_error(conversation, label, binding(), %{
kind: :error,
reason: err,
stack: __STACKTRACE__
})
:erlang.raise(:error, err, __STACKTRACE__)
end
end
end
@doc """
Send a message to conversation
send_message(:api, "this is a sample message")
"""
@spec send_message(atom, String.t()) :: {:ok, map} | {:error, map}
def send_message(conversation \\ :default, message) do
config = Application.get_env(:magik, :telegram_noti, [])
with conversations <- config[:conversations],
chat_id <- Keyword.get(conversations || [], conversation),
false <- chat_id in [nil, ""],
false <- config[:bot_token] in [nil, ""] do
url = "https://api.telegram.org/bot#{config[:bot_token]}/sendMessage"
data =
Jason.encode!(%{
"chat_id" => chat_id,
"text" => escape_message(message),
"disable_web_page_preview" => true,
"parse_mode" => "MarkdownV2"
})
Tesla.post(
url,
data,
headers: [{"content-type", "application/json"}]
)
else
_ -> Logger.error("Telegram Noti: Missing chat_id/bot_token configuration")
end
end
@doc """
Format and send error message to a Telegam conversation with data from a connection. This helper is used to send error from your phoenix router or controller.
## From router
defmodule MyApp.Router do
use MyAppWeb, :router
use Plug.ErrorHandler
...
def handle_errors(conn, error) do
if conn.status >= 500 do
Magik.TelegramNoti.send_conn_error(:api, conn, error)
end
....
end
end
## from controller
defmodule MyAppWeb.PageController do
...
def index(conn, params)do
try do
...
catch
error ->
Magik.TelegramNoti.send_conn_error(:api, conn, %{kind: :error, reason: error, stack: __STACKTRACE__})
# return your error
end
end
end
"""
@spec send_conn_error(atom, Plug.Conn.t(), map) :: {:ok, map} | {:error, map}
def send_conn_error(conversation \\ :default, %{} = conn, %{
kind: kind,
reason: reason,
stack: stacktrace
}) do
message = """
*ERROR: #{conn.private[:phoenix_controller]}.#{conn.private[:phoenix_action]}*
--------------------
*Request URL*: #{Phoenix.Controller.current_url(conn)}
*Request Params*:
```
#{inspect(conn.params, pretty: true)}
```
*Details*
```
#{Exception.format(kind, reason, stacktrace)}
```
"""
send_message(conversation, message)
end
@doc """
Format error and send to Telegram conversation.
defmodule MyApp.Calculator do
...
def divide(a, b)do
try do
...
catch
error ->
Magik.TelegramNoti.send_error(:api, "MyApp.Calculator error", [a,b], %{kind: :error, reason: error, stack: __STACKTRACE__})
# return your error
end
end
end
"""
@spec send_error(atom, String.t(), any, map) :: {:ok, map} | {:error, map}
def send_error(conversation \\ :default, title, args \\ nil, %{
kind: kind,
reason: reason,
stack: stacktrace
}) do
message = """
*ERROR: #{title}*
----------------
*Params*:
```
#{inspect(args, pretty: true)}
```
*Details*
```
#{Exception.format(kind, reason, stacktrace)}
```
"""
send_message(conversation, message)
end
defp escape_message(message) do
message
|> String.replace("{", "\\{")
|> String.replace("}", "\\}")
|> String.replace(".", "\\.")
|> String.replace("-", "\\-")
|> String.replace("_", "\\_")
# |> String.replace("*", "\\*")
# |> String.replace("[", "\\[")
# |> String.replace("`", "\\`")
end
end