From 8b32a9d0f1705a126d838e2ecac173de7960b87a Mon Sep 17 00:00:00 2001 From: Brian Su Date: Sat, 2 May 2026 18:38:09 -0700 Subject: [PATCH] feat: add Discord message deletion action --- tests/tools/test_discord_tool.py | 21 +++++++++++++++++++-- tools/discord_tool.py | 12 ++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/tests/tools/test_discord_tool.py b/tests/tools/test_discord_tool.py index 51226f0702..41d2cc957b 100644 --- a/tests/tools/test_discord_tool.py +++ b/tests/tools/test_discord_tool.py @@ -175,6 +175,12 @@ class TestDiscordServerValidation: assert "error" in result assert "channel_id" in result["error"] + def test_missing_required_message_id_for_delete(self, monkeypatch): + monkeypatch.setenv("DISCORD_BOT_TOKEN", "test-token") + result = json.loads(discord_admin_handler(action="delete_message", channel_id="11")) + assert "error" in result + assert "message_id" in result["error"] + def test_missing_multiple_params(self, monkeypatch): monkeypatch.setenv("DISCORD_BOT_TOKEN", "test-token") result = json.loads(discord_admin_handler(action="add_role")) @@ -407,10 +413,10 @@ class TestListPins: # --------------------------------------------------------------------------- -# Actions: pin_message / unpin_message +# Actions: pin_message / unpin_message / delete_message # --------------------------------------------------------------------------- -class TestPinUnpin: +class TestPinUnpinDelete: @patch("tools.discord_tool._discord_request") def test_pin_message(self, mock_req, monkeypatch): monkeypatch.setenv("DISCORD_BOT_TOKEN", "test-token") @@ -425,6 +431,16 @@ class TestPinUnpin: mock_req.return_value = None result = json.loads(discord_admin_handler(action="unpin_message", channel_id="11", message_id="500")) assert result["success"] is True + mock_req.assert_called_once_with("DELETE", "/channels/11/pins/500", "test-token") + + @patch("tools.discord_tool._discord_request") + def test_delete_message(self, mock_req, monkeypatch): + monkeypatch.setenv("DISCORD_BOT_TOKEN", "test-token") + mock_req.return_value = None + result = json.loads(discord_admin_handler(action="delete_message", channel_id="11", message_id="500")) + assert result["success"] is True + assert "deleted" in result["message"] + mock_req.assert_called_once_with("DELETE", "/channels/11/messages/500", "test-token") # --------------------------------------------------------------------------- @@ -586,6 +602,7 @@ class TestRegistration: desc = entry.schema["description"] assert "list_guilds()" in desc assert "add_role(guild_id, user_id, role_id)" in desc + assert "delete_message(channel_id, message_id)" in desc # Core actions should NOT be in admin description assert "fetch_messages(" not in desc assert "create_thread(" not in desc diff --git a/tools/discord_tool.py b/tools/discord_tool.py index 589b702228..1da43ac914 100644 --- a/tools/discord_tool.py +++ b/tools/discord_tool.py @@ -418,6 +418,12 @@ def _unpin_message(token: str, channel_id: str, message_id: str, **_kwargs: Any) return json.dumps({"success": True, "message": f"Message {message_id} unpinned."}) +def _delete_message(token: str, channel_id: str, message_id: str, **_kwargs: Any) -> str: + """Delete a message from a channel or thread.""" + _discord_request("DELETE", f"/channels/{channel_id}/messages/{message_id}", token) + return json.dumps({"success": True, "message": f"Message {message_id} deleted."}) + + def _create_thread( token: str, channel_id: str, name: str, message_id: Optional[str] = None, @@ -476,6 +482,7 @@ _ACTIONS = { "list_pins": _list_pins, "pin_message": _pin_message, "unpin_message": _unpin_message, + "delete_message": _delete_message, "create_thread": _create_thread, "add_role": _add_role, "remove_role": _remove_role, @@ -502,6 +509,7 @@ _ACTION_MANIFEST: List[Tuple[str, str, str]] = [ ("list_pins", "(channel_id)", "pinned messages in a channel"), ("pin_message", "(channel_id, message_id)", "pin a message"), ("unpin_message", "(channel_id, message_id)", "unpin a message"), + ("delete_message", "(channel_id, message_id)", "delete a message"), ("create_thread", "(channel_id, name)", "create a public thread; optional message_id anchor"), ("add_role", "(guild_id, user_id, role_id)", "assign a role"), ("remove_role", "(guild_id, user_id, role_id)", "remove a role"), @@ -522,6 +530,7 @@ _REQUIRED_PARAMS: Dict[str, List[str]] = { "list_pins": ["channel_id"], "pin_message": ["channel_id", "message_id"], "unpin_message": ["channel_id", "message_id"], + "delete_message": ["channel_id", "message_id"], "create_thread": ["channel_id", "name"], "add_role": ["guild_id", "user_id", "role_id"], "remove_role": ["guild_id", "user_id", "role_id"], @@ -758,6 +767,9 @@ _ACTION_403_HINT = { "unpin_message": ( "Bot lacks MANAGE_MESSAGES permission in this channel." ), + "delete_message": ( + "Bot lacks MANAGE_MESSAGES permission in this channel, or cannot view the channel/message." + ), "create_thread": ( "Bot lacks CREATE_PUBLIC_THREADS in this channel, or cannot view it." ),