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:
@@ -3,6 +3,8 @@ defmodule PlantIdDiscordBot.Cog.Info do
|
||||
import Nostrum.Struct.Embed
|
||||
alias PlantIdDiscordBot.Utils
|
||||
|
||||
require Logger
|
||||
|
||||
# reference to Nostrum.Api in non-test environments, reference to mock in test
|
||||
@api Application.compile_env(:plantid_discord_bot, :api)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
defmodule PlantIdDiscordBot.Cog.PlantNet do
|
||||
require Logger
|
||||
use Nostrum.Consumer
|
||||
alias PlantIdDiscordBot.RateLimiter
|
||||
alias PlantIdDiscordBot.PlantNet.Parser
|
||||
@@ -50,7 +51,7 @@ defmodule PlantIdDiscordBot.Cog.PlantNet do
|
||||
})
|
||||
end
|
||||
|
||||
IO.inspect(saved_images)
|
||||
Logger.debug(saved_images)
|
||||
|
||||
# TODO use actual image data
|
||||
image1 =
|
||||
@@ -73,13 +74,13 @@ defmodule PlantIdDiscordBot.Cog.PlantNet do
|
||||
})
|
||||
|
||||
{: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, %{
|
||||
content: "Bad Request"
|
||||
})
|
||||
|
||||
{:ok, %HTTPoison.Response{status_code: 404}} ->
|
||||
# TODO add logger
|
||||
RateLimiter.increase_counter(interaction.guild_id)
|
||||
|
||||
Api.create_followup_message(interaction.application_id, interaction.token, %{
|
||||
@@ -87,19 +88,20 @@ defmodule PlantIdDiscordBot.Cog.PlantNet do
|
||||
})
|
||||
|
||||
{: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, %{
|
||||
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, %{
|
||||
content: "Internal Server Error"
|
||||
})
|
||||
end
|
||||
|
||||
# TODO make into a task for async deletion, deal with errors
|
||||
Enum.map(saved_images, fn {:ok, filename} -> filename end)
|
||||
|> File.delete_files!()
|
||||
end
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
defmodule PlantIdDiscordBot.PlantNet.Parser do
|
||||
@moduledoc """
|
||||
Parses the response from the PlantNet API.
|
||||
|
||||
# Examples
|
||||
iex> response = ~S({
|
||||
...> "results": [
|
||||
...> {
|
||||
...> "score": 0.87871,
|
||||
...> "species": {
|
||||
...> "scientificNameWithoutAuthor": "Prunus cerasifera",
|
||||
...> "commonNames": ["Cherry plum", "myrobalan", "Cherry Plum", "Purple-leaf Plum"]
|
||||
...> },
|
||||
...> "gbif": {"id": "3021730"},
|
||||
...> "powo": {"id": "729568-1"},
|
||||
...> "iucn": {"category": "DD"}
|
||||
...> }
|
||||
...> ]
|
||||
...> })
|
||||
iex> PlantIdDiscordBot.PlantNet.Parser.parse(response)
|
||||
|
||||
"My best guess is **Prunus cerasifera** with a confidence of **88%**. Common names include **Cherry plum, myrobalan, Cherry Plum, Purple-leaf Plum**.
|
||||
|
||||
[GBIF](<https://www.gbif.org/species/3021730>) | [PFAF](<https://pfaf.org/user/Plant.aspx?LatinName=Prunus+cerasifera>) | [POWO](<https://powo.science.kew.org/taxon/729568-1>)
|
||||
Threat status: DD"
|
||||
"""
|
||||
|
||||
@gbif_base_url "https://www.gbif.org/species"
|
||||
@pfaf_base_url "https://pfaf.org/user/Plant.aspx?LatinName="
|
||||
@powo_base_url "https://powo.science.kew.org/taxon"
|
||||
@score_threshold Application.compile_env(:plantid_discord_bot, :score_threshold)
|
||||
|
||||
@doc """
|
||||
Parses the response from the PlantNet API into a map.
|
||||
"""
|
||||
@spec parse(String.t()) :: map()
|
||||
def parse(response) do
|
||||
response
|
||||
|> to_map!()
|
||||
|> filter_by_score()
|
||||
|> add_external_urls()
|
||||
|> generate_response_message()
|
||||
end
|
||||
|
||||
@spec to_map!(String.t()) :: map()
|
||||
def to_map!(response), do: Jason.decode!(response)
|
||||
|
||||
@doc """
|
||||
Filters the data by score. Data is a parsed response from the PlantNet API.
|
||||
"""
|
||||
@spec filter_by_score(map()) :: map()
|
||||
def filter_by_score(data) do
|
||||
updated_results =
|
||||
data["results"]
|
||||
|> Enum.filter(&(&1["score"] > @score_threshold))
|
||||
|
||||
Map.put(data, "results", updated_results)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds external URLs to the data. Data is a parsed response from the PlantNet API.
|
||||
"""
|
||||
@spec add_external_urls(map()) :: map()
|
||||
def add_external_urls(data) do
|
||||
updated_results =
|
||||
data["results"]
|
||||
|> generate_gbif_url()
|
||||
|> generate_pfaf_url()
|
||||
|> generate_powo_url()
|
||||
|
||||
Map.put(data, "results", updated_results)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generates a response message from the data. Data is a parsed response from the PlantNet API.
|
||||
"""
|
||||
@spec generate_response_message(map()) :: String.t()
|
||||
def generate_response_message(data) do
|
||||
best_result = hd(data["results"])
|
||||
other_results = tl(data["results"])
|
||||
best_guess_name = best_result["species"]["scientificNameWithoutAuthor"]
|
||||
score = round(best_result["score"] * 100) |> Integer.to_string()
|
||||
|
||||
"""
|
||||
My best guess is **#{best_guess_name}** with a confidence of **#{score}%**. Common names include **#{Enum.join(best_result["species"]["commonNames"], ", ")}**.
|
||||
|
||||
[GBIF](<#{best_result["gbif_url"]}>) | [PFAF](<#{best_result["pfaf_url"]}>) | [POWO](<#{best_result["powo_url"]}>)
|
||||
Threat status: #{best_result["iucn"]["category"]}
|
||||
|
||||
#{get_alternatives(other_results)}
|
||||
"""
|
||||
end
|
||||
|
||||
@spec generate_gbif_url(map()) :: map()
|
||||
defp generate_gbif_url(data) do
|
||||
Enum.map(data, fn result ->
|
||||
gbif_id = result["gbif"]["id"]
|
||||
if gbif_id, do: Map.put(result, "gbif_url", "#{@gbif_base_url}/#{gbif_id}"), else: result
|
||||
end)
|
||||
end
|
||||
|
||||
@spec generate_pfaf_url(map()) :: map()
|
||||
defp generate_pfaf_url(data) do
|
||||
Enum.map(data, fn result ->
|
||||
pfaf_slug = String.replace(result["species"]["scientificNameWithoutAuthor"], " ", "+")
|
||||
|
||||
if pfaf_slug,
|
||||
do: Map.put(result, "pfaf_url", "#{@pfaf_base_url}/#{pfaf_slug}"),
|
||||
else: result
|
||||
end)
|
||||
end
|
||||
|
||||
@spec generate_powo_url(map()) :: map()
|
||||
defp generate_powo_url(data) do
|
||||
Enum.map(data, fn result ->
|
||||
powo_id = result["powo"]["id"]
|
||||
if powo_id, do: Map.put(result, "powo_url", "#{@powo_base_url}/#{powo_id}"), else: result
|
||||
end)
|
||||
end
|
||||
|
||||
defp get_alternatives(data) do
|
||||
if length(data) === 0 do
|
||||
"No alternatives found."
|
||||
else
|
||||
alternatives =
|
||||
Enum.map(data, & &1["species"]["scientificNameWithoutAuthor"])
|
||||
|> Enum.join(", ")
|
||||
|
||||
"Alternatives include **#{alternatives}**."
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,19 +0,0 @@
|
||||
# https://rosettacode.org/wiki/Convert_seconds_to_compound_duration
|
||||
defmodule PlantIdDiscordBot.Utils.Duration do
|
||||
@minute 60
|
||||
@hour @minute * 60
|
||||
@day @hour * 24
|
||||
@week @day * 7
|
||||
@divisor [@week, @day, @hour, @minute, 1]
|
||||
|
||||
def sec_to_str(sec) do
|
||||
{_, [s, m, h, d, w]} =
|
||||
Enum.reduce(@divisor, {sec, []}, fn divisor, {n, acc} ->
|
||||
{rem(n, divisor), [div(n, divisor) | acc]}
|
||||
end)
|
||||
|
||||
["#{w}w", "#{d}d", "#{h}h", "#{m}m", "#{s}s"]
|
||||
|> Enum.reject(fn str -> String.starts_with?(str, "0") end)
|
||||
|> Enum.join(" ")
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user