mirror of
https://github.com/TheRealOwenRees/plantid-discord-bot.git
synced 2026-07-22 20:16:57 +00:00
Feat/error logs (#16)
* added list of tasks to achieve * update deps * update elixir version in CI/CD pipeline * update todo with branch push checks * underscores separation of large numbers * update elixir version and deps * add basic module docs * remove IO.inspect call * check if list is empty rather than traversing list for length * revert empty list code * duration tests * add mime type tests * use logger instread of inspect, remove todos * replace list length check with Enum.empty * replace list length check with !Enum.empty? * numbers readability fix by using underscores on large numbers * iucn parser replaced case with multiclause function and added test * replace map and join with map_join * fix multiple spec issues where map() must be inside a list
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
defmodule PlantIdDiscordBot.Application do
|
||||
@moduledoc false
|
||||
use Application
|
||||
|
||||
@impl true
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
defmodule PlantIdDiscordBot.Consumer.Commands do
|
||||
@moduledoc """
|
||||
Application commands.
|
||||
"""
|
||||
def global_application_commands do
|
||||
[
|
||||
%{
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
defmodule PlantIdDiscordBot.Metrics.Message do
|
||||
@moduledoc """
|
||||
Metrics messaging.
|
||||
"""
|
||||
def send() do
|
||||
PlantIdDiscordBot.Metrics.requests()
|
||||
|> format_message()
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
defmodule PlantIdDiscordBot.Metrics.Requests do
|
||||
@moduledoc """
|
||||
Basic metrics with usage count per guild.
|
||||
"""
|
||||
use Agent
|
||||
alias PlantIdDiscordBot.Metrics.Requests
|
||||
alias PlantIdDiscordBot.ProcessRegistry
|
||||
|
||||
@@ -6,7 +6,7 @@ defmodule PlantIdDiscordBotTest.Mocks.Nostrum.Api do
|
||||
# def create_message(_channel_id, content), do: {:ok, content}
|
||||
|
||||
def create_message(_channel_id, content) do
|
||||
send(self(), {:create_message, 123456, content})
|
||||
send(self(), {:create_message, 123_456, content})
|
||||
{:ok, content}
|
||||
end
|
||||
|
||||
|
||||
@@ -26,10 +26,15 @@ defmodule PlantIdDiscordBot.Consumer do
|
||||
end
|
||||
|
||||
def handle_event({:MESSAGE_CREATE, %{attachments: attachments} = message, _ws_state}) do
|
||||
if length(attachments) > 0 do
|
||||
# deprecated -> Nostrum.Api.Channel.start_typing/1 in v1.0
|
||||
if !Enum.empty?(attachments) do
|
||||
Api.start_typing!(message.channel_id)
|
||||
Cog.PlantNetMessage.id(message)
|
||||
end
|
||||
|
||||
# if length(attachments) > 0 do
|
||||
# # deprecated -> Nostrum.Api.Channel.start_typing/1 in v1.0
|
||||
# Api.start_typing!(message.channel_id)
|
||||
# Cog.PlantNetMessage.id(message)
|
||||
# end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
defmodule PlantIdDiscordBot.Cog.Info do
|
||||
@moduledoc """
|
||||
Functions for using the /info application command.
|
||||
|
||||
Returns a message embed of application commands available to the user.
|
||||
"""
|
||||
use Nostrum.Consumer
|
||||
import Nostrum.Struct.Embed
|
||||
alias PlantIdDiscordBot.Utils
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
defmodule PlantIdDiscordBot.Cog.PlantNetMessage do
|
||||
@moduledoc """
|
||||
Functions for sending and receiving data from PLantNet
|
||||
"""
|
||||
require Logger
|
||||
|
||||
use Nostrum.Consumer
|
||||
@@ -49,7 +52,6 @@ defmodule PlantIdDiscordBot.Cog.PlantNetMessage do
|
||||
nil
|
||||
end
|
||||
|
||||
# TODO improve composition
|
||||
if saved_images do
|
||||
try do
|
||||
prepare_images(saved_images)
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
defmodule PlantIdDiscordBot.FileServer do
|
||||
@moduledoc """
|
||||
File server for temporary storage of images until a response is made.
|
||||
"""
|
||||
use Plug.Builder
|
||||
|
||||
plug(Plug.Logger)
|
||||
|
||||
@@ -2,6 +2,8 @@ defmodule PlantIdDiscordBot.FileServer.File do
|
||||
@moduledoc """
|
||||
File utilities
|
||||
"""
|
||||
require Logger
|
||||
|
||||
alias PlantIdDiscordBot.FileServer.ImageConverter
|
||||
|
||||
@image_path Application.compile_env(:plantid_discord_bot, :image_path)
|
||||
@@ -56,7 +58,6 @@ defmodule PlantIdDiscordBot.FileServer.File do
|
||||
|> File.read()
|
||||
end
|
||||
|
||||
# TODO make into a task for async deletion and deal with errors
|
||||
@spec delete_files!([String.t()]) :: :ok
|
||||
def delete_files!(filenames) do
|
||||
tasks =
|
||||
@@ -65,8 +66,7 @@ defmodule PlantIdDiscordBot.FileServer.File do
|
||||
try do
|
||||
File.rm!(Path.join(@image_path, filename))
|
||||
rescue
|
||||
# TODO Logger
|
||||
e -> IO.inspect(e)
|
||||
e -> Logger.error(e)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
defmodule PlantIdDiscordBot.Guild do
|
||||
@moduledoc """
|
||||
Guild related functions.
|
||||
"""
|
||||
alias Nostrum.Cache.GuildCache
|
||||
|
||||
def get_guild_name!(guild_id) do
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
defmodule PlantIdDiscordBot.ProcessRegistry do
|
||||
@moduledoc """
|
||||
Process registry.
|
||||
"""
|
||||
def start_link do
|
||||
Registry.start_link(keys: :unique, name: __MODULE__)
|
||||
end
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
defmodule PlantIdDiscordBot.RateLimiter do
|
||||
@moduledoc """
|
||||
Basic rate limiter to prevent one guild from calling too many requests in 24 hours.
|
||||
|
||||
Custom limits have been set for some guilds that are trusted.
|
||||
"""
|
||||
|
||||
use GenServer
|
||||
require Logger
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
defmodule PlantIdDiscordBot.Utils do
|
||||
@moduledoc """
|
||||
Bot related utility functions.
|
||||
"""
|
||||
def get_uptime() do
|
||||
start_time = Application.get_env(:plantid_discord_bot, :start_time)
|
||||
|
||||
|
||||
+32
-21
@@ -33,7 +33,7 @@ defmodule PlantIdDiscordBot.PlantNet.Parser do
|
||||
@doc """
|
||||
Parses the response from the PlantNet API into a map.
|
||||
"""
|
||||
@spec parse(String.t()) :: map()
|
||||
@spec parse(String.t()) :: String.t()
|
||||
def parse(response) do
|
||||
response
|
||||
|> to_map!()
|
||||
@@ -85,7 +85,7 @@ defmodule PlantIdDiscordBot.PlantNet.Parser do
|
||||
"My best guess is **#{best_guess_name}** with a confidence of **#{score}%**.#{get_common_names(best_result)}\n\nSpecies info from plant databases:\n[GBIF](<#{best_result["gbif_url"]}>) | [PFAF](<#{best_result["pfaf_url"]}>) | [POWO](<#{best_result["powo_url"]}>)#{if best_result_iucn_category, do: "\n\nConservation status: #{iucn_parser(best_result_iucn_category)}"}#{get_alternatives(other_results)}"
|
||||
end
|
||||
|
||||
@spec generate_gbif_url(map()) :: map()
|
||||
@spec generate_gbif_url([map()]) :: [map()]
|
||||
defp generate_gbif_url(data) do
|
||||
Enum.map(data, fn result ->
|
||||
gbif_id = result["gbif"]["id"]
|
||||
@@ -93,7 +93,7 @@ defmodule PlantIdDiscordBot.PlantNet.Parser do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec generate_pfaf_url(map()) :: map()
|
||||
@spec generate_pfaf_url([map()]) :: [map()]
|
||||
defp generate_pfaf_url(data) do
|
||||
Enum.map(data, fn result ->
|
||||
pfaf_slug = String.replace(result["species"]["scientificNameWithoutAuthor"], " ", "+")
|
||||
@@ -104,7 +104,7 @@ defmodule PlantIdDiscordBot.PlantNet.Parser do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec generate_powo_url(map()) :: map()
|
||||
@spec generate_powo_url([map()]) :: [map()]
|
||||
defp generate_powo_url(data) do
|
||||
Enum.map(data, fn result ->
|
||||
powo_id = result["powo"]["id"]
|
||||
@@ -123,29 +123,40 @@ defmodule PlantIdDiscordBot.PlantNet.Parser do
|
||||
end
|
||||
|
||||
@spec iucn_parser(String.t()) :: String.t()
|
||||
defp iucn_parser(abbreviation) do
|
||||
case abbreviation do
|
||||
"DD" -> "Data Deficient"
|
||||
"LC" -> "Least Concern"
|
||||
"NT" -> "Near Threatened"
|
||||
"VU" -> "Vulnerable"
|
||||
"EN" -> "Endangered"
|
||||
"CR" -> "Critically Endangered"
|
||||
"EW" -> "Extinct in the Wild"
|
||||
"EX" -> "Extinct"
|
||||
"NE" -> "Not Evaluated"
|
||||
_ -> "Unknown"
|
||||
end
|
||||
end
|
||||
def iucn_parser("DD"), do: "Data Deficient"
|
||||
def iucn_parser("LC"), do: "Least Concern"
|
||||
def iucn_parser("NT"), do: "Near Threatened"
|
||||
def iucn_parser("VU"), do: "Vulnerable"
|
||||
def iucn_parser("EN"), do: "Endangered"
|
||||
def iucn_parser("CR"), do: "Critically Endangered"
|
||||
def iucn_parser("EW"), do: "Extinct in the Wild"
|
||||
def iucn_parser("EX"), do: "Extinct"
|
||||
def iucn_parser("NE"), do: "Not Evaluated"
|
||||
def iucn_parser(_), do: "Unknown"
|
||||
|
||||
@spec get_alternatives(map()) :: String.t()
|
||||
defp get_alternatives(data) do
|
||||
if length(data) > 0 do
|
||||
if !Enum.empty?(data) do
|
||||
alternatives =
|
||||
Enum.map(data, & &1["species"]["scientificNameWithoutAuthor"])
|
||||
|> Enum.join(", ")
|
||||
Enum.map_join(data, ", ", & &1["species"]["scientificNameWithoutAuthor"])
|
||||
|
||||
"\n\nAlternatives include **#{alternatives}**."
|
||||
end
|
||||
|
||||
# if !Enum.empty?(data) do
|
||||
# alternatives =
|
||||
# Enum.map(data, & &1["species"]["scientificNameWithoutAuthor"])
|
||||
# |> Enum.join(", ")
|
||||
|
||||
# "\n\nAlternatives include **#{alternatives}**."
|
||||
# end
|
||||
|
||||
# if length(data) > 0 do
|
||||
# alternatives =
|
||||
# Enum.map(data, & &1["species"]["scientificNameWithoutAuthor"])
|
||||
# |> Enum.join(", ")
|
||||
|
||||
# "\n\nAlternatives include **#{alternatives}**."
|
||||
# end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
defmodule PlantIdDiscordBot.Scheduler do
|
||||
@moduledoc """
|
||||
CRON job scheduler.
|
||||
"""
|
||||
use Quantum, otp_app: :plantid_discord_bot
|
||||
end
|
||||
|
||||
+11
-1
@@ -1,11 +1,21 @@
|
||||
# https://rosettacode.org/wiki/Convert_seconds_to_compound_duration
|
||||
defmodule PlantIdDiscordBot.Utils.Duration do
|
||||
@moduledoc """
|
||||
Duration conversion functions.
|
||||
"""
|
||||
@minute 60
|
||||
@hour @minute * 60
|
||||
@day @hour * 24
|
||||
@week @day * 7
|
||||
@divisor [@week, @day, @hour, @minute, 1]
|
||||
|
||||
@doc """
|
||||
Convert seconds to a time string.
|
||||
# https://rosettacode.org/wiki/Convert_seconds_to_compound_duration
|
||||
|
||||
## Examples
|
||||
iex> PlantIdDiscordBot.Utils.Duration.sec_to_str(65)
|
||||
iex> "1m 5s"
|
||||
"""
|
||||
def sec_to_str(sec) do
|
||||
{_, [s, m, h, d, w]} =
|
||||
Enum.reduce(@divisor, {sec, []}, fn divisor, {n, acc} ->
|
||||
|
||||
Reference in New Issue
Block a user