mirror of
https://github.com/TheRealOwenRees/plantid-discord-bot.git
synced 2026-07-23 04:26:57 +00:00
Discord customer logger
This commit is contained in:
@@ -13,13 +13,34 @@ If you wish to invite this bot to your server, use [this link.](https://discord.
|
|||||||
- Plant names are given in latin with a list of possible common names
|
- Plant names are given in latin with a list of possible common names
|
||||||
- Provides links to [GBIF], [PFAF], and [POWO] for the identified plant
|
- Provides links to [GBIF], [PFAF], and [POWO] for the identified plant
|
||||||
|
|
||||||
## Prerequisites
|
## Dependencies
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
# mix.exs
|
||||||
|
|
||||||
|
defp deps do
|
||||||
|
[
|
||||||
|
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
|
||||||
{:nostrum, "~> 0.10"},
|
{:nostrum, "~> 0.10"},
|
||||||
{:httpoison, "~> 2.2"},
|
{:httpoison, "~> 2.2"},
|
||||||
{:image, "~> 0.55"},
|
{:image, "~> 0.55"},
|
||||||
{:jason, "~> 1.4"},
|
{:jason, "~> 1.4"},
|
||||||
{:plug_cowboy, "~> 2.7"}
|
{:plug_cowboy, "~> 2.7"},
|
||||||
|
{:quantum, "~> 3.5"},
|
||||||
|
{:tesla, "~> 1.13"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
Below are the environment variables that you need to set for the program to function:
|
||||||
|
|
||||||
|
```
|
||||||
|
DISCORD_TOKEN= client secret from the bot's application
|
||||||
|
LOGS_DISCORD_WEBHOOK_URL= webhook url for the log channel
|
||||||
|
PLANTNET_API_KEY= API key for the PlantNet service
|
||||||
|
```
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,4 @@ config :plantid_discord_bot, PlantIdDiscordBot.Scheduler,
|
|||||||
{"@daily", {PlantIdDiscordBot.RateLimiter, :reset_counters, []}}
|
{"@daily", {PlantIdDiscordBot.RateLimiter, :reset_counters, []}}
|
||||||
]
|
]
|
||||||
|
|
||||||
config :logger, :console,
|
|
||||||
format: "$time $metadata[$level] $message\n",
|
|
||||||
level: :info
|
|
||||||
|
|
||||||
import_config "#{config_env()}.exs"
|
import_config "#{config_env()}.exs"
|
||||||
|
|||||||
+3
-1
@@ -12,4 +12,6 @@ config :plantid_discord_bot,
|
|||||||
"https://discord.com/api/oauth2/authorize?client_id=948227126094598204&permissions=19520&scope=bot",
|
"https://discord.com/api/oauth2/authorize?client_id=948227126094598204&permissions=19520&scope=bot",
|
||||||
api: Nostrum.Api
|
api: Nostrum.Api
|
||||||
|
|
||||||
config :logger, level: :debug
|
config :logger, :console,
|
||||||
|
format: "$time $metadata[$level] $message\n",
|
||||||
|
level: :debug
|
||||||
|
|||||||
+1
-1
@@ -13,4 +13,4 @@ config :plantid_discord_bot,
|
|||||||
|
|
||||||
config :logger, :console,
|
config :logger, :console,
|
||||||
format: "$time $metadata[$level] $message\n",
|
format: "$time $metadata[$level] $message\n",
|
||||||
level: :info
|
level: :error
|
||||||
|
|||||||
@@ -15,6 +15,14 @@ config :nostrum,
|
|||||||
token: System.get_env("DISCORD_TOKEN"),
|
token: System.get_env("DISCORD_TOKEN"),
|
||||||
ffmpeg: nil
|
ffmpeg: nil
|
||||||
|
|
||||||
|
config :logger,
|
||||||
|
backends: [{PlantIdDiscordBot.DiscordLogger, :discord_logger}, :console]
|
||||||
|
|
||||||
config :logger, :console,
|
config :logger, :console,
|
||||||
format: "$time $metadata[$level] $message\n",
|
format: "$time $metadata[$level] $message\n",
|
||||||
level: :info
|
level: :info
|
||||||
|
|
||||||
|
config :logger, :discord_logger,
|
||||||
|
webhook_url: System.get_env("LOGS_DISCORD_WEBHOOK_URL"),
|
||||||
|
level: :error,
|
||||||
|
bot_token: System.get_env("DISCORD_TOKEN")
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
defmodule PlantIdDiscordBot.DiscordLogger do
|
||||||
|
require Logger
|
||||||
|
use Tesla
|
||||||
|
|
||||||
|
plug(Tesla.Middleware.JSON)
|
||||||
|
|
||||||
|
@behaviour :gen_event
|
||||||
|
|
||||||
|
def init({__MODULE__, name}) do
|
||||||
|
{:ok, configure(name, [])}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_call({:configure, opts}, %{name: name} = state) do
|
||||||
|
{:ok, :ok, configure(name, opts, state)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event({_level, gl, {Logger, _, _, _}}, state) when node(gl) != node() do
|
||||||
|
{:ok, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event({level, _gl, {Logger, msg, ts, md}}, %{} = state) do
|
||||||
|
if is_level_okay(level, state.level) do
|
||||||
|
log_to_discord(state.webhook_url, level, msg, ts, md)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event(:flush, state) do
|
||||||
|
{:ok, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info(_, state) do
|
||||||
|
{:ok, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp is_level_okay(lvl, min_level) do
|
||||||
|
is_nil(min_level) or Logger.compare_levels(lvl, min_level) != :lt
|
||||||
|
end
|
||||||
|
|
||||||
|
def log_to_discord(webhook_url, level, msg, ts, md) do
|
||||||
|
formatted_msg = format_message(level, msg, ts, md)
|
||||||
|
body = %{content: formatted_msg}
|
||||||
|
|
||||||
|
post(webhook_url, body)
|
||||||
|
|> case do
|
||||||
|
{:ok, %{status: status}} when status in 200..299 -> :ok
|
||||||
|
{:error, reason} -> Logger.error("Error sending log to Discord: #{inspect(reason)}")
|
||||||
|
_any -> Logger.error("Error sending log to Discord")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def format_message(level, msg, _ts, md) do
|
||||||
|
timestamp = DateTime.utc_now()
|
||||||
|
source = md[:application]
|
||||||
|
msg = IO.iodata_to_binary(msg) |> String.slice(0..1900)
|
||||||
|
|
||||||
|
"[#{timestamp}] [#{source}] [#{level}] `#{msg}`"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp configure(name, opts) do
|
||||||
|
state = %{name: name, format: nil, level: nil, metadata: nil, metadata_filter: nil}
|
||||||
|
configure(name, opts, state)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp configure(name, opts, state) do
|
||||||
|
env = Application.get_env(:logger, name, [])
|
||||||
|
opts = Keyword.merge(env, opts)
|
||||||
|
Application.put_env(:logger, name, opts)
|
||||||
|
|
||||||
|
new_state = %{
|
||||||
|
webhook_url: Keyword.get(opts, :webhook_url, nil),
|
||||||
|
level: Keyword.get(opts, :level)
|
||||||
|
}
|
||||||
|
|
||||||
|
Map.merge(state, new_state)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,7 +2,6 @@ defmodule PlantIdDiscordBot.Consumer do
|
|||||||
@moduledoc """
|
@moduledoc """
|
||||||
A Discord bot that identifies plants from photos of their organs.
|
A Discord bot that identifies plants from photos of their organs.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use Nostrum.Consumer
|
use Nostrum.Consumer
|
||||||
alias Nostrum.Api
|
alias Nostrum.Api
|
||||||
alias PlantIdDiscordBot.Cog
|
alias PlantIdDiscordBot.Cog
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ defmodule PlantIdDiscordBot.Cog.Info do
|
|||||||
import Nostrum.Struct.Embed
|
import Nostrum.Struct.Embed
|
||||||
alias PlantIdDiscordBot.Utils
|
alias PlantIdDiscordBot.Utils
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
# reference to Nostrum.Api in non-test environments, reference to mock in test
|
# reference to Nostrum.Api in non-test environments, reference to mock in test
|
||||||
@api Application.compile_env(:plantid_discord_bot, :api)
|
@api Application.compile_env(:plantid_discord_bot, :api)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
defmodule PlantIdDiscordBot.Cog.PlantNet do
|
defmodule PlantIdDiscordBot.Cog.PlantNet do
|
||||||
|
require Logger
|
||||||
use Nostrum.Consumer
|
use Nostrum.Consumer
|
||||||
alias PlantIdDiscordBot.RateLimiter
|
alias PlantIdDiscordBot.RateLimiter
|
||||||
alias PlantIdDiscordBot.PlantNet.Parser
|
alias PlantIdDiscordBot.PlantNet.Parser
|
||||||
@@ -50,7 +51,7 @@ defmodule PlantIdDiscordBot.Cog.PlantNet do
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
IO.inspect(saved_images)
|
Logger.debug(saved_images)
|
||||||
|
|
||||||
# TODO use actual image data
|
# TODO use actual image data
|
||||||
image1 =
|
image1 =
|
||||||
@@ -73,13 +74,13 @@ defmodule PlantIdDiscordBot.Cog.PlantNet do
|
|||||||
})
|
})
|
||||||
|
|
||||||
{:ok, %HTTPoison.Response{status_code: 400}} ->
|
{:ok, %HTTPoison.Response{status_code: 400}} ->
|
||||||
# TODO add logger
|
Logger.error("Malformed request sent to the PlantNet API")
|
||||||
|
|
||||||
Api.create_followup_message(interaction.application_id, interaction.token, %{
|
Api.create_followup_message(interaction.application_id, interaction.token, %{
|
||||||
content: "Bad Request"
|
content: "Bad Request"
|
||||||
})
|
})
|
||||||
|
|
||||||
{:ok, %HTTPoison.Response{status_code: 404}} ->
|
{:ok, %HTTPoison.Response{status_code: 404}} ->
|
||||||
# TODO add logger
|
|
||||||
RateLimiter.increase_counter(interaction.guild_id)
|
RateLimiter.increase_counter(interaction.guild_id)
|
||||||
|
|
||||||
Api.create_followup_message(interaction.application_id, interaction.token, %{
|
Api.create_followup_message(interaction.application_id, interaction.token, %{
|
||||||
@@ -87,19 +88,20 @@ defmodule PlantIdDiscordBot.Cog.PlantNet do
|
|||||||
})
|
})
|
||||||
|
|
||||||
{:ok, %HTTPoison.Response{status_code: 429}} ->
|
{:ok, %HTTPoison.Response{status_code: 429}} ->
|
||||||
# TODO add logger
|
Logger.warning("Request limit exceeded for the PlantNet API")
|
||||||
|
|
||||||
Api.create_followup_message(interaction.application_id, interaction.token, %{
|
Api.create_followup_message(interaction.application_id, interaction.token, %{
|
||||||
content: "Too Many Requests"
|
content: "Too Many Requests"
|
||||||
})
|
})
|
||||||
|
|
||||||
{_, _} ->
|
{_, _} ->
|
||||||
# TODO add logger
|
Logger.error("Internal server error when contacting the PlantNet API")
|
||||||
|
|
||||||
Api.create_followup_message(interaction.application_id, interaction.token, %{
|
Api.create_followup_message(interaction.application_id, interaction.token, %{
|
||||||
content: "Internal Server Error"
|
content: "Internal Server Error"
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO make into a task for async deletion, deal with errors
|
|
||||||
Enum.map(saved_images, fn {:ok, filename} -> filename end)
|
Enum.map(saved_images, fn {:ok, filename} -> filename end)
|
||||||
|> File.delete_files!()
|
|> File.delete_files!()
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ defmodule PlantidDiscordBot.MixProject do
|
|||||||
{:image, "~> 0.55"},
|
{:image, "~> 0.55"},
|
||||||
{:jason, "~> 1.4"},
|
{:jason, "~> 1.4"},
|
||||||
{:plug_cowboy, "~> 2.7"},
|
{:plug_cowboy, "~> 2.7"},
|
||||||
{:quantum, "~> 3.5"}
|
{:quantum, "~> 3.5"},
|
||||||
|
{:tesla, "~> 1.13"}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
"sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"},
|
"sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"},
|
||||||
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
|
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
|
||||||
"telemetry_registry": {:hex, :telemetry_registry, "0.3.2", "701576890320be6428189bff963e865e8f23e0ff3615eade8f78662be0fc003c", [:mix, :rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7ed191eb1d115a3034af8e1e35e4e63d5348851d556646d46ca3d1b4e16bab9"},
|
"telemetry_registry": {:hex, :telemetry_registry, "0.3.2", "701576890320be6428189bff963e865e8f23e0ff3615eade8f78662be0fc003c", [:mix, :rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7ed191eb1d115a3034af8e1e35e4e63d5348851d556646d46ca3d1b4e16bab9"},
|
||||||
|
"tesla": {:hex, :tesla, "1.13.2", "85afa342eb2ac0fee830cf649dbd19179b6b359bec4710d02a3d5d587f016910", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:mox, "~> 1.0", [hex: :mox, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "960609848f1ef654c3cdfad68453cd84a5febecb6ed9fed9416e36cd9cd724f9"},
|
||||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
||||||
"vix": {:hex, :vix, "0.31.1", "2b1d379393060ee8e4e1f1c9a621811c4091d8f063221c1ff24a41a4f0c97edc", [:make, :mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:cc_precompiler, "~> 0.1.4 or ~> 0.2", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7.3 or ~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "766856b52bec222cb5fd301f645a7a9869b61e0ec6e87dc0789ae9657356a8ea"},
|
"vix": {:hex, :vix, "0.31.1", "2b1d379393060ee8e4e1f1c9a621811c4091d8f063221c1ff24a41a4f0c97edc", [:make, :mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:cc_precompiler, "~> 0.1.4 or ~> 0.2", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7.3 or ~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "766856b52bec222cb5fd301f645a7a9869b61e0ec6e87dc0789ae9657356a8ea"},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user