From ab27b2f1ed60385050d2dcfa1963a3a05077c943 Mon Sep 17 00:00:00 2001 From: AI Date: Sun, 12 Jul 2026 00:04:07 +0530 Subject: [PATCH 1/3] feat: Add Telegram Group Management prisms --- .../group_management/ban_chat_member.ex | 87 +++++++++++++++++ .../group_management/pin_chat_message.ex | 87 +++++++++++++++++ .../group_management/promote_chat_member.ex | 91 ++++++++++++++++++ .../group_management/restrict_chat_member.ex | 93 +++++++++++++++++++ .../group_management/set_chat_description.ex | 81 ++++++++++++++++ .../group_management/set_chat_permissions.ex | 83 +++++++++++++++++ .../group_management/set_chat_title.ex | 83 +++++++++++++++++ .../group_management/unban_chat_member.ex | 87 +++++++++++++++++ .../unpin_all_chat_messages.ex | 77 +++++++++++++++ .../group_management/unpin_chat_message.ex | 81 ++++++++++++++++ .../group_management/ban_chat_member_test.exs | 84 +++++++++++++++++ .../pin_chat_message_test.exs | 84 +++++++++++++++++ .../promote_chat_member_test.exs | 84 +++++++++++++++++ .../restrict_chat_member_test.exs | 87 +++++++++++++++++ .../set_chat_description_test.exs | 81 ++++++++++++++++ .../set_chat_permissions_test.exs | 84 +++++++++++++++++ .../group_management/set_chat_title_test.exs | 84 +++++++++++++++++ .../unban_chat_member_test.exs | 84 +++++++++++++++++ .../unpin_all_chat_messages_test.exs | 81 ++++++++++++++++ .../unpin_chat_message_test.exs | 81 ++++++++++++++++ 20 files changed, 1684 insertions(+) create mode 100644 lux/lib/lux/prisms/telegram/group_management/ban_chat_member.ex create mode 100644 lux/lib/lux/prisms/telegram/group_management/pin_chat_message.ex create mode 100644 lux/lib/lux/prisms/telegram/group_management/promote_chat_member.ex create mode 100644 lux/lib/lux/prisms/telegram/group_management/restrict_chat_member.ex create mode 100644 lux/lib/lux/prisms/telegram/group_management/set_chat_description.ex create mode 100644 lux/lib/lux/prisms/telegram/group_management/set_chat_permissions.ex create mode 100644 lux/lib/lux/prisms/telegram/group_management/set_chat_title.ex create mode 100644 lux/lib/lux/prisms/telegram/group_management/unban_chat_member.ex create mode 100644 lux/lib/lux/prisms/telegram/group_management/unpin_all_chat_messages.ex create mode 100644 lux/lib/lux/prisms/telegram/group_management/unpin_chat_message.ex create mode 100644 lux/test/unit/lux/prisms/telegram/group_management/ban_chat_member_test.exs create mode 100644 lux/test/unit/lux/prisms/telegram/group_management/pin_chat_message_test.exs create mode 100644 lux/test/unit/lux/prisms/telegram/group_management/promote_chat_member_test.exs create mode 100644 lux/test/unit/lux/prisms/telegram/group_management/restrict_chat_member_test.exs create mode 100644 lux/test/unit/lux/prisms/telegram/group_management/set_chat_description_test.exs create mode 100644 lux/test/unit/lux/prisms/telegram/group_management/set_chat_permissions_test.exs create mode 100644 lux/test/unit/lux/prisms/telegram/group_management/set_chat_title_test.exs create mode 100644 lux/test/unit/lux/prisms/telegram/group_management/unban_chat_member_test.exs create mode 100644 lux/test/unit/lux/prisms/telegram/group_management/unpin_all_chat_messages_test.exs create mode 100644 lux/test/unit/lux/prisms/telegram/group_management/unpin_chat_message_test.exs diff --git a/lux/lib/lux/prisms/telegram/group_management/ban_chat_member.ex b/lux/lib/lux/prisms/telegram/group_management/ban_chat_member.ex new file mode 100644 index 000000000..3b7274976 --- /dev/null +++ b/lux/lib/lux/prisms/telegram/group_management/ban_chat_member.ex @@ -0,0 +1,87 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.BanChatMember do + @moduledoc """ + A prism for bans a member from a chat/group via the Telegram Bot API. + + ## Examples + + iex> BanChatMember.handler(%{ + ...> chat_id: 123_456_789, +...> user_id: 987_654 + ...> }, %{name: "Agent"}) + {:ok, %{success: true, chat_id: 123_456_789}} + """ + + use Lux.Prism, + name: "Ban Chat Member", + description: "Bans a member from a chat/group", + input_schema: %{ + type: :object, + properties: %{ + chat_id: %{ + type: [:string, :integer], + description: "Unique identifier for the target chat" + }, + user_id: %{ + type: :integer, + description: "Unique identifier of the target user" + }, + until_date: %{ + type: :integer, + description: "Date when the user will be unbanned, unix time" + } + }, + required: ["chat_id", "user_id"] + }, + output_schema: %{ + type: :object, + properties: %{ + success: %{ + type: :boolean, + description: "Whether the operation was successful" + }, + chat_id: %{ + type: [:string, :integer], + description: "Identifier of the target chat" + } + }, + required: ["success", "chat_id"] + } + + alias Lux.Integrations.Telegram.Client + require Logger + + def handler(params, agent) do + with {:ok, chat_id} <- validate_param(params, :chat_id), + {:ok, _} <- validate_param(params, :user_id) do + agent_name = agent[:name] || "Unknown Agent" + Logger.info("Agent #{agent_name} attempting to ban member in chat #{chat_id}") + + request_body = Map.take(params, [:chat_id, :user_id, :until_date, :plug]) + request_opts = %{json: request_body} + + case Client.request(:post, "/banChatMember", request_opts) do + {:ok, %{"result" => true}} -> + Logger.info("Successfully completed ban member in chat #{chat_id}") + {:ok, %{success: true, chat_id: chat_id}} + + {:error, {status, %{"description" => description}}} -> + {:error, "Failed to ban member: #{description} (HTTP #{status})"} + + {:error, {status, description}} when is_binary(description) -> + {:error, "Failed to ban member: #{description} (HTTP #{status})"} + + {:error, error} -> + {:error, "Failed to ban member: #{inspect(error)}"} + end + end + end + + defp validate_param(params, key) do + case Map.fetch(params, key) do + {:ok, value} when is_binary(value) and value != "" -> {:ok, value} + {:ok, value} when is_integer(value) -> {:ok, value} + {:ok, value} when is_map(value) -> {:ok, value} + _ -> {:error, "Missing or invalid #{key}"} + end + end +end diff --git a/lux/lib/lux/prisms/telegram/group_management/pin_chat_message.ex b/lux/lib/lux/prisms/telegram/group_management/pin_chat_message.ex new file mode 100644 index 000000000..147f0fbbf --- /dev/null +++ b/lux/lib/lux/prisms/telegram/group_management/pin_chat_message.ex @@ -0,0 +1,87 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.PinChatMessage do + @moduledoc """ + A prism for adds a message to the list of pinned messages in a chat via the Telegram Bot API. + + ## Examples + + iex> PinChatMessage.handler(%{ + ...> chat_id: 123_456_789, +...> message_id: 42 + ...> }, %{name: "Agent"}) + {:ok, %{success: true, chat_id: 123_456_789}} + """ + + use Lux.Prism, + name: "Pin Chat Message", + description: "Adds a message to the list of pinned messages in a chat", + input_schema: %{ + type: :object, + properties: %{ + chat_id: %{ + type: [:string, :integer], + description: "Unique identifier for the target chat" + }, + message_id: %{ + type: :integer, + description: "Identifier of a message to pin" + }, + disable_notification: %{ + type: :boolean, + description: "Pass True, if it is not necessary to send a notification to all chat members about the new pinned message" + } + }, + required: ["chat_id", "message_id"] + }, + output_schema: %{ + type: :object, + properties: %{ + success: %{ + type: :boolean, + description: "Whether the operation was successful" + }, + chat_id: %{ + type: [:string, :integer], + description: "Identifier of the target chat" + } + }, + required: ["success", "chat_id"] + } + + alias Lux.Integrations.Telegram.Client + require Logger + + def handler(params, agent) do + with {:ok, chat_id} <- validate_param(params, :chat_id), + {:ok, _} <- validate_param(params, :message_id) do + agent_name = agent[:name] || "Unknown Agent" + Logger.info("Agent #{agent_name} attempting to pin chat message in chat #{chat_id}") + + request_body = Map.take(params, [:chat_id, :message_id, :disable_notification, :plug]) + request_opts = %{json: request_body} + + case Client.request(:post, "/pinChatMessage", request_opts) do + {:ok, %{"result" => true}} -> + Logger.info("Successfully completed pin chat message in chat #{chat_id}") + {:ok, %{success: true, chat_id: chat_id}} + + {:error, {status, %{"description" => description}}} -> + {:error, "Failed to pin chat message: #{description} (HTTP #{status})"} + + {:error, {status, description}} when is_binary(description) -> + {:error, "Failed to pin chat message: #{description} (HTTP #{status})"} + + {:error, error} -> + {:error, "Failed to pin chat message: #{inspect(error)}"} + end + end + end + + defp validate_param(params, key) do + case Map.fetch(params, key) do + {:ok, value} when is_binary(value) and value != "" -> {:ok, value} + {:ok, value} when is_integer(value) -> {:ok, value} + {:ok, value} when is_map(value) -> {:ok, value} + _ -> {:error, "Missing or invalid #{key}"} + end + end +end diff --git a/lux/lib/lux/prisms/telegram/group_management/promote_chat_member.ex b/lux/lib/lux/prisms/telegram/group_management/promote_chat_member.ex new file mode 100644 index 000000000..566d46267 --- /dev/null +++ b/lux/lib/lux/prisms/telegram/group_management/promote_chat_member.ex @@ -0,0 +1,91 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.PromoteChatMember do + @moduledoc """ + A prism for promotes or demotes a user in a supergroup or a channel via the Telegram Bot API. + + ## Examples + + iex> PromoteChatMember.handler(%{ + ...> chat_id: 123_456_789, +...> user_id: 987_654 + ...> }, %{name: "Agent"}) + {:ok, %{success: true, chat_id: 123_456_789}} + """ + + use Lux.Prism, + name: "Promote Chat Member", + description: "Promotes or demotes a user in a supergroup or a channel", + input_schema: %{ + type: :object, + properties: %{ + chat_id: %{ + type: [:string, :integer], + description: "Unique identifier for the target chat" + }, + user_id: %{ + type: :integer, + description: "Unique identifier of the target user" + }, + is_anonymous: %{ + type: :boolean, + description: "Pass True, if the administrator's presence in the chat is hidden" + }, + can_manage_chat: %{ + type: :boolean, + description: "Pass True, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode." + } + }, + required: ["chat_id", "user_id"] + }, + output_schema: %{ + type: :object, + properties: %{ + success: %{ + type: :boolean, + description: "Whether the operation was successful" + }, + chat_id: %{ + type: [:string, :integer], + description: "Identifier of the target chat" + } + }, + required: ["success", "chat_id"] + } + + alias Lux.Integrations.Telegram.Client + require Logger + + def handler(params, agent) do + with {:ok, chat_id} <- validate_param(params, :chat_id), + {:ok, _} <- validate_param(params, :user_id) do + agent_name = agent[:name] || "Unknown Agent" + Logger.info("Agent #{agent_name} attempting to promote member in chat #{chat_id}") + + request_body = Map.take(params, [:chat_id, :user_id, :is_anonymous, :can_manage_chat, :plug]) + request_opts = %{json: request_body} + + case Client.request(:post, "/promoteChatMember", request_opts) do + {:ok, %{"result" => true}} -> + Logger.info("Successfully completed promote member in chat #{chat_id}") + {:ok, %{success: true, chat_id: chat_id}} + + {:error, {status, %{"description" => description}}} -> + {:error, "Failed to promote member: #{description} (HTTP #{status})"} + + {:error, {status, description}} when is_binary(description) -> + {:error, "Failed to promote member: #{description} (HTTP #{status})"} + + {:error, error} -> + {:error, "Failed to promote member: #{inspect(error)}"} + end + end + end + + defp validate_param(params, key) do + case Map.fetch(params, key) do + {:ok, value} when is_binary(value) and value != "" -> {:ok, value} + {:ok, value} when is_integer(value) -> {:ok, value} + {:ok, value} when is_map(value) -> {:ok, value} + _ -> {:error, "Missing or invalid #{key}"} + end + end +end diff --git a/lux/lib/lux/prisms/telegram/group_management/restrict_chat_member.ex b/lux/lib/lux/prisms/telegram/group_management/restrict_chat_member.ex new file mode 100644 index 000000000..df59a7ce3 --- /dev/null +++ b/lux/lib/lux/prisms/telegram/group_management/restrict_chat_member.ex @@ -0,0 +1,93 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.RestrictChatMember do + @moduledoc """ + A prism for restricts a member in a supergroup via the Telegram Bot API. + + ## Examples + + iex> RestrictChatMember.handler(%{ + ...> chat_id: 123_456_789, +...> user_id: 987_654, +...> permissions: %{can_send_messages: true} + ...> }, %{name: "Agent"}) + {:ok, %{success: true, chat_id: 123_456_789}} + """ + + use Lux.Prism, + name: "Restrict Chat Member", + description: "Restricts a member in a supergroup", + input_schema: %{ + type: :object, + properties: %{ + chat_id: %{ + type: [:string, :integer], + description: "Unique identifier for the target chat" + }, + user_id: %{ + type: :integer, + description: "Unique identifier of the target user" + }, + permissions: %{ + type: :object, + description: "An object for new user permissions" + }, + until_date: %{ + type: :integer, + description: "Date when restrictions will be lifted" + } + }, + required: ["chat_id", "user_id", "permissions"] + }, + output_schema: %{ + type: :object, + properties: %{ + success: %{ + type: :boolean, + description: "Whether the operation was successful" + }, + chat_id: %{ + type: [:string, :integer], + description: "Identifier of the target chat" + } + }, + required: ["success", "chat_id"] + } + + alias Lux.Integrations.Telegram.Client + require Logger + + def handler(params, agent) do + with {:ok, chat_id} <- validate_param(params, :chat_id), + {:ok, _} <- validate_param(params, :user_id), + {:ok, _} <- validate_param(params, :permissions) do + agent_name = agent[:name] || "Unknown Agent" + Logger.info("Agent #{agent_name} attempting to restrict member in chat #{chat_id}") + + request_body = Map.take(params, [:chat_id, :user_id, :permissions, :until_date, :plug]) + request_opts = %{json: request_body} + + case Client.request(:post, "/restrictChatMember", request_opts) do + {:ok, %{"result" => true}} -> + Logger.info("Successfully completed restrict member in chat #{chat_id}") + {:ok, %{success: true, chat_id: chat_id}} + + {:error, {status, %{"description" => description}}} -> + {:error, "Failed to restrict member: #{description} (HTTP #{status})"} + + {:error, {status, description}} when is_binary(description) -> + {:error, "Failed to restrict member: #{description} (HTTP #{status})"} + + {:error, error} -> + {:error, "Failed to restrict member: #{inspect(error)}"} + end + end + end + + defp validate_param(params, key) do + case Map.fetch(params, key) do + {:ok, value} when is_binary(value) and value != "" -> {:ok, value} + {:ok, value} when is_integer(value) -> {:ok, value} + {:ok, value} when is_map(value) -> {:ok, value} + _ -> {:error, "Missing or invalid #{key}"} + end + end +end diff --git a/lux/lib/lux/prisms/telegram/group_management/set_chat_description.ex b/lux/lib/lux/prisms/telegram/group_management/set_chat_description.ex new file mode 100644 index 000000000..611abb507 --- /dev/null +++ b/lux/lib/lux/prisms/telegram/group_management/set_chat_description.ex @@ -0,0 +1,81 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.SetChatDescription do + @moduledoc """ + A prism for changes the description of a chat via the Telegram Bot API. + + ## Examples + + iex> SetChatDescription.handler(%{ + ...> chat_id: 123_456_789 + ...> }, %{name: "Agent"}) + {:ok, %{success: true, chat_id: 123_456_789}} + """ + + use Lux.Prism, + name: "Set Chat Description", + description: "Changes the description of a chat", + input_schema: %{ + type: :object, + properties: %{ + chat_id: %{ + type: [:string, :integer], + description: "Unique identifier for the target chat" + }, + description: %{ + type: :string, + description: "New chat description, 0-255 characters" + } + }, + required: ["chat_id"] + }, + output_schema: %{ + type: :object, + properties: %{ + success: %{ + type: :boolean, + description: "Whether the operation was successful" + }, + chat_id: %{ + type: [:string, :integer], + description: "Identifier of the target chat" + } + }, + required: ["success", "chat_id"] + } + + alias Lux.Integrations.Telegram.Client + require Logger + + def handler(params, agent) do + with {:ok, chat_id} <- validate_param(params, :chat_id) do + agent_name = agent[:name] || "Unknown Agent" + Logger.info("Agent #{agent_name} attempting to set chat description in chat #{chat_id}") + + request_body = Map.take(params, [:chat_id, :description, :plug]) + request_opts = %{json: request_body} + + case Client.request(:post, "/setChatDescription", request_opts) do + {:ok, %{"result" => true}} -> + Logger.info("Successfully completed set chat description in chat #{chat_id}") + {:ok, %{success: true, chat_id: chat_id}} + + {:error, {status, %{"description" => description}}} -> + {:error, "Failed to set chat description: #{description} (HTTP #{status})"} + + {:error, {status, description}} when is_binary(description) -> + {:error, "Failed to set chat description: #{description} (HTTP #{status})"} + + {:error, error} -> + {:error, "Failed to set chat description: #{inspect(error)}"} + end + end + end + + defp validate_param(params, key) do + case Map.fetch(params, key) do + {:ok, value} when is_binary(value) and value != "" -> {:ok, value} + {:ok, value} when is_integer(value) -> {:ok, value} + {:ok, value} when is_map(value) -> {:ok, value} + _ -> {:error, "Missing or invalid #{key}"} + end + end +end diff --git a/lux/lib/lux/prisms/telegram/group_management/set_chat_permissions.ex b/lux/lib/lux/prisms/telegram/group_management/set_chat_permissions.ex new file mode 100644 index 000000000..6070ab0a9 --- /dev/null +++ b/lux/lib/lux/prisms/telegram/group_management/set_chat_permissions.ex @@ -0,0 +1,83 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.SetChatPermissions do + @moduledoc """ + A prism for sets default chat permissions for all members via the Telegram Bot API. + + ## Examples + + iex> SetChatPermissions.handler(%{ + ...> chat_id: 123_456_789, +...> permissions: %{can_send_messages: true} + ...> }, %{name: "Agent"}) + {:ok, %{success: true, chat_id: 123_456_789}} + """ + + use Lux.Prism, + name: "Set Chat Permissions", + description: "Sets default chat permissions for all members", + input_schema: %{ + type: :object, + properties: %{ + chat_id: %{ + type: [:string, :integer], + description: "Unique identifier for the target chat" + }, + permissions: %{ + type: :object, + description: "New default chat permissions" + } + }, + required: ["chat_id", "permissions"] + }, + output_schema: %{ + type: :object, + properties: %{ + success: %{ + type: :boolean, + description: "Whether the operation was successful" + }, + chat_id: %{ + type: [:string, :integer], + description: "Identifier of the target chat" + } + }, + required: ["success", "chat_id"] + } + + alias Lux.Integrations.Telegram.Client + require Logger + + def handler(params, agent) do + with {:ok, chat_id} <- validate_param(params, :chat_id), + {:ok, _} <- validate_param(params, :permissions) do + agent_name = agent[:name] || "Unknown Agent" + Logger.info("Agent #{agent_name} attempting to set chat permissions in chat #{chat_id}") + + request_body = Map.take(params, [:chat_id, :permissions, :plug]) + request_opts = %{json: request_body} + + case Client.request(:post, "/setChatPermissions", request_opts) do + {:ok, %{"result" => true}} -> + Logger.info("Successfully completed set chat permissions in chat #{chat_id}") + {:ok, %{success: true, chat_id: chat_id}} + + {:error, {status, %{"description" => description}}} -> + {:error, "Failed to set chat permissions: #{description} (HTTP #{status})"} + + {:error, {status, description}} when is_binary(description) -> + {:error, "Failed to set chat permissions: #{description} (HTTP #{status})"} + + {:error, error} -> + {:error, "Failed to set chat permissions: #{inspect(error)}"} + end + end + end + + defp validate_param(params, key) do + case Map.fetch(params, key) do + {:ok, value} when is_binary(value) and value != "" -> {:ok, value} + {:ok, value} when is_integer(value) -> {:ok, value} + {:ok, value} when is_map(value) -> {:ok, value} + _ -> {:error, "Missing or invalid #{key}"} + end + end +end diff --git a/lux/lib/lux/prisms/telegram/group_management/set_chat_title.ex b/lux/lib/lux/prisms/telegram/group_management/set_chat_title.ex new file mode 100644 index 000000000..1bc5b77f8 --- /dev/null +++ b/lux/lib/lux/prisms/telegram/group_management/set_chat_title.ex @@ -0,0 +1,83 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.SetChatTitle do + @moduledoc """ + A prism for changes the title of a chat via the Telegram Bot API. + + ## Examples + + iex> SetChatTitle.handler(%{ + ...> chat_id: 123_456_789, +...> title: "New Title" + ...> }, %{name: "Agent"}) + {:ok, %{success: true, chat_id: 123_456_789}} + """ + + use Lux.Prism, + name: "Set Chat Title", + description: "Changes the title of a chat", + input_schema: %{ + type: :object, + properties: %{ + chat_id: %{ + type: [:string, :integer], + description: "Unique identifier for the target chat" + }, + title: %{ + type: :string, + description: "New chat title, 1-128 characters" + } + }, + required: ["chat_id", "title"] + }, + output_schema: %{ + type: :object, + properties: %{ + success: %{ + type: :boolean, + description: "Whether the operation was successful" + }, + chat_id: %{ + type: [:string, :integer], + description: "Identifier of the target chat" + } + }, + required: ["success", "chat_id"] + } + + alias Lux.Integrations.Telegram.Client + require Logger + + def handler(params, agent) do + with {:ok, chat_id} <- validate_param(params, :chat_id), + {:ok, _} <- validate_param(params, :title) do + agent_name = agent[:name] || "Unknown Agent" + Logger.info("Agent #{agent_name} attempting to set chat title in chat #{chat_id}") + + request_body = Map.take(params, [:chat_id, :title, :plug]) + request_opts = %{json: request_body} + + case Client.request(:post, "/setChatTitle", request_opts) do + {:ok, %{"result" => true}} -> + Logger.info("Successfully completed set chat title in chat #{chat_id}") + {:ok, %{success: true, chat_id: chat_id}} + + {:error, {status, %{"description" => description}}} -> + {:error, "Failed to set chat title: #{description} (HTTP #{status})"} + + {:error, {status, description}} when is_binary(description) -> + {:error, "Failed to set chat title: #{description} (HTTP #{status})"} + + {:error, error} -> + {:error, "Failed to set chat title: #{inspect(error)}"} + end + end + end + + defp validate_param(params, key) do + case Map.fetch(params, key) do + {:ok, value} when is_binary(value) and value != "" -> {:ok, value} + {:ok, value} when is_integer(value) -> {:ok, value} + {:ok, value} when is_map(value) -> {:ok, value} + _ -> {:error, "Missing or invalid #{key}"} + end + end +end diff --git a/lux/lib/lux/prisms/telegram/group_management/unban_chat_member.ex b/lux/lib/lux/prisms/telegram/group_management/unban_chat_member.ex new file mode 100644 index 000000000..ccab6a86d --- /dev/null +++ b/lux/lib/lux/prisms/telegram/group_management/unban_chat_member.ex @@ -0,0 +1,87 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.UnbanChatMember do + @moduledoc """ + A prism for unbans a previously banned member in a chat/group via the Telegram Bot API. + + ## Examples + + iex> UnbanChatMember.handler(%{ + ...> chat_id: 123_456_789, +...> user_id: 987_654 + ...> }, %{name: "Agent"}) + {:ok, %{success: true, chat_id: 123_456_789}} + """ + + use Lux.Prism, + name: "Unban Chat Member", + description: "Unbans a previously banned member in a chat/group", + input_schema: %{ + type: :object, + properties: %{ + chat_id: %{ + type: [:string, :integer], + description: "Unique identifier for the target chat" + }, + user_id: %{ + type: :integer, + description: "Unique identifier of the target user" + }, + only_if_banned: %{ + type: :boolean, + description: "Do nothing if the user is not banned" + } + }, + required: ["chat_id", "user_id"] + }, + output_schema: %{ + type: :object, + properties: %{ + success: %{ + type: :boolean, + description: "Whether the operation was successful" + }, + chat_id: %{ + type: [:string, :integer], + description: "Identifier of the target chat" + } + }, + required: ["success", "chat_id"] + } + + alias Lux.Integrations.Telegram.Client + require Logger + + def handler(params, agent) do + with {:ok, chat_id} <- validate_param(params, :chat_id), + {:ok, _} <- validate_param(params, :user_id) do + agent_name = agent[:name] || "Unknown Agent" + Logger.info("Agent #{agent_name} attempting to unban member in chat #{chat_id}") + + request_body = Map.take(params, [:chat_id, :user_id, :only_if_banned, :plug]) + request_opts = %{json: request_body} + + case Client.request(:post, "/unbanChatMember", request_opts) do + {:ok, %{"result" => true}} -> + Logger.info("Successfully completed unban member in chat #{chat_id}") + {:ok, %{success: true, chat_id: chat_id}} + + {:error, {status, %{"description" => description}}} -> + {:error, "Failed to unban member: #{description} (HTTP #{status})"} + + {:error, {status, description}} when is_binary(description) -> + {:error, "Failed to unban member: #{description} (HTTP #{status})"} + + {:error, error} -> + {:error, "Failed to unban member: #{inspect(error)}"} + end + end + end + + defp validate_param(params, key) do + case Map.fetch(params, key) do + {:ok, value} when is_binary(value) and value != "" -> {:ok, value} + {:ok, value} when is_integer(value) -> {:ok, value} + {:ok, value} when is_map(value) -> {:ok, value} + _ -> {:error, "Missing or invalid #{key}"} + end + end +end diff --git a/lux/lib/lux/prisms/telegram/group_management/unpin_all_chat_messages.ex b/lux/lib/lux/prisms/telegram/group_management/unpin_all_chat_messages.ex new file mode 100644 index 000000000..6be4eba7a --- /dev/null +++ b/lux/lib/lux/prisms/telegram/group_management/unpin_all_chat_messages.ex @@ -0,0 +1,77 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.UnpinAllChatMessages do + @moduledoc """ + A prism for clears the list of pinned messages in a chat via the Telegram Bot API. + + ## Examples + + iex> UnpinAllChatMessages.handler(%{ + ...> chat_id: 123_456_789 + ...> }, %{name: "Agent"}) + {:ok, %{success: true, chat_id: 123_456_789}} + """ + + use Lux.Prism, + name: "Unpin All Chat Messages", + description: "Clears the list of pinned messages in a chat", + input_schema: %{ + type: :object, + properties: %{ + chat_id: %{ + type: [:string, :integer], + description: "Unique identifier for the target chat" + } + }, + required: ["chat_id"] + }, + output_schema: %{ + type: :object, + properties: %{ + success: %{ + type: :boolean, + description: "Whether the operation was successful" + }, + chat_id: %{ + type: [:string, :integer], + description: "Identifier of the target chat" + } + }, + required: ["success", "chat_id"] + } + + alias Lux.Integrations.Telegram.Client + require Logger + + def handler(params, agent) do + with {:ok, chat_id} <- validate_param(params, :chat_id) do + agent_name = agent[:name] || "Unknown Agent" + Logger.info("Agent #{agent_name} attempting to unpin all chat messages in chat #{chat_id}") + + request_body = Map.take(params, [:chat_id, :plug]) + request_opts = %{json: request_body} + + case Client.request(:post, "/unpinAllChatMessages", request_opts) do + {:ok, %{"result" => true}} -> + Logger.info("Successfully completed unpin all chat messages in chat #{chat_id}") + {:ok, %{success: true, chat_id: chat_id}} + + {:error, {status, %{"description" => description}}} -> + {:error, "Failed to unpin all chat messages: #{description} (HTTP #{status})"} + + {:error, {status, description}} when is_binary(description) -> + {:error, "Failed to unpin all chat messages: #{description} (HTTP #{status})"} + + {:error, error} -> + {:error, "Failed to unpin all chat messages: #{inspect(error)}"} + end + end + end + + defp validate_param(params, key) do + case Map.fetch(params, key) do + {:ok, value} when is_binary(value) and value != "" -> {:ok, value} + {:ok, value} when is_integer(value) -> {:ok, value} + {:ok, value} when is_map(value) -> {:ok, value} + _ -> {:error, "Missing or invalid #{key}"} + end + end +end diff --git a/lux/lib/lux/prisms/telegram/group_management/unpin_chat_message.ex b/lux/lib/lux/prisms/telegram/group_management/unpin_chat_message.ex new file mode 100644 index 000000000..3b295a1a1 --- /dev/null +++ b/lux/lib/lux/prisms/telegram/group_management/unpin_chat_message.ex @@ -0,0 +1,81 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.UnpinChatMessage do + @moduledoc """ + A prism for removes a message from the list of pinned messages in a chat via the Telegram Bot API. + + ## Examples + + iex> UnpinChatMessage.handler(%{ + ...> chat_id: 123_456_789 + ...> }, %{name: "Agent"}) + {:ok, %{success: true, chat_id: 123_456_789}} + """ + + use Lux.Prism, + name: "Unpin Chat Message", + description: "Removes a message from the list of pinned messages in a chat", + input_schema: %{ + type: :object, + properties: %{ + chat_id: %{ + type: [:string, :integer], + description: "Unique identifier for the target chat" + }, + message_id: %{ + type: :integer, + description: "Identifier of a message to unpin. If not specified, the most recent pinned message will be unpinned" + } + }, + required: ["chat_id"] + }, + output_schema: %{ + type: :object, + properties: %{ + success: %{ + type: :boolean, + description: "Whether the operation was successful" + }, + chat_id: %{ + type: [:string, :integer], + description: "Identifier of the target chat" + } + }, + required: ["success", "chat_id"] + } + + alias Lux.Integrations.Telegram.Client + require Logger + + def handler(params, agent) do + with {:ok, chat_id} <- validate_param(params, :chat_id) do + agent_name = agent[:name] || "Unknown Agent" + Logger.info("Agent #{agent_name} attempting to unpin chat message in chat #{chat_id}") + + request_body = Map.take(params, [:chat_id, :message_id, :plug]) + request_opts = %{json: request_body} + + case Client.request(:post, "/unpinChatMessage", request_opts) do + {:ok, %{"result" => true}} -> + Logger.info("Successfully completed unpin chat message in chat #{chat_id}") + {:ok, %{success: true, chat_id: chat_id}} + + {:error, {status, %{"description" => description}}} -> + {:error, "Failed to unpin chat message: #{description} (HTTP #{status})"} + + {:error, {status, description}} when is_binary(description) -> + {:error, "Failed to unpin chat message: #{description} (HTTP #{status})"} + + {:error, error} -> + {:error, "Failed to unpin chat message: #{inspect(error)}"} + end + end + end + + defp validate_param(params, key) do + case Map.fetch(params, key) do + {:ok, value} when is_binary(value) and value != "" -> {:ok, value} + {:ok, value} when is_integer(value) -> {:ok, value} + {:ok, value} when is_map(value) -> {:ok, value} + _ -> {:error, "Missing or invalid #{key}"} + end + end +end diff --git a/lux/test/unit/lux/prisms/telegram/group_management/ban_chat_member_test.exs b/lux/test/unit/lux/prisms/telegram/group_management/ban_chat_member_test.exs new file mode 100644 index 000000000..b0ca7c048 --- /dev/null +++ b/lux/test/unit/lux/prisms/telegram/group_management/ban_chat_member_test.exs @@ -0,0 +1,84 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.BanChatMemberTest do + use UnitAPICase, async: true + + alias Lux.Prisms.Telegram.GroupManagement.BanChatMember + + @chat_id 123_456_789 + @agent_ctx %{name: "TestAgent"} + + setup do + Req.Test.verify_on_exit!() + :ok + end + + describe "handler/2" do + test "successfully executes ban member" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + {:ok, body, _conn} = Plug.Conn.read_body(conn) + decoded_body = Jason.decode!(body) + assert decoded_body["chat_id"] == @chat_id + assert decoded_body["user_id"] == 987_654 + + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(200, Jason.encode!(%{ + "ok" => true, + "result" => true + })) + end) + + assert {:ok, %{success: true, chat_id: @chat_id}} = + BanChatMember.handler( + %{ + chat_id: @chat_id, + user_id: 987_654, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + + test "validates required parameters" do + result = BanChatMember.handler(%{}, @agent_ctx) + assert result == {:error, "Missing or invalid chat_id"} + end + + test "handles Telegram API error" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(400, Jason.encode!(%{ + "ok" => false, + "description" => "Bad Request: error description" + })) + end) + + assert {:error, "Failed to ban member: Bad Request: error description (HTTP 400)"} = + BanChatMember.handler( + %{ + chat_id: @chat_id, + user_id: 987_654, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + end + + describe "schema validation" do + test "validates input schema" do + prism = BanChatMember.view() + assert Enum.sort(prism.input_schema.required) == Enum.sort(["chat_id", "user_id"]) + end + + test "validates output schema" do + prism = BanChatMember.view() + assert Enum.sort(prism.output_schema.required) == ["chat_id", "success"] + end + end +end diff --git a/lux/test/unit/lux/prisms/telegram/group_management/pin_chat_message_test.exs b/lux/test/unit/lux/prisms/telegram/group_management/pin_chat_message_test.exs new file mode 100644 index 000000000..37388087b --- /dev/null +++ b/lux/test/unit/lux/prisms/telegram/group_management/pin_chat_message_test.exs @@ -0,0 +1,84 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.PinChatMessageTest do + use UnitAPICase, async: true + + alias Lux.Prisms.Telegram.GroupManagement.PinChatMessage + + @chat_id 123_456_789 + @agent_ctx %{name: "TestAgent"} + + setup do + Req.Test.verify_on_exit!() + :ok + end + + describe "handler/2" do + test "successfully executes pin chat message" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + {:ok, body, _conn} = Plug.Conn.read_body(conn) + decoded_body = Jason.decode!(body) + assert decoded_body["chat_id"] == @chat_id + assert decoded_body["message_id"] == 42 + + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(200, Jason.encode!(%{ + "ok" => true, + "result" => true + })) + end) + + assert {:ok, %{success: true, chat_id: @chat_id}} = + PinChatMessage.handler( + %{ + chat_id: @chat_id, + message_id: 42, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + + test "validates required parameters" do + result = PinChatMessage.handler(%{}, @agent_ctx) + assert result == {:error, "Missing or invalid chat_id"} + end + + test "handles Telegram API error" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(400, Jason.encode!(%{ + "ok" => false, + "description" => "Bad Request: error description" + })) + end) + + assert {:error, "Failed to pin chat message: Bad Request: error description (HTTP 400)"} = + PinChatMessage.handler( + %{ + chat_id: @chat_id, + message_id: 42, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + end + + describe "schema validation" do + test "validates input schema" do + prism = PinChatMessage.view() + assert Enum.sort(prism.input_schema.required) == Enum.sort(["chat_id", "message_id"]) + end + + test "validates output schema" do + prism = PinChatMessage.view() + assert Enum.sort(prism.output_schema.required) == ["chat_id", "success"] + end + end +end diff --git a/lux/test/unit/lux/prisms/telegram/group_management/promote_chat_member_test.exs b/lux/test/unit/lux/prisms/telegram/group_management/promote_chat_member_test.exs new file mode 100644 index 000000000..8831369e4 --- /dev/null +++ b/lux/test/unit/lux/prisms/telegram/group_management/promote_chat_member_test.exs @@ -0,0 +1,84 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.PromoteChatMemberTest do + use UnitAPICase, async: true + + alias Lux.Prisms.Telegram.GroupManagement.PromoteChatMember + + @chat_id 123_456_789 + @agent_ctx %{name: "TestAgent"} + + setup do + Req.Test.verify_on_exit!() + :ok + end + + describe "handler/2" do + test "successfully executes promote member" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + {:ok, body, _conn} = Plug.Conn.read_body(conn) + decoded_body = Jason.decode!(body) + assert decoded_body["chat_id"] == @chat_id + assert decoded_body["user_id"] == 987_654 + + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(200, Jason.encode!(%{ + "ok" => true, + "result" => true + })) + end) + + assert {:ok, %{success: true, chat_id: @chat_id}} = + PromoteChatMember.handler( + %{ + chat_id: @chat_id, + user_id: 987_654, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + + test "validates required parameters" do + result = PromoteChatMember.handler(%{}, @agent_ctx) + assert result == {:error, "Missing or invalid chat_id"} + end + + test "handles Telegram API error" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(400, Jason.encode!(%{ + "ok" => false, + "description" => "Bad Request: error description" + })) + end) + + assert {:error, "Failed to promote member: Bad Request: error description (HTTP 400)"} = + PromoteChatMember.handler( + %{ + chat_id: @chat_id, + user_id: 987_654, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + end + + describe "schema validation" do + test "validates input schema" do + prism = PromoteChatMember.view() + assert Enum.sort(prism.input_schema.required) == Enum.sort(["chat_id", "user_id"]) + end + + test "validates output schema" do + prism = PromoteChatMember.view() + assert Enum.sort(prism.output_schema.required) == ["chat_id", "success"] + end + end +end diff --git a/lux/test/unit/lux/prisms/telegram/group_management/restrict_chat_member_test.exs b/lux/test/unit/lux/prisms/telegram/group_management/restrict_chat_member_test.exs new file mode 100644 index 000000000..d18c34665 --- /dev/null +++ b/lux/test/unit/lux/prisms/telegram/group_management/restrict_chat_member_test.exs @@ -0,0 +1,87 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.RestrictChatMemberTest do + use UnitAPICase, async: true + + alias Lux.Prisms.Telegram.GroupManagement.RestrictChatMember + + @chat_id 123_456_789 + @agent_ctx %{name: "TestAgent"} + + setup do + Req.Test.verify_on_exit!() + :ok + end + + describe "handler/2" do + test "successfully executes restrict member" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + {:ok, body, _conn} = Plug.Conn.read_body(conn) + decoded_body = Jason.decode!(body) + assert decoded_body["chat_id"] == @chat_id + assert decoded_body["user_id"] == 987_654 + assert decoded_body["permissions"]["can_send_messages"] == true + + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(200, Jason.encode!(%{ + "ok" => true, + "result" => true + })) + end) + + assert {:ok, %{success: true, chat_id: @chat_id}} = + RestrictChatMember.handler( + %{ + chat_id: @chat_id, + user_id: 987_654, + permissions: %{can_send_messages: true}, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + + test "validates required parameters" do + result = RestrictChatMember.handler(%{}, @agent_ctx) + assert result == {:error, "Missing or invalid chat_id"} + end + + test "handles Telegram API error" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(400, Jason.encode!(%{ + "ok" => false, + "description" => "Bad Request: error description" + })) + end) + + assert {:error, "Failed to restrict member: Bad Request: error description (HTTP 400)"} = + RestrictChatMember.handler( + %{ + chat_id: @chat_id, + user_id: 987_654, + permissions: %{can_send_messages: true}, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + end + + describe "schema validation" do + test "validates input schema" do + prism = RestrictChatMember.view() + assert Enum.sort(prism.input_schema.required) == Enum.sort(["chat_id", "user_id", "permissions"]) + end + + test "validates output schema" do + prism = RestrictChatMember.view() + assert Enum.sort(prism.output_schema.required) == ["chat_id", "success"] + end + end +end diff --git a/lux/test/unit/lux/prisms/telegram/group_management/set_chat_description_test.exs b/lux/test/unit/lux/prisms/telegram/group_management/set_chat_description_test.exs new file mode 100644 index 000000000..e54d2e447 --- /dev/null +++ b/lux/test/unit/lux/prisms/telegram/group_management/set_chat_description_test.exs @@ -0,0 +1,81 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.SetChatDescriptionTest do + use UnitAPICase, async: true + + alias Lux.Prisms.Telegram.GroupManagement.SetChatDescription + + @chat_id 123_456_789 + @agent_ctx %{name: "TestAgent"} + + setup do + Req.Test.verify_on_exit!() + :ok + end + + describe "handler/2" do + test "successfully executes set chat description" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + {:ok, body, _conn} = Plug.Conn.read_body(conn) + decoded_body = Jason.decode!(body) + assert decoded_body["chat_id"] == @chat_id + + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(200, Jason.encode!(%{ + "ok" => true, + "result" => true + })) + end) + + assert {:ok, %{success: true, chat_id: @chat_id}} = + SetChatDescription.handler( + %{ + chat_id: @chat_id, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + + test "validates required parameters" do + result = SetChatDescription.handler(%{}, @agent_ctx) + assert result == {:error, "Missing or invalid chat_id"} + end + + test "handles Telegram API error" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(400, Jason.encode!(%{ + "ok" => false, + "description" => "Bad Request: error description" + })) + end) + + assert {:error, "Failed to set chat description: Bad Request: error description (HTTP 400)"} = + SetChatDescription.handler( + %{ + chat_id: @chat_id, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + end + + describe "schema validation" do + test "validates input schema" do + prism = SetChatDescription.view() + assert Enum.sort(prism.input_schema.required) == Enum.sort(["chat_id"]) + end + + test "validates output schema" do + prism = SetChatDescription.view() + assert Enum.sort(prism.output_schema.required) == ["chat_id", "success"] + end + end +end diff --git a/lux/test/unit/lux/prisms/telegram/group_management/set_chat_permissions_test.exs b/lux/test/unit/lux/prisms/telegram/group_management/set_chat_permissions_test.exs new file mode 100644 index 000000000..6fc2e198f --- /dev/null +++ b/lux/test/unit/lux/prisms/telegram/group_management/set_chat_permissions_test.exs @@ -0,0 +1,84 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.SetChatPermissionsTest do + use UnitAPICase, async: true + + alias Lux.Prisms.Telegram.GroupManagement.SetChatPermissions + + @chat_id 123_456_789 + @agent_ctx %{name: "TestAgent"} + + setup do + Req.Test.verify_on_exit!() + :ok + end + + describe "handler/2" do + test "successfully executes set chat permissions" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + {:ok, body, _conn} = Plug.Conn.read_body(conn) + decoded_body = Jason.decode!(body) + assert decoded_body["chat_id"] == @chat_id + assert decoded_body["permissions"]["can_send_messages"] == true + + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(200, Jason.encode!(%{ + "ok" => true, + "result" => true + })) + end) + + assert {:ok, %{success: true, chat_id: @chat_id}} = + SetChatPermissions.handler( + %{ + chat_id: @chat_id, + permissions: %{can_send_messages: true}, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + + test "validates required parameters" do + result = SetChatPermissions.handler(%{}, @agent_ctx) + assert result == {:error, "Missing or invalid chat_id"} + end + + test "handles Telegram API error" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(400, Jason.encode!(%{ + "ok" => false, + "description" => "Bad Request: error description" + })) + end) + + assert {:error, "Failed to set chat permissions: Bad Request: error description (HTTP 400)"} = + SetChatPermissions.handler( + %{ + chat_id: @chat_id, + permissions: %{can_send_messages: true}, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + end + + describe "schema validation" do + test "validates input schema" do + prism = SetChatPermissions.view() + assert Enum.sort(prism.input_schema.required) == Enum.sort(["chat_id", "permissions"]) + end + + test "validates output schema" do + prism = SetChatPermissions.view() + assert Enum.sort(prism.output_schema.required) == ["chat_id", "success"] + end + end +end diff --git a/lux/test/unit/lux/prisms/telegram/group_management/set_chat_title_test.exs b/lux/test/unit/lux/prisms/telegram/group_management/set_chat_title_test.exs new file mode 100644 index 000000000..7d0a307fc --- /dev/null +++ b/lux/test/unit/lux/prisms/telegram/group_management/set_chat_title_test.exs @@ -0,0 +1,84 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.SetChatTitleTest do + use UnitAPICase, async: true + + alias Lux.Prisms.Telegram.GroupManagement.SetChatTitle + + @chat_id 123_456_789 + @agent_ctx %{name: "TestAgent"} + + setup do + Req.Test.verify_on_exit!() + :ok + end + + describe "handler/2" do + test "successfully executes set chat title" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + {:ok, body, _conn} = Plug.Conn.read_body(conn) + decoded_body = Jason.decode!(body) + assert decoded_body["chat_id"] == @chat_id + assert decoded_body["title"] == "New Title" + + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(200, Jason.encode!(%{ + "ok" => true, + "result" => true + })) + end) + + assert {:ok, %{success: true, chat_id: @chat_id}} = + SetChatTitle.handler( + %{ + chat_id: @chat_id, + title: "New Title", + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + + test "validates required parameters" do + result = SetChatTitle.handler(%{}, @agent_ctx) + assert result == {:error, "Missing or invalid chat_id"} + end + + test "handles Telegram API error" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(400, Jason.encode!(%{ + "ok" => false, + "description" => "Bad Request: error description" + })) + end) + + assert {:error, "Failed to set chat title: Bad Request: error description (HTTP 400)"} = + SetChatTitle.handler( + %{ + chat_id: @chat_id, + title: "New Title", + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + end + + describe "schema validation" do + test "validates input schema" do + prism = SetChatTitle.view() + assert Enum.sort(prism.input_schema.required) == Enum.sort(["chat_id", "title"]) + end + + test "validates output schema" do + prism = SetChatTitle.view() + assert Enum.sort(prism.output_schema.required) == ["chat_id", "success"] + end + end +end diff --git a/lux/test/unit/lux/prisms/telegram/group_management/unban_chat_member_test.exs b/lux/test/unit/lux/prisms/telegram/group_management/unban_chat_member_test.exs new file mode 100644 index 000000000..832fca1da --- /dev/null +++ b/lux/test/unit/lux/prisms/telegram/group_management/unban_chat_member_test.exs @@ -0,0 +1,84 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.UnbanChatMemberTest do + use UnitAPICase, async: true + + alias Lux.Prisms.Telegram.GroupManagement.UnbanChatMember + + @chat_id 123_456_789 + @agent_ctx %{name: "TestAgent"} + + setup do + Req.Test.verify_on_exit!() + :ok + end + + describe "handler/2" do + test "successfully executes unban member" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + {:ok, body, _conn} = Plug.Conn.read_body(conn) + decoded_body = Jason.decode!(body) + assert decoded_body["chat_id"] == @chat_id + assert decoded_body["user_id"] == 987_654 + + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(200, Jason.encode!(%{ + "ok" => true, + "result" => true + })) + end) + + assert {:ok, %{success: true, chat_id: @chat_id}} = + UnbanChatMember.handler( + %{ + chat_id: @chat_id, + user_id: 987_654, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + + test "validates required parameters" do + result = UnbanChatMember.handler(%{}, @agent_ctx) + assert result == {:error, "Missing or invalid chat_id"} + end + + test "handles Telegram API error" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(400, Jason.encode!(%{ + "ok" => false, + "description" => "Bad Request: error description" + })) + end) + + assert {:error, "Failed to unban member: Bad Request: error description (HTTP 400)"} = + UnbanChatMember.handler( + %{ + chat_id: @chat_id, + user_id: 987_654, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + end + + describe "schema validation" do + test "validates input schema" do + prism = UnbanChatMember.view() + assert Enum.sort(prism.input_schema.required) == Enum.sort(["chat_id", "user_id"]) + end + + test "validates output schema" do + prism = UnbanChatMember.view() + assert Enum.sort(prism.output_schema.required) == ["chat_id", "success"] + end + end +end diff --git a/lux/test/unit/lux/prisms/telegram/group_management/unpin_all_chat_messages_test.exs b/lux/test/unit/lux/prisms/telegram/group_management/unpin_all_chat_messages_test.exs new file mode 100644 index 000000000..4f4cf911c --- /dev/null +++ b/lux/test/unit/lux/prisms/telegram/group_management/unpin_all_chat_messages_test.exs @@ -0,0 +1,81 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.UnpinAllChatMessagesTest do + use UnitAPICase, async: true + + alias Lux.Prisms.Telegram.GroupManagement.UnpinAllChatMessages + + @chat_id 123_456_789 + @agent_ctx %{name: "TestAgent"} + + setup do + Req.Test.verify_on_exit!() + :ok + end + + describe "handler/2" do + test "successfully executes unpin all chat messages" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + {:ok, body, _conn} = Plug.Conn.read_body(conn) + decoded_body = Jason.decode!(body) + assert decoded_body["chat_id"] == @chat_id + + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(200, Jason.encode!(%{ + "ok" => true, + "result" => true + })) + end) + + assert {:ok, %{success: true, chat_id: @chat_id}} = + UnpinAllChatMessages.handler( + %{ + chat_id: @chat_id, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + + test "validates required parameters" do + result = UnpinAllChatMessages.handler(%{}, @agent_ctx) + assert result == {:error, "Missing or invalid chat_id"} + end + + test "handles Telegram API error" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(400, Jason.encode!(%{ + "ok" => false, + "description" => "Bad Request: error description" + })) + end) + + assert {:error, "Failed to unpin all chat messages: Bad Request: error description (HTTP 400)"} = + UnpinAllChatMessages.handler( + %{ + chat_id: @chat_id, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + end + + describe "schema validation" do + test "validates input schema" do + prism = UnpinAllChatMessages.view() + assert Enum.sort(prism.input_schema.required) == Enum.sort(["chat_id"]) + end + + test "validates output schema" do + prism = UnpinAllChatMessages.view() + assert Enum.sort(prism.output_schema.required) == ["chat_id", "success"] + end + end +end diff --git a/lux/test/unit/lux/prisms/telegram/group_management/unpin_chat_message_test.exs b/lux/test/unit/lux/prisms/telegram/group_management/unpin_chat_message_test.exs new file mode 100644 index 000000000..be32eb972 --- /dev/null +++ b/lux/test/unit/lux/prisms/telegram/group_management/unpin_chat_message_test.exs @@ -0,0 +1,81 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.UnpinChatMessageTest do + use UnitAPICase, async: true + + alias Lux.Prisms.Telegram.GroupManagement.UnpinChatMessage + + @chat_id 123_456_789 + @agent_ctx %{name: "TestAgent"} + + setup do + Req.Test.verify_on_exit!() + :ok + end + + describe "handler/2" do + test "successfully executes unpin chat message" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + {:ok, body, _conn} = Plug.Conn.read_body(conn) + decoded_body = Jason.decode!(body) + assert decoded_body["chat_id"] == @chat_id + + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(200, Jason.encode!(%{ + "ok" => true, + "result" => true + })) + end) + + assert {:ok, %{success: true, chat_id: @chat_id}} = + UnpinChatMessage.handler( + %{ + chat_id: @chat_id, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + + test "validates required parameters" do + result = UnpinChatMessage.handler(%{}, @agent_ctx) + assert result == {:error, "Missing or invalid chat_id"} + end + + test "handles Telegram API error" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(400, Jason.encode!(%{ + "ok" => false, + "description" => "Bad Request: error description" + })) + end) + + assert {:error, "Failed to unpin chat message: Bad Request: error description (HTTP 400)"} = + UnpinChatMessage.handler( + %{ + chat_id: @chat_id, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + end + + describe "schema validation" do + test "validates input schema" do + prism = UnpinChatMessage.view() + assert Enum.sort(prism.input_schema.required) == Enum.sort(["chat_id"]) + end + + test "validates output schema" do + prism = UnpinChatMessage.view() + assert Enum.sort(prism.output_schema.required) == ["chat_id", "success"] + end + end +end From a26a02ed701b1c446109225cb57fc0d2b9974556 Mon Sep 17 00:00:00 2001 From: Bot Date: Sun, 12 Jul 2026 21:05:06 +0530 Subject: [PATCH 2/3] Fix review comments for group management --- .../approve_chat_join_request.ex | 83 +++++++++++++++++ .../group_management/ban_chat_member.ex | 3 +- .../create_chat_invite_link.ex | 93 +++++++++++++++++++ .../decline_chat_join_request.ex | 83 +++++++++++++++++ .../group_management/delete_message.ex | 83 +++++++++++++++++ .../group_management/pin_chat_message.ex | 3 +- .../group_management/promote_chat_member.ex | 41 +++++++- .../group_management/restrict_chat_member.ex | 3 +- .../group_management/set_chat_description.ex | 3 +- .../group_management/set_chat_permissions.ex | 3 +- .../group_management/set_chat_title.ex | 3 +- .../group_management/unban_chat_member.ex | 3 +- .../unpin_all_chat_messages.ex | 3 +- .../group_management/unpin_chat_message.ex | 3 +- .../approve_chat_join_request_test.exs | 83 +++++++++++++++++ .../create_chat_invite_link_test.exs | 80 ++++++++++++++++ .../decline_chat_join_request_test.exs | 83 +++++++++++++++++ .../group_management/delete_message_test.exs | 83 +++++++++++++++++ 18 files changed, 728 insertions(+), 11 deletions(-) create mode 100644 lux/lib/lux/prisms/telegram/group_management/approve_chat_join_request.ex create mode 100644 lux/lib/lux/prisms/telegram/group_management/create_chat_invite_link.ex create mode 100644 lux/lib/lux/prisms/telegram/group_management/decline_chat_join_request.ex create mode 100644 lux/lib/lux/prisms/telegram/group_management/delete_message.ex create mode 100644 lux/test/unit/lux/prisms/telegram/group_management/approve_chat_join_request_test.exs create mode 100644 lux/test/unit/lux/prisms/telegram/group_management/create_chat_invite_link_test.exs create mode 100644 lux/test/unit/lux/prisms/telegram/group_management/decline_chat_join_request_test.exs create mode 100644 lux/test/unit/lux/prisms/telegram/group_management/delete_message_test.exs diff --git a/lux/lib/lux/prisms/telegram/group_management/approve_chat_join_request.ex b/lux/lib/lux/prisms/telegram/group_management/approve_chat_join_request.ex new file mode 100644 index 000000000..363d03746 --- /dev/null +++ b/lux/lib/lux/prisms/telegram/group_management/approve_chat_join_request.ex @@ -0,0 +1,83 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.ApproveChatJoinRequest do + @moduledoc """ + A prism for approving a chat join request via the Telegram Bot API. + + ## Examples + + iex> ApproveChatJoinRequest.handler(%{ + ...> chat_id: 123_456_789, + ...> user_id: 987_654 + ...> }, %{name: "Agent"}) + {:ok, %{success: true, chat_id: 123_456_789}} + """ + + use Lux.Prism, + name: "Approve Chat Join Request", + description: "Approves a chat join request", + input_schema: %{ + type: :object, + properties: %{ + chat_id: %{ + type: [:string, :integer], + description: "Unique identifier for the target chat" + }, + user_id: %{ + type: :integer, + description: "Unique identifier of the target user" + } + }, + required: ["chat_id", "user_id"] + }, + output_schema: %{ + type: :object, + properties: %{ + success: %{ + type: :boolean, + description: "Whether the operation was successful" + }, + chat_id: %{ + type: [:string, :integer], + description: "Identifier of the target chat" + } + }, + required: ["success", "chat_id"] + } + + alias Lux.Integrations.Telegram.Client + require Logger + + def handler(params, agent) do + with {:ok, chat_id} <- validate_param(params, :chat_id), + {:ok, _} <- validate_param(params, :user_id) do + agent_name = agent[:name] || "Unknown Agent" + Logger.info("Agent #{agent_name} attempting to approve join request in chat #{chat_id}") + + request_body = Map.take(params, [:chat_id, :user_id]) + request_opts = %{json: request_body} + request_opts = if params[:plug], do: Map.put(request_opts, :plug, params[:plug]), else: request_opts + + case Client.request(:post, "/approveChatJoinRequest", request_opts) do + {:ok, %{"result" => true}} -> + Logger.info("Successfully approved join request in chat #{chat_id}") + {:ok, %{success: true, chat_id: chat_id}} + + {:error, {status, %{"description" => description}}} -> + {:error, "Failed to approve join request: #{description} (HTTP #{status})"} + + {:error, {status, description}} when is_binary(description) -> + {:error, "Failed to approve join request: #{description} (HTTP #{status})"} + + {:error, error} -> + {:error, "Failed to approve join request: #{inspect(error)}"} + end + end + end + + defp validate_param(params, key) do + case Map.fetch(params, key) do + {:ok, value} when is_binary(value) and value != "" -> {:ok, value} + {:ok, value} when is_integer(value) -> {:ok, value} + _ -> {:error, "Missing or invalid #{key}"} + end + end +end diff --git a/lux/lib/lux/prisms/telegram/group_management/ban_chat_member.ex b/lux/lib/lux/prisms/telegram/group_management/ban_chat_member.ex index 3b7274976..905c40038 100644 --- a/lux/lib/lux/prisms/telegram/group_management/ban_chat_member.ex +++ b/lux/lib/lux/prisms/telegram/group_management/ban_chat_member.ex @@ -56,8 +56,9 @@ defmodule Lux.Prisms.Telegram.GroupManagement.BanChatMember do agent_name = agent[:name] || "Unknown Agent" Logger.info("Agent #{agent_name} attempting to ban member in chat #{chat_id}") - request_body = Map.take(params, [:chat_id, :user_id, :until_date, :plug]) + request_body = Map.take(params, [:chat_id, :user_id, :until_date]) request_opts = %{json: request_body} + request_opts = if params[:plug], do: Map.put(request_opts, :plug, params[:plug]), else: request_opts case Client.request(:post, "/banChatMember", request_opts) do {:ok, %{"result" => true}} -> diff --git a/lux/lib/lux/prisms/telegram/group_management/create_chat_invite_link.ex b/lux/lib/lux/prisms/telegram/group_management/create_chat_invite_link.ex new file mode 100644 index 000000000..fbb4cf40b --- /dev/null +++ b/lux/lib/lux/prisms/telegram/group_management/create_chat_invite_link.ex @@ -0,0 +1,93 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.CreateChatInviteLink do + @moduledoc """ + A prism for creating an additional invite link for a chat via the Telegram Bot API. + + ## Examples + + iex> CreateChatInviteLink.handler(%{ + ...> chat_id: 123_456_789 + ...> }, %{name: "Agent"}) + {:ok, %{success: true, invite_link: "https://t.me/joinchat/..."}} + """ + + use Lux.Prism, + name: "Create Chat Invite Link", + description: "Creates an additional invite link for a chat", + input_schema: %{ + type: :object, + properties: %{ + chat_id: %{ + type: [:string, :integer], + description: "Unique identifier for the target chat" + }, + name: %{ + type: :string, + description: "Invite link name" + }, + expire_date: %{ + type: :integer, + description: "Point in time when the link will expire, unix time" + }, + member_limit: %{ + type: :integer, + description: "Maximum number of users that can be members of the chat simultaneously" + }, + creates_join_request: %{ + type: :boolean, + description: "True, if users joining the chat via the link need to be approved by chat administrators" + } + }, + required: ["chat_id"] + }, + output_schema: %{ + type: :object, + properties: %{ + success: %{ + type: :boolean, + description: "Whether the operation was successful" + }, + invite_link: %{ + type: :string, + description: "The newly created invite link" + } + }, + required: ["success", "invite_link"] + } + + alias Lux.Integrations.Telegram.Client + require Logger + + def handler(params, agent) do + with {:ok, chat_id} <- validate_param(params, :chat_id) do + agent_name = agent[:name] || "Unknown Agent" + Logger.info("Agent #{agent_name} attempting to create invite link for chat #{chat_id}") + + request_body = Map.take(params, [:chat_id, :name, :expire_date, :member_limit, :creates_join_request]) + request_opts = %{json: request_body} + request_opts = if params[:plug], do: Map.put(request_opts, :plug, params[:plug]), else: request_opts + + case Client.request(:post, "/createChatInviteLink", request_opts) do + {:ok, %{"result" => %{"invite_link" => link}}} -> + Logger.info("Successfully created invite link for chat #{chat_id}") + {:ok, %{success: true, invite_link: link}} + + {:error, {status, %{"description" => description}}} -> + {:error, "Failed to create invite link: #{description} (HTTP #{status})"} + + {:error, {status, description}} when is_binary(description) -> + {:error, "Failed to create invite link: #{description} (HTTP #{status})"} + + {:error, error} -> + {:error, "Failed to create invite link: #{inspect(error)}"} + end + end + end + + defp validate_param(params, key) do + case Map.fetch(params, key) do + {:ok, value} when is_binary(value) and value != "" -> {:ok, value} + {:ok, value} when is_integer(value) -> {:ok, value} + _ -> {:error, "Missing or invalid #{key}"} + end + end +end diff --git a/lux/lib/lux/prisms/telegram/group_management/decline_chat_join_request.ex b/lux/lib/lux/prisms/telegram/group_management/decline_chat_join_request.ex new file mode 100644 index 000000000..9565e39c7 --- /dev/null +++ b/lux/lib/lux/prisms/telegram/group_management/decline_chat_join_request.ex @@ -0,0 +1,83 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.DeclineChatJoinRequest do + @moduledoc """ + A prism for declining a chat join request via the Telegram Bot API. + + ## Examples + + iex> DeclineChatJoinRequest.handler(%{ + ...> chat_id: 123_456_789, + ...> user_id: 987_654 + ...> }, %{name: "Agent"}) + {:ok, %{success: true, chat_id: 123_456_789}} + """ + + use Lux.Prism, + name: "Decline Chat Join Request", + description: "Declines a chat join request", + input_schema: %{ + type: :object, + properties: %{ + chat_id: %{ + type: [:string, :integer], + description: "Unique identifier for the target chat" + }, + user_id: %{ + type: :integer, + description: "Unique identifier of the target user" + } + }, + required: ["chat_id", "user_id"] + }, + output_schema: %{ + type: :object, + properties: %{ + success: %{ + type: :boolean, + description: "Whether the operation was successful" + }, + chat_id: %{ + type: [:string, :integer], + description: "Identifier of the target chat" + } + }, + required: ["success", "chat_id"] + } + + alias Lux.Integrations.Telegram.Client + require Logger + + def handler(params, agent) do + with {:ok, chat_id} <- validate_param(params, :chat_id), + {:ok, _} <- validate_param(params, :user_id) do + agent_name = agent[:name] || "Unknown Agent" + Logger.info("Agent #{agent_name} attempting to decline join request in chat #{chat_id}") + + request_body = Map.take(params, [:chat_id, :user_id]) + request_opts = %{json: request_body} + request_opts = if params[:plug], do: Map.put(request_opts, :plug, params[:plug]), else: request_opts + + case Client.request(:post, "/declineChatJoinRequest", request_opts) do + {:ok, %{"result" => true}} -> + Logger.info("Successfully declined join request in chat #{chat_id}") + {:ok, %{success: true, chat_id: chat_id}} + + {:error, {status, %{"description" => description}}} -> + {:error, "Failed to decline join request: #{description} (HTTP #{status})"} + + {:error, {status, description}} when is_binary(description) -> + {:error, "Failed to decline join request: #{description} (HTTP #{status})"} + + {:error, error} -> + {:error, "Failed to decline join request: #{inspect(error)}"} + end + end + end + + defp validate_param(params, key) do + case Map.fetch(params, key) do + {:ok, value} when is_binary(value) and value != "" -> {:ok, value} + {:ok, value} when is_integer(value) -> {:ok, value} + _ -> {:error, "Missing or invalid #{key}"} + end + end +end diff --git a/lux/lib/lux/prisms/telegram/group_management/delete_message.ex b/lux/lib/lux/prisms/telegram/group_management/delete_message.ex new file mode 100644 index 000000000..0e21015d9 --- /dev/null +++ b/lux/lib/lux/prisms/telegram/group_management/delete_message.ex @@ -0,0 +1,83 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.DeleteMessage do + @moduledoc """ + A prism for deleting a message in a chat via the Telegram Bot API. + + ## Examples + + iex> DeleteMessage.handler(%{ + ...> chat_id: 123_456_789, + ...> message_id: 101 + ...> }, %{name: "Agent"}) + {:ok, %{success: true, chat_id: 123_456_789}} + """ + + use Lux.Prism, + name: "Delete Message", + description: "Deletes a message in a chat", + input_schema: %{ + type: :object, + properties: %{ + chat_id: %{ + type: [:string, :integer], + description: "Unique identifier for the target chat" + }, + message_id: %{ + type: :integer, + description: "Identifier of the message to delete" + } + }, + required: ["chat_id", "message_id"] + }, + output_schema: %{ + type: :object, + properties: %{ + success: %{ + type: :boolean, + description: "Whether the operation was successful" + }, + chat_id: %{ + type: [:string, :integer], + description: "Identifier of the target chat" + } + }, + required: ["success", "chat_id"] + } + + alias Lux.Integrations.Telegram.Client + require Logger + + def handler(params, agent) do + with {:ok, chat_id} <- validate_param(params, :chat_id), + {:ok, _} <- validate_param(params, :message_id) do + agent_name = agent[:name] || "Unknown Agent" + Logger.info("Agent #{agent_name} attempting to delete message in chat #{chat_id}") + + request_body = Map.take(params, [:chat_id, :message_id]) + request_opts = %{json: request_body} + request_opts = if params[:plug], do: Map.put(request_opts, :plug, params[:plug]), else: request_opts + + case Client.request(:post, "/deleteMessage", request_opts) do + {:ok, %{"result" => true}} -> + Logger.info("Successfully deleted message in chat #{chat_id}") + {:ok, %{success: true, chat_id: chat_id}} + + {:error, {status, %{"description" => description}}} -> + {:error, "Failed to delete message: #{description} (HTTP #{status})"} + + {:error, {status, description}} when is_binary(description) -> + {:error, "Failed to delete message: #{description} (HTTP #{status})"} + + {:error, error} -> + {:error, "Failed to delete message: #{inspect(error)}"} + end + end + end + + defp validate_param(params, key) do + case Map.fetch(params, key) do + {:ok, value} when is_binary(value) and value != "" -> {:ok, value} + {:ok, value} when is_integer(value) -> {:ok, value} + _ -> {:error, "Missing or invalid #{key}"} + end + end +end diff --git a/lux/lib/lux/prisms/telegram/group_management/pin_chat_message.ex b/lux/lib/lux/prisms/telegram/group_management/pin_chat_message.ex index 147f0fbbf..4e1183eb2 100644 --- a/lux/lib/lux/prisms/telegram/group_management/pin_chat_message.ex +++ b/lux/lib/lux/prisms/telegram/group_management/pin_chat_message.ex @@ -56,8 +56,9 @@ defmodule Lux.Prisms.Telegram.GroupManagement.PinChatMessage do agent_name = agent[:name] || "Unknown Agent" Logger.info("Agent #{agent_name} attempting to pin chat message in chat #{chat_id}") - request_body = Map.take(params, [:chat_id, :message_id, :disable_notification, :plug]) + request_body = Map.take(params, [:chat_id, :message_id, :disable_notification]) request_opts = %{json: request_body} + request_opts = if params[:plug], do: Map.put(request_opts, :plug, params[:plug]), else: request_opts case Client.request(:post, "/pinChatMessage", request_opts) do {:ok, %{"result" => true}} -> diff --git a/lux/lib/lux/prisms/telegram/group_management/promote_chat_member.ex b/lux/lib/lux/prisms/telegram/group_management/promote_chat_member.ex index 566d46267..028e5ae16 100644 --- a/lux/lib/lux/prisms/telegram/group_management/promote_chat_member.ex +++ b/lux/lib/lux/prisms/telegram/group_management/promote_chat_member.ex @@ -32,6 +32,38 @@ defmodule Lux.Prisms.Telegram.GroupManagement.PromoteChatMember do can_manage_chat: %{ type: :boolean, description: "Pass True, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode." + }, + can_delete_messages: %{ + type: :boolean, + description: "Pass True, if the administrator can delete messages of other users" + }, + can_manage_video_chats: %{ + type: :boolean, + description: "Pass True, if the administrator can manage video chats" + }, + can_restrict_members: %{ + type: :boolean, + description: "Pass True, if the administrator can restrict, ban or unban chat members" + }, + can_promote_members: %{ + type: :boolean, + description: "Pass True, if the administrator can add new administrators with a subset of their own privileges or demote administrators that they have promoted, directly or indirectly" + }, + can_change_info: %{ + type: :boolean, + description: "Pass True, if the administrator can change chat title, photo and other settings" + }, + can_invite_users: %{ + type: :boolean, + description: "Pass True, if the administrator can invite new users to the chat" + }, + can_pin_messages: %{ + type: :boolean, + description: "Pass True, if the administrator can pin messages" + }, + can_manage_topics: %{ + type: :boolean, + description: "Pass True, if the user is allowed to create, rename, close, and reopen forum topics" } }, required: ["chat_id", "user_id"] @@ -60,8 +92,14 @@ defmodule Lux.Prisms.Telegram.GroupManagement.PromoteChatMember do agent_name = agent[:name] || "Unknown Agent" Logger.info("Agent #{agent_name} attempting to promote member in chat #{chat_id}") - request_body = Map.take(params, [:chat_id, :user_id, :is_anonymous, :can_manage_chat, :plug]) + request_body = Map.take(params, [ + :chat_id, :user_id, :is_anonymous, :can_manage_chat, + :can_delete_messages, :can_manage_video_chats, :can_restrict_members, + :can_promote_members, :can_change_info, :can_invite_users, + :can_pin_messages, :can_manage_topics + ]) request_opts = %{json: request_body} + request_opts = if params[:plug], do: Map.put(request_opts, :plug, params[:plug]), else: request_opts case Client.request(:post, "/promoteChatMember", request_opts) do {:ok, %{"result" => true}} -> @@ -84,7 +122,6 @@ defmodule Lux.Prisms.Telegram.GroupManagement.PromoteChatMember do case Map.fetch(params, key) do {:ok, value} when is_binary(value) and value != "" -> {:ok, value} {:ok, value} when is_integer(value) -> {:ok, value} - {:ok, value} when is_map(value) -> {:ok, value} _ -> {:error, "Missing or invalid #{key}"} end end diff --git a/lux/lib/lux/prisms/telegram/group_management/restrict_chat_member.ex b/lux/lib/lux/prisms/telegram/group_management/restrict_chat_member.ex index df59a7ce3..253a334db 100644 --- a/lux/lib/lux/prisms/telegram/group_management/restrict_chat_member.ex +++ b/lux/lib/lux/prisms/telegram/group_management/restrict_chat_member.ex @@ -62,8 +62,9 @@ defmodule Lux.Prisms.Telegram.GroupManagement.RestrictChatMember do agent_name = agent[:name] || "Unknown Agent" Logger.info("Agent #{agent_name} attempting to restrict member in chat #{chat_id}") - request_body = Map.take(params, [:chat_id, :user_id, :permissions, :until_date, :plug]) + request_body = Map.take(params, [:chat_id, :user_id, :permissions, :until_date]) request_opts = %{json: request_body} + request_opts = if params[:plug], do: Map.put(request_opts, :plug, params[:plug]), else: request_opts case Client.request(:post, "/restrictChatMember", request_opts) do {:ok, %{"result" => true}} -> diff --git a/lux/lib/lux/prisms/telegram/group_management/set_chat_description.ex b/lux/lib/lux/prisms/telegram/group_management/set_chat_description.ex index 611abb507..df9c83db6 100644 --- a/lux/lib/lux/prisms/telegram/group_management/set_chat_description.ex +++ b/lux/lib/lux/prisms/telegram/group_management/set_chat_description.ex @@ -50,8 +50,9 @@ defmodule Lux.Prisms.Telegram.GroupManagement.SetChatDescription do agent_name = agent[:name] || "Unknown Agent" Logger.info("Agent #{agent_name} attempting to set chat description in chat #{chat_id}") - request_body = Map.take(params, [:chat_id, :description, :plug]) + request_body = Map.take(params, [:chat_id, :description]) request_opts = %{json: request_body} + request_opts = if params[:plug], do: Map.put(request_opts, :plug, params[:plug]), else: request_opts case Client.request(:post, "/setChatDescription", request_opts) do {:ok, %{"result" => true}} -> diff --git a/lux/lib/lux/prisms/telegram/group_management/set_chat_permissions.ex b/lux/lib/lux/prisms/telegram/group_management/set_chat_permissions.ex index 6070ab0a9..6071c9d11 100644 --- a/lux/lib/lux/prisms/telegram/group_management/set_chat_permissions.ex +++ b/lux/lib/lux/prisms/telegram/group_management/set_chat_permissions.ex @@ -52,8 +52,9 @@ defmodule Lux.Prisms.Telegram.GroupManagement.SetChatPermissions do agent_name = agent[:name] || "Unknown Agent" Logger.info("Agent #{agent_name} attempting to set chat permissions in chat #{chat_id}") - request_body = Map.take(params, [:chat_id, :permissions, :plug]) + request_body = Map.take(params, [:chat_id, :permissions]) request_opts = %{json: request_body} + request_opts = if params[:plug], do: Map.put(request_opts, :plug, params[:plug]), else: request_opts case Client.request(:post, "/setChatPermissions", request_opts) do {:ok, %{"result" => true}} -> diff --git a/lux/lib/lux/prisms/telegram/group_management/set_chat_title.ex b/lux/lib/lux/prisms/telegram/group_management/set_chat_title.ex index 1bc5b77f8..8934fc1cf 100644 --- a/lux/lib/lux/prisms/telegram/group_management/set_chat_title.ex +++ b/lux/lib/lux/prisms/telegram/group_management/set_chat_title.ex @@ -52,8 +52,9 @@ defmodule Lux.Prisms.Telegram.GroupManagement.SetChatTitle do agent_name = agent[:name] || "Unknown Agent" Logger.info("Agent #{agent_name} attempting to set chat title in chat #{chat_id}") - request_body = Map.take(params, [:chat_id, :title, :plug]) + request_body = Map.take(params, [:chat_id, :title]) request_opts = %{json: request_body} + request_opts = if params[:plug], do: Map.put(request_opts, :plug, params[:plug]), else: request_opts case Client.request(:post, "/setChatTitle", request_opts) do {:ok, %{"result" => true}} -> diff --git a/lux/lib/lux/prisms/telegram/group_management/unban_chat_member.ex b/lux/lib/lux/prisms/telegram/group_management/unban_chat_member.ex index ccab6a86d..3f837194b 100644 --- a/lux/lib/lux/prisms/telegram/group_management/unban_chat_member.ex +++ b/lux/lib/lux/prisms/telegram/group_management/unban_chat_member.ex @@ -56,8 +56,9 @@ defmodule Lux.Prisms.Telegram.GroupManagement.UnbanChatMember do agent_name = agent[:name] || "Unknown Agent" Logger.info("Agent #{agent_name} attempting to unban member in chat #{chat_id}") - request_body = Map.take(params, [:chat_id, :user_id, :only_if_banned, :plug]) + request_body = Map.take(params, [:chat_id, :user_id, :only_if_banned]) request_opts = %{json: request_body} + request_opts = if params[:plug], do: Map.put(request_opts, :plug, params[:plug]), else: request_opts case Client.request(:post, "/unbanChatMember", request_opts) do {:ok, %{"result" => true}} -> diff --git a/lux/lib/lux/prisms/telegram/group_management/unpin_all_chat_messages.ex b/lux/lib/lux/prisms/telegram/group_management/unpin_all_chat_messages.ex index 6be4eba7a..3adc5e482 100644 --- a/lux/lib/lux/prisms/telegram/group_management/unpin_all_chat_messages.ex +++ b/lux/lib/lux/prisms/telegram/group_management/unpin_all_chat_messages.ex @@ -46,8 +46,9 @@ defmodule Lux.Prisms.Telegram.GroupManagement.UnpinAllChatMessages do agent_name = agent[:name] || "Unknown Agent" Logger.info("Agent #{agent_name} attempting to unpin all chat messages in chat #{chat_id}") - request_body = Map.take(params, [:chat_id, :plug]) + request_body = Map.take(params, [:chat_id]) request_opts = %{json: request_body} + request_opts = if params[:plug], do: Map.put(request_opts, :plug, params[:plug]), else: request_opts case Client.request(:post, "/unpinAllChatMessages", request_opts) do {:ok, %{"result" => true}} -> diff --git a/lux/lib/lux/prisms/telegram/group_management/unpin_chat_message.ex b/lux/lib/lux/prisms/telegram/group_management/unpin_chat_message.ex index 3b295a1a1..5960d13f2 100644 --- a/lux/lib/lux/prisms/telegram/group_management/unpin_chat_message.ex +++ b/lux/lib/lux/prisms/telegram/group_management/unpin_chat_message.ex @@ -50,8 +50,9 @@ defmodule Lux.Prisms.Telegram.GroupManagement.UnpinChatMessage do agent_name = agent[:name] || "Unknown Agent" Logger.info("Agent #{agent_name} attempting to unpin chat message in chat #{chat_id}") - request_body = Map.take(params, [:chat_id, :message_id, :plug]) + request_body = Map.take(params, [:chat_id, :message_id]) request_opts = %{json: request_body} + request_opts = if params[:plug], do: Map.put(request_opts, :plug, params[:plug]), else: request_opts case Client.request(:post, "/unpinChatMessage", request_opts) do {:ok, %{"result" => true}} -> diff --git a/lux/test/unit/lux/prisms/telegram/group_management/approve_chat_join_request_test.exs b/lux/test/unit/lux/prisms/telegram/group_management/approve_chat_join_request_test.exs new file mode 100644 index 000000000..5ef30d298 --- /dev/null +++ b/lux/test/unit/lux/prisms/telegram/group_management/approve_chat_join_request_test.exs @@ -0,0 +1,83 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.ApproveChatJoinRequestTest do + use UnitAPICase, async: true + + alias Lux.Prisms.Telegram.GroupManagement.ApproveChatJoinRequest + + @chat_id 123_456_789 + @agent_ctx %{name: "TestAgent"} + + setup do + Req.Test.verify_on_exit!() + :ok + end + + describe "handler/2" do + test "successfully executes approve chat join request" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + {:ok, body, _conn} = Plug.Conn.read_body(conn) + decoded_body = Jason.decode!(body) + assert decoded_body["chat_id"] == @chat_id + assert decoded_body["user_id"] == 987_654 + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(200, Jason.encode!(%{ + "ok" => true, + "result" => true + })) + end) + + assert {:ok, %{success: true, chat_id: @chat_id}} = + ApproveChatJoinRequest.handler( + %{ + chat_id: @chat_id, + user_id: 987_654, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + + test "validates required parameters" do + result = ApproveChatJoinRequest.handler(%{}, @agent_ctx) + assert result == {:error, "Missing or invalid chat_id"} + end + + test "handles Telegram API error" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(400, Jason.encode!(%{ + "ok" => false, + "description" => "Bad Request: error description" + })) + end) + + assert {:error, "Failed to approve join request: Bad Request: error description (HTTP 400)"} = + ApproveChatJoinRequest.handler( + %{ + chat_id: @chat_id, + user_id: 987_654, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + end + + describe "schema validation" do + test "validates input schema" do + prism = ApproveChatJoinRequest.view() + assert Enum.sort(prism.input_schema.required) == Enum.sort(["chat_id", "user_id"]) + end + + test "validates output schema" do + prism = ApproveChatJoinRequest.view() + assert Enum.sort(prism.output_schema.required) == ["chat_id", "success"] + end + end +end diff --git a/lux/test/unit/lux/prisms/telegram/group_management/create_chat_invite_link_test.exs b/lux/test/unit/lux/prisms/telegram/group_management/create_chat_invite_link_test.exs new file mode 100644 index 000000000..7fa1212db --- /dev/null +++ b/lux/test/unit/lux/prisms/telegram/group_management/create_chat_invite_link_test.exs @@ -0,0 +1,80 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.CreateChatInviteLinkTest do + use UnitAPICase, async: true + + alias Lux.Prisms.Telegram.GroupManagement.CreateChatInviteLink + + @chat_id 123_456_789 + @agent_ctx %{name: "TestAgent"} + + setup do + Req.Test.verify_on_exit!() + :ok + end + + describe "handler/2" do + test "successfully executes create chat invite link" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + {:ok, body, _conn} = Plug.Conn.read_body(conn) + decoded_body = Jason.decode!(body) + assert decoded_body["chat_id"] == @chat_id + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(200, Jason.encode!(%{ + "ok" => true, + "result" => %{"invite_link" => "https://t.me/joinchat/xyz"} + })) + end) + + assert {:ok, %{success: true, invite_link: "https://t.me/joinchat/xyz"}} = + CreateChatInviteLink.handler( + %{ + chat_id: @chat_id, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + + test "validates required parameters" do + result = CreateChatInviteLink.handler(%{}, @agent_ctx) + assert result == {:error, "Missing or invalid chat_id"} + end + + test "handles Telegram API error" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(400, Jason.encode!(%{ + "ok" => false, + "description" => "Bad Request: error description" + })) + end) + + assert {:error, "Failed to create invite link: Bad Request: error description (HTTP 400)"} = + CreateChatInviteLink.handler( + %{ + chat_id: @chat_id, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + end + + describe "schema validation" do + test "validates input schema" do + prism = CreateChatInviteLink.view() + assert Enum.sort(prism.input_schema.required) == ["chat_id"] + end + + test "validates output schema" do + prism = CreateChatInviteLink.view() + assert Enum.sort(prism.output_schema.required) == ["invite_link", "success"] + end + end +end diff --git a/lux/test/unit/lux/prisms/telegram/group_management/decline_chat_join_request_test.exs b/lux/test/unit/lux/prisms/telegram/group_management/decline_chat_join_request_test.exs new file mode 100644 index 000000000..9860cce77 --- /dev/null +++ b/lux/test/unit/lux/prisms/telegram/group_management/decline_chat_join_request_test.exs @@ -0,0 +1,83 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.DeclineChatJoinRequestTest do + use UnitAPICase, async: true + + alias Lux.Prisms.Telegram.GroupManagement.DeclineChatJoinRequest + + @chat_id 123_456_789 + @agent_ctx %{name: "TestAgent"} + + setup do + Req.Test.verify_on_exit!() + :ok + end + + describe "handler/2" do + test "successfully executes decline chat join request" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + {:ok, body, _conn} = Plug.Conn.read_body(conn) + decoded_body = Jason.decode!(body) + assert decoded_body["chat_id"] == @chat_id + assert decoded_body["user_id"] == 987_654 + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(200, Jason.encode!(%{ + "ok" => true, + "result" => true + })) + end) + + assert {:ok, %{success: true, chat_id: @chat_id}} = + DeclineChatJoinRequest.handler( + %{ + chat_id: @chat_id, + user_id: 987_654, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + + test "validates required parameters" do + result = DeclineChatJoinRequest.handler(%{}, @agent_ctx) + assert result == {:error, "Missing or invalid chat_id"} + end + + test "handles Telegram API error" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(400, Jason.encode!(%{ + "ok" => false, + "description" => "Bad Request: error description" + })) + end) + + assert {:error, "Failed to decline join request: Bad Request: error description (HTTP 400)"} = + DeclineChatJoinRequest.handler( + %{ + chat_id: @chat_id, + user_id: 987_654, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + end + + describe "schema validation" do + test "validates input schema" do + prism = DeclineChatJoinRequest.view() + assert Enum.sort(prism.input_schema.required) == Enum.sort(["chat_id", "user_id"]) + end + + test "validates output schema" do + prism = DeclineChatJoinRequest.view() + assert Enum.sort(prism.output_schema.required) == ["chat_id", "success"] + end + end +end diff --git a/lux/test/unit/lux/prisms/telegram/group_management/delete_message_test.exs b/lux/test/unit/lux/prisms/telegram/group_management/delete_message_test.exs new file mode 100644 index 000000000..143da0c8b --- /dev/null +++ b/lux/test/unit/lux/prisms/telegram/group_management/delete_message_test.exs @@ -0,0 +1,83 @@ +defmodule Lux.Prisms.Telegram.GroupManagement.DeleteMessageTest do + use UnitAPICase, async: true + + alias Lux.Prisms.Telegram.GroupManagement.DeleteMessage + + @chat_id 123_456_789 + @agent_ctx %{name: "TestAgent"} + + setup do + Req.Test.verify_on_exit!() + :ok + end + + describe "handler/2" do + test "successfully executes delete message" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + {:ok, body, _conn} = Plug.Conn.read_body(conn) + decoded_body = Jason.decode!(body) + assert decoded_body["chat_id"] == @chat_id + assert decoded_body["message_id"] == 101 + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(200, Jason.encode!(%{ + "ok" => true, + "result" => true + })) + end) + + assert {:ok, %{success: true, chat_id: @chat_id}} = + DeleteMessage.handler( + %{ + chat_id: @chat_id, + message_id: 101, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + + test "validates required parameters" do + result = DeleteMessage.handler(%{}, @agent_ctx) + assert result == {:error, "Missing or invalid chat_id"} + end + + test "handles Telegram API error" do + Req.Test.expect(TelegramClientMock, fn conn -> + assert conn.method == "POST" + + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(400, Jason.encode!(%{ + "ok" => false, + "description" => "Bad Request: error description" + })) + end) + + assert {:error, "Failed to delete message: Bad Request: error description (HTTP 400)"} = + DeleteMessage.handler( + %{ + chat_id: @chat_id, + message_id: 101, + plug: {Req.Test, __MODULE__} + }, + @agent_ctx + ) + end + end + + describe "schema validation" do + test "validates input schema" do + prism = DeleteMessage.view() + assert Enum.sort(prism.input_schema.required) == Enum.sort(["chat_id", "message_id"]) + end + + test "validates output schema" do + prism = DeleteMessage.view() + assert Enum.sort(prism.output_schema.required) == ["chat_id", "success"] + end + end +end From 34ca462029f4078c46d8917ca9c5f99c7674e2e5 Mon Sep 17 00:00:00 2001 From: AI Date: Sun, 12 Jul 2026 22:28:35 +0530 Subject: [PATCH 3/3] style: satisfy whitespace gate for Telegram group PR --- .../lux/prisms/telegram/group_management/promote_chat_member.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lux/lib/lux/prisms/telegram/group_management/promote_chat_member.ex b/lux/lib/lux/prisms/telegram/group_management/promote_chat_member.ex index 028e5ae16..4a1b28f44 100644 --- a/lux/lib/lux/prisms/telegram/group_management/promote_chat_member.ex +++ b/lux/lib/lux/prisms/telegram/group_management/promote_chat_member.ex @@ -93,7 +93,7 @@ defmodule Lux.Prisms.Telegram.GroupManagement.PromoteChatMember do Logger.info("Agent #{agent_name} attempting to promote member in chat #{chat_id}") request_body = Map.take(params, [ - :chat_id, :user_id, :is_anonymous, :can_manage_chat, + :chat_id, :user_id, :is_anonymous, :can_manage_chat, :can_delete_messages, :can_manage_video_chats, :can_restrict_members, :can_promote_members, :can_change_info, :can_invite_users, :can_pin_messages, :can_manage_topics