mirror of
https://github.com/TheRealOwenRees/plantid-discord-bot.git
synced 2026-07-23 04:26:57 +00:00
Feature/projects (#18)
* add up to 5 attachments * multiclause function event handler * fix incorrect Cog calls * add default param as "all" so we can start differentiating the types of identifications in the future - projects / diseases * abstract message sending * overwrite global application commands * pass message from projects to message sending and id functions
This commit is contained in:
@@ -55,6 +55,41 @@ defmodule PlantIdDiscordBot.Consumer.Commands do
|
||||
description: "Search for a project",
|
||||
required: true,
|
||||
autocomplete: true
|
||||
},
|
||||
%{
|
||||
# ATTACHMENT (Image 1 - Required)
|
||||
type: 11,
|
||||
name: "image1",
|
||||
description: "First photo of the plant",
|
||||
required: true
|
||||
},
|
||||
%{
|
||||
# ATTACHMENT (Image 2 - Optional)
|
||||
type: 11,
|
||||
name: "image2",
|
||||
description: "Second photo (optional)",
|
||||
required: false
|
||||
},
|
||||
%{
|
||||
# ATTACHMENT (Image 3 - Optional)
|
||||
type: 11,
|
||||
name: "image3",
|
||||
description: "Third photo (optional)",
|
||||
required: false
|
||||
},
|
||||
%{
|
||||
# ATTACHMENT (Image 4 - Optional)
|
||||
type: 11,
|
||||
name: "image4",
|
||||
description: "Fourth photo (optional)",
|
||||
required: false
|
||||
},
|
||||
%{
|
||||
# ATTACHMENT (Image 5 - Optional)
|
||||
type: 11,
|
||||
name: "image5",
|
||||
description: "Fifth photo (optional)",
|
||||
required: false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+14
-13
@@ -5,6 +5,7 @@ defmodule PlantIdDiscordBot.Consumer do
|
||||
use Nostrum.Consumer
|
||||
alias Nostrum.Api
|
||||
alias PlantIdDiscordBot.{Cog, Consumer}
|
||||
alias PlantIdDiscordBot.PlantNet.Projects
|
||||
|
||||
@global_application_commands Consumer.Commands.global_application_commands()
|
||||
|
||||
@@ -17,9 +18,9 @@ defmodule PlantIdDiscordBot.Consumer do
|
||||
Api.create_guild_application_command(guild_id, cmd)
|
||||
end)
|
||||
|
||||
# Only register global commands in production
|
||||
if Mix.env() == :prod do
|
||||
Api.create_global_application_command(@global_application_commands)
|
||||
# Api.create_global_application_command(@global_application_commands)
|
||||
Api.bulk_overwrite_global_application_commands(@global_application_commands)
|
||||
end
|
||||
|
||||
Api.update_status(:online, "Guess the Plant | /help")
|
||||
@@ -30,17 +31,7 @@ defmodule PlantIdDiscordBot.Consumer do
|
||||
end
|
||||
|
||||
def handle_event({:INTERACTION_CREATE, %{data: %{name: command}} = interaction, _ws_state}) do
|
||||
case command do
|
||||
"source" -> Cog.Info.source(interaction)
|
||||
"invite" -> Cog.Info.invite(interaction)
|
||||
"help" -> Cog.Info.help(interaction)
|
||||
"info" -> Cog.Info.info(interaction)
|
||||
"stats" -> Cog.Info.stats(interaction)
|
||||
"status" -> Cog.Info.status(interaction)
|
||||
"servers" -> Cog.Info.servers(interaction)
|
||||
"diseases" -> Cog.Diseases.diseases(interaction)
|
||||
"projects" -> Cog.Projects.projects(interaction)
|
||||
end
|
||||
handle_interaction(command, interaction)
|
||||
end
|
||||
|
||||
def handle_event({:MESSAGE_CREATE, %{attachments: attachments} = message, _ws_state}) do
|
||||
@@ -49,4 +40,14 @@ defmodule PlantIdDiscordBot.Consumer do
|
||||
Cog.PlantNetMessage.id(message)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_interaction("source", interaction), do: Cog.Info.source(interaction)
|
||||
defp handle_interaction("invite", interaction), do: Cog.Info.invite(interaction)
|
||||
defp handle_interaction("help", interaction), do: Cog.Info.help(interaction)
|
||||
defp handle_interaction("info", interaction), do: Cog.Info.info(interaction)
|
||||
defp handle_interaction("stats", interaction), do: Cog.Info.stats(interaction)
|
||||
defp handle_interaction("status", interaction), do: Cog.Info.status(interaction)
|
||||
defp handle_interaction("servers", interaction), do: Cog.Info.servers(interaction)
|
||||
defp handle_interaction("diseases", interaction), do: Cog.Diseases.diseases(interaction)
|
||||
defp handle_interaction("projects", interaction), do: Cog.Projects.projects(interaction)
|
||||
end
|
||||
|
||||
@@ -18,21 +18,21 @@ defmodule PlantIdDiscordBot.Cog.PlantNetMessage do
|
||||
@plantnet_api_base_url Application.compile_env(:plantid_discord_bot, :plantnet_api_base_url)
|
||||
@max_results Application.compile_env(:plantid_discord_bot, :max_results)
|
||||
|
||||
def id(message) do
|
||||
def id(message, identification_type \\ "all") do
|
||||
case RateLimiter.check_limit(message.guild_id) do
|
||||
{:limit_exceeded, _requests_used, _requests_limit} ->
|
||||
Api.create_message(message.channel_id,
|
||||
content:
|
||||
"This server has exceeded its allowed requests in 24 hours. Please try again tomorrow.",
|
||||
message_reference: %{message_id: message.id}
|
||||
send_message(
|
||||
"This server has exceeded its allowed requests in 24 hours. Please try again tomorrow.",
|
||||
message.channel_id,
|
||||
message.id
|
||||
)
|
||||
|
||||
{:ok, _requests_used, _requests_limit} ->
|
||||
do_identification(message)
|
||||
do_identification(message, identification_type)
|
||||
end
|
||||
end
|
||||
|
||||
def do_identification(message) do
|
||||
def do_identification(message, identification_type) do
|
||||
saved_images =
|
||||
try do
|
||||
Enum.take(message.attachments, 5)
|
||||
@@ -49,13 +49,15 @@ defmodule PlantIdDiscordBot.Cog.PlantNetMessage do
|
||||
content: "An error has occured. Please try again later."
|
||||
)
|
||||
|
||||
# send_message("An error has occured. Please try again later.", message.channel_id)
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
if saved_images do
|
||||
try do
|
||||
prepare_images(saved_images)
|
||||
|> build_query_uri()
|
||||
|> build_query_uri(identification_type)
|
||||
|> get_response(message)
|
||||
rescue
|
||||
e ->
|
||||
@@ -97,12 +99,7 @@ defmodule PlantIdDiscordBot.Cog.PlantNetMessage do
|
||||
RateLimiter.increase_counter(guild_id)
|
||||
Metrics.increase_request_count(guild_id, guild_name)
|
||||
|
||||
# Nostrum.Api.create_message/2 is deprecated but the new function is not available in v0.10 of the library
|
||||
# Nostrum.Api.message/2 will be the new function
|
||||
Api.create_message(message.channel_id,
|
||||
content: response_message,
|
||||
message_reference: %{message_id: message.id}
|
||||
)
|
||||
send_message(response_message, message.channel_id, message.id)
|
||||
|
||||
{:ok, %HTTPoison.Response{status_code: 401, body: body}} ->
|
||||
Logger.critical("Unauthorized request to PlantNet API: #{body}",
|
||||
@@ -110,27 +107,18 @@ defmodule PlantIdDiscordBot.Cog.PlantNetMessage do
|
||||
guild_name: guild_name
|
||||
)
|
||||
|
||||
Api.create_message(message.channel_id,
|
||||
content: "Unauthorized request to PlantNet API.",
|
||||
message_reference: %{message_id: message.id}
|
||||
)
|
||||
send_message("Unauthorizes requesnt to PlantNet API", message.channel_id, message.id)
|
||||
|
||||
{:ok, %HTTPoison.Response{status_code: 404}} ->
|
||||
RateLimiter.increase_counter(guild_id)
|
||||
Metrics.increase_request_count(guild_id, guild_name)
|
||||
|
||||
Api.create_message(message.channel_id,
|
||||
content: "Species Not Found",
|
||||
message_reference: %{message_id: message.id}
|
||||
)
|
||||
send_message("Species Not Found", message.channel_id, message.id)
|
||||
|
||||
{:ok, %HTTPoison.Response{status_code: 429}} ->
|
||||
Logger.warning("Request limit exceeded for the PlantNet API")
|
||||
|
||||
Api.create_message(message.channel_id,
|
||||
content: "Too Many Requests",
|
||||
message_reference: %{message_id: message.id}
|
||||
)
|
||||
send_message("Too Many Requests", message.channel_id, message.id)
|
||||
|
||||
{_, _} ->
|
||||
Logger.error("Internal server error when contacting the PlantNet API",
|
||||
@@ -138,18 +126,27 @@ defmodule PlantIdDiscordBot.Cog.PlantNetMessage do
|
||||
guild_name: guild_name
|
||||
)
|
||||
|
||||
Api.create_message(message.channel_id,
|
||||
content: "Internal Server Error",
|
||||
message_reference: %{message_id: message.id}
|
||||
)
|
||||
send_message("Internal Server Error", message.channel_id, message.id)
|
||||
end
|
||||
end
|
||||
|
||||
@spec build_query_uri([String.t()]) :: String.t()
|
||||
defp build_query_uri(image_filenames) do
|
||||
URI.parse(
|
||||
"#{@plantnet_api_base_url}/identify/all?api-key=#{Application.get_env(:plantid_discord_bot, :plantnet_api_key)}"
|
||||
)
|
||||
@spec build_query_uri([String.t()], String.t()) :: String.t()
|
||||
def build_query_uri(image_filenames, identification_type) do
|
||||
create_base_uri(identification_type)
|
||||
|> add_required_query_params(image_filenames)
|
||||
end
|
||||
|
||||
defp create_base_uri("all"), do: "#{@plantnet_api_base_url}/identify/all"
|
||||
defp create_base_uri("diseases"), do: "#{@plantnet_api_base_url}/diseases/identify"
|
||||
|
||||
defp create_base_uri(%{name: "projects", id: id}),
|
||||
do: "#{@plantnet_api_base_url}/identify/#{id}"
|
||||
|
||||
defp create_base_uri(_), do: "#{@plantnet_api_base_url}/identify/all"
|
||||
|
||||
defp add_required_query_params(base_uri, image_filenames) do
|
||||
URI.parse(base_uri)
|
||||
|> URI.append_query("api-key=#{Application.get_env(:plantid_discord_bot, :plantnet_api_key)}")
|
||||
|> URI.append_query("images=#{Enum.join(image_filenames, "&images=")}")
|
||||
|> URI.append_query("nb-results=#{@max_results}")
|
||||
|> URI.append_query("type=kt")
|
||||
@@ -160,4 +157,20 @@ defmodule PlantIdDiscordBot.Cog.PlantNetMessage do
|
||||
Enum.map(saved_images, fn {:ok, filename} -> filename end)
|
||||
|> File.delete_files!()
|
||||
end
|
||||
|
||||
# Nostrum.Api.create_message/2 is deprecated but the new function is not available in v0.10 of the library
|
||||
# Nostrum.Api.message/2 will be the new function
|
||||
# look at @api.create_message and move to this if needed
|
||||
defp send_message(content, channel_id, message_id \\ nil) do
|
||||
case message_id do
|
||||
nil ->
|
||||
Api.create_message(channel_id, content: content)
|
||||
|
||||
_ ->
|
||||
Api.create_message(channel_id,
|
||||
content: content,
|
||||
message_reference: %{message_id: message_id}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,20 +4,20 @@ defmodule PlantIdDiscordBot.Cog.Projects do
|
||||
"""
|
||||
|
||||
alias PlantIdDiscordBot.PlantNet.Projects
|
||||
alias PlantIdDiscordBot.Cog.PlantNetMessage
|
||||
|
||||
@api Application.compile_env(:plantid_discord_bot, :api)
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 1. AUTOCOMPLETE HANDLER
|
||||
# ---------------------------------------------------------
|
||||
def autocomplete(interaction) do
|
||||
# Extract what the user is typing (may be nil)
|
||||
[%{value: user_input}] = interaction.data.options
|
||||
user_input = user_input || ""
|
||||
options = interaction.data.options || []
|
||||
|
||||
focused_option =
|
||||
Enum.find(options, fn opt -> Map.get(opt, :focused) == true end)
|
||||
|
||||
user_input = (focused_option && focused_option.value) || ""
|
||||
|
||||
# Fetch your project list
|
||||
projects = Projects.retrieve_projects()
|
||||
|
||||
# Filter based on description
|
||||
suggestions =
|
||||
projects
|
||||
|> Enum.filter(fn p ->
|
||||
@@ -36,36 +36,48 @@ defmodule PlantIdDiscordBot.Cog.Projects do
|
||||
}
|
||||
end)
|
||||
|
||||
# Respond with autocomplete choices
|
||||
@api.create_interaction_response(interaction, %{
|
||||
type: 8,
|
||||
data: %{choices: suggestions}
|
||||
})
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 2. FINAL COMMAND EXECUTION
|
||||
# ---------------------------------------------------------
|
||||
def projects(interaction) do
|
||||
# Extract selected project ID
|
||||
project_id =
|
||||
interaction.data.options
|
||||
|> Enum.find(&(&1.name == "project"))
|
||||
|> then(&(&1 && &1.value))
|
||||
|
||||
@api.create_interaction_response(interaction, %{
|
||||
type: 4,
|
||||
data: %{
|
||||
content:
|
||||
case project_id do
|
||||
nil ->
|
||||
"Please choose a project using autocomplete."
|
||||
case project_id do
|
||||
nil ->
|
||||
@api.create_interaction_response(interaction, %{
|
||||
type: 4,
|
||||
data: %{content: "Please choose a project using autocomplete."}
|
||||
})
|
||||
|
||||
id ->
|
||||
# "You selected project: **#{id}**"
|
||||
"Identification by project coming soon."
|
||||
end
|
||||
}
|
||||
})
|
||||
id ->
|
||||
@api.create_interaction_response(interaction, %{
|
||||
type: 4,
|
||||
data: %{content: "Processing images for project **#{id}**..."}
|
||||
})
|
||||
|
||||
resolved = Map.get(interaction.data, :resolved)
|
||||
attachments_map = (resolved && Map.get(resolved, :attachments)) || %{}
|
||||
|
||||
images =
|
||||
interaction.data.options
|
||||
|> Enum.filter(fn opt -> String.starts_with?(opt.name, "image") end)
|
||||
|> Enum.map(fn opt -> Map.get(attachments_map, opt.value) end)
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|
||||
message = %{
|
||||
guild_id: interaction.guild_id,
|
||||
channel_id: interaction.channel_id,
|
||||
id: nil,
|
||||
attachments: images
|
||||
}
|
||||
|
||||
PlantNetMessage.id(message, %{name: "projects", id: id})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user