follow up messages, separation of identification functions

This commit is contained in:
Owen
2024-12-16 22:30:08 +01:00
parent 727706bb2c
commit cfdc5018c2
3 changed files with 78 additions and 22 deletions
+3 -1
View File
@@ -2,6 +2,8 @@ import Config
config :plantid_discord_bot,
image_path: "priv/static",
plantnet_api_base_url: "https://my-api.plantnet.org/v2"
plantnet_api_base_url: "https://my-api.plantnet.org/v2",
score_threshold: 0.3,
max_results: 5
import_config "#{config_env()}.exs"
+70 -21
View File
@@ -3,15 +3,16 @@ defmodule PlantIdDiscordBot.Cog.PlantNet do
alias PlantIdDiscordBot.RateLimiter
alias PlantIdDiscordBot.PlantNet.Parser
alias PlantIdDiscordBot.FileServer.File
alias Nostrum.Api
@api Application.compile_env(:plantid_discord_bot, :api)
@plantnet_api_base_url Application.compile_env(:plantid_discord_bot, :plantnet_api_base_url)
# TEMP: Mock data
@plantnet_raw_response "{\"query\":{\"project\":\"all\",\"images\":[\"https://upload.wikimedia.org/wikipedia/commons/f/ff/Prunus_cerasifera_A.jpg\",\"https://le-jardin-de-pascal.com/2195113-large_default/prunus-cerasifera-atropurpurea-prunier-myrobolan-nigra.jpg\"],\"organs\":[\"auto\",\"auto\"],\"includeRelatedImages\":false,\"noReject\":false},\"language\":\"en\",\"preferedReferential\":\"k-world-flora\",\"bestMatch\":\"Prunus cerasifera Ehrh.\",\"results\":[{\"score\":0.87871,\"species\":{\"scientificNameWithoutAuthor\":\"Prunus cerasifera\",\"scientificNameAuthorship\":\"Ehrh.\",\"genus\":{\"scientificNameWithoutAuthor\":\"Prunus\",\"scientificNameAuthorship\":\"\",\"scientificName\":\"Prunus\"},\"family\":{\"scientificNameWithoutAuthor\":\"Rosaceae\",\"scientificNameAuthorship\":\"\",\"scientificName\":\"Rosaceae\"},\"commonNames\":[\"Cherry plum, myrobalan\",\"Cherry Plum\",\"Purple-leaf Plum\"],\"scientificName\":\"Prunus cerasifera Ehrh.\"},\"gbif\":{\"id\":\"3021730\"},\"powo\":{\"id\":\"729568-1\"},\"iucn\":{\"id\":\"172162\",\"category\":\"DD\"}},{\"score\":0.31668,\"species\":{\"scientificNameWithoutAuthor\":\"Prunus × cistena\",\"scientificNameAuthorship\":\"N.E.Hansen ex Koehne\",\"genus\":{\"scientificNameWithoutAuthor\":\"Prunus\",\"scientificNameAuthorship\":\"\",\"scientificName\":\"Prunus\"},\"family\":{\"scientificNameWithoutAuthor\":\"Rosaceae\",\"scientificNameAuthorship\":\"\",\"scientificName\":\"Rosaceae\"},\"commonNames\":[\"Dwarf red-leaf plum\",\"Purple-leaf sand cherry\",\"Purple-leaved sand cherry\"],\"scientificName\":\"Prunus × cistena N.E.Hansen ex Koehne\"},\"gbif\":{\"id\":\"3022465\"},\"powo\":{\"id\":\"2959315-4\"}},{\"score\":0.01801,\"species\":{\"scientificNameWithoutAuthor\":\"Prunus sargentii\",\"scientificNameAuthorship\":\"Rehder\",\"genus\":{\"scientificNameWithoutAuthor\":\"Prunus\",\"scientificNameAuthorship\":\"\",\"scientificName\":\"Prunus\"},\"family\":{\"scientificNameWithoutAuthor\":\"Rosaceae\",\"scientificNameAuthorship\":\"\",\"scientificName\":\"Rosaceae\"},\"commonNames\":[\"Sargent's cherry\",\"Northern Japanese hill cherry\",\"Sargents cherry\"],\"scientificName\":\"Prunus sargentii Rehder\"},\"gbif\":{\"id\":\"3020955\"},\"powo\":{\"id\":\"730239-1\"},\"iucn\":{\"id\":\"64127603\",\"category\":\"LC\"}},{\"score\":0.00896,\"species\":{\"scientificNameWithoutAuthor\":\"Prunus × yedoensis\",\"scientificNameAuthorship\":\"Matsum.\",\"genus\":{\"scientificNameWithoutAuthor\":\"Prunus\",\"scientificNameAuthorship\":\"\",\"scientificName\":\"Prunus\"},\"family\":{\"scientificNameWithoutAuthor\":\"Rosaceae\",\"scientificNameAuthorship\":\"\",\"scientificName\":\"Rosaceae\"},\"commonNames\":[\"Yoshino cherry\",\"Hybrid cherry\",\"Korean flowering cherry\"],\"scientificName\":\"Prunus × yedoensis Matsum.\"},\"gbif\":{\"id\":\"3021335\"},\"powo\":{\"id\":\"30119904-2\"}},{\"score\":0.00518,\"species\":{\"scientificNameWithoutAuthor\":\"Prunus serrulata\",\"scientificNameAuthorship\":\"Lindl.\",\"genus\":{\"scientificNameWithoutAuthor\":\"Prunus\",\"scientificNameAuthorship\":\"\",\"scientificName\":\"Prunus\"},\"family\":{\"scientificNameWithoutAuthor\":\"Rosaceae\",\"scientificNameAuthorship\":\"\",\"scientificName\":\"Rosaceae\"},\"commonNames\":[\"Japanese flowering cherry\",\"Japanese flowering cherry Kwanzan\",\"Tibetan Cherry\"],\"scientificName\":\"Prunus serrulata Lindl.\"},\"gbif\":{\"id\":\"3022609\"},\"powo\":{\"id\":\"730268-1\"},\"iucn\":{\"id\":\"217170511\",\"category\":\"LC\"}}],\"version\":\"2024-11-19 (7.3)\",\"remainingIdentificationRequests\":488}"
@plantnet_api_key Application.compile_env(:plantid_discord_bot, :plantnet_api_key)
@score_threshold Application.compile_env(:plantid_discord_bot, :score_threshold)
@max_results Application.compile_env(:plantid_discord_bot, :max_results)
@doc """
ID a plant from up to 5 images of organs.
Process /id application command.
"""
def id(interaction) do
case RateLimiter.check_limit(interaction.guild_id) do
@@ -30,41 +31,75 @@ defmodule PlantIdDiscordBot.Cog.PlantNet do
end
defp do_identification(interaction) do
@api.create_interaction_response(interaction, %{
type: 5,
data: %{
content: "Processing..."
}
})
attachment_urls = get_attachment_urls(interaction)
original_images = get_original_images(attachment_urls)
# TODO return image filename list as a list of images to delete
# FUNCTION return image filename list as a list of images to delete
saved_images =
try do
File.download_and_save_files!(attachment_urls)
rescue
e ->
@api.create_interaction_response(interaction, %{
type: 4,
data: %{
content: e.message
}
Api.create_followup_message(interaction.application_id, interaction.token, %{
content: e.message
})
end
IO.inspect(saved_images)
# TODO call PlantNet API and get response, with error handling
# TODO use actual image data
image1 =
"https://upload.wikimedia.org/wikipedia/commons/f/ff/Prunus_cerasifera_A.jpg"
# TODO pass this response to the parser as below, with error handling
image2 =
"https://le-jardin-de-pascal.com/2195113-large_default/prunus-cerasifera-atropurpurea-prunier-myrobolan-nigra.jpg"
# temp data
response_message = Parser.parse(@plantnet_raw_response)
images = [image1, image2]
# TODO increase on success (failed id or otherwise)
# RateLimiter.increase_counter(guild_id)
query_uri = build_query_uri(images)
case HTTPoison.get(query_uri) do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
response_message = Parser.parse(body)
# RateLimiter.increase_counter(guild_id)
Api.create_followup_message(interaction.application_id, interaction.token, %{
content: response_message
})
{:ok, %HTTPoison.Response{status_code: 400}} ->
# TODO add logger
Api.create_followup_message(interaction.application_id, interaction.token, %{
content: "Bad Request"
})
{:ok, %HTTPoison.Response{status_code: 404}} ->
# TODO add logger
# RateLimiter.increase_counter(guild_id)
Api.create_followup_message(interaction.application_id, interaction.token, %{
content: "Species Not Found"
})
{:ok, %HTTPoison.Response{status_code: 429}} ->
# TODO add logger
Api.create_followup_message(interaction.application_id, interaction.token, %{
content: "Too Many Requests"
})
{_, _} ->
# TODO add logger
Api.create_followup_message(interaction.application_id, interaction.token, %{
content: "Internal Server Error"
})
end
# TODO delete saved images
@api.create_interaction_response(interaction, %{
type: 4,
data: %{content: response_message <> "\n#{original_images}"}
})
end
defp get_attachment_urls(interaction) do
@@ -75,4 +110,18 @@ defmodule PlantIdDiscordBot.Cog.PlantNet do
@spec get_original_images([String.t()]) :: String.t()
defp get_original_images(attachment_urls), do: Enum.join(attachment_urls, "\n")
@spec build_query_uri([String.t()]) :: String.t()
defp build_query_uri(image_filenames) do
identify_api_url = "#{@plantnet_api_base_url}/identify/all?api-key=#{@plantnet_api_key}"
query_uri =
URI.append_query(
URI.parse(identify_api_url),
"images=#{Enum.join(image_filenames, "&images=")}"
)
|> URI.append_query("nb-results=#{@max_results}")
|> URI.append_query("type=kt")
|> URI.to_string()
end
end
@@ -56,6 +56,11 @@ defmodule PlantIdDiscordBot.FileServer.File do
|> File.read()
end
@spec delete_file!(String.t()) :: :ok
def delete_file!(filename) do
File.rm!(Path.join(@image_path, filename))
end
@spec download_file!(String.t()) :: binary
defp download_file!(url) do
HTTPoison.get!(url)