Add Webhook model and update helpers (#6)
This commit is contained in:
parent
8be34bdcbf
commit
eb5908682d
@ -46,6 +46,7 @@ if TYPE_CHECKING:
|
||||
CategoryChannel,
|
||||
Thread,
|
||||
DMChannel,
|
||||
Webhook,
|
||||
)
|
||||
from .ui.view import View
|
||||
from .enums import ChannelType as EnumChannelType
|
||||
@ -133,6 +134,7 @@ class Client:
|
||||
) # Placeholder for User model cache if needed
|
||||
self._messages: Dict[Snowflake, "Message"] = {}
|
||||
self._views: Dict[Snowflake, "View"] = {}
|
||||
self._webhooks: Dict[Snowflake, "Webhook"] = {}
|
||||
|
||||
# Default whether replies mention the user
|
||||
self.mention_replies: bool = mention_replies
|
||||
@ -648,6 +650,19 @@ class Client:
|
||||
self._messages[message.id] = message
|
||||
return message
|
||||
|
||||
def parse_webhook(self, data: Union[Dict[str, Any], "Webhook"]) -> "Webhook":
|
||||
"""Parses webhook data and returns a Webhook object, updating cache."""
|
||||
|
||||
from .models import Webhook
|
||||
|
||||
if isinstance(data, Webhook):
|
||||
webhook = data
|
||||
webhook._client = self # type: ignore[attr-defined]
|
||||
else:
|
||||
webhook = Webhook(data, client_instance=self)
|
||||
self._webhooks[webhook.id] = webhook
|
||||
return webhook
|
||||
|
||||
async def fetch_user(self, user_id: Snowflake) -> Optional["User"]:
|
||||
"""Fetches a user by ID from Discord."""
|
||||
if self._closed:
|
||||
@ -1083,6 +1098,36 @@ class Client:
|
||||
print(f"Failed to fetch channel {channel_id}: {e}")
|
||||
return None
|
||||
|
||||
async def create_webhook(
|
||||
self, channel_id: Snowflake, payload: Dict[str, Any]
|
||||
) -> "Webhook":
|
||||
"""|coro| Create a webhook in the given channel."""
|
||||
|
||||
if self._closed:
|
||||
raise DisagreementException("Client is closed.")
|
||||
|
||||
data = await self._http.create_webhook(channel_id, payload)
|
||||
return self.parse_webhook(data)
|
||||
|
||||
async def edit_webhook(
|
||||
self, webhook_id: Snowflake, payload: Dict[str, Any]
|
||||
) -> "Webhook":
|
||||
"""|coro| Edit an existing webhook."""
|
||||
|
||||
if self._closed:
|
||||
raise DisagreementException("Client is closed.")
|
||||
|
||||
data = await self._http.edit_webhook(webhook_id, payload)
|
||||
return self.parse_webhook(data)
|
||||
|
||||
async def delete_webhook(self, webhook_id: Snowflake) -> None:
|
||||
"""|coro| Delete a webhook by ID."""
|
||||
|
||||
if self._closed:
|
||||
raise DisagreementException("Client is closed.")
|
||||
|
||||
await self._http.delete_webhook(webhook_id)
|
||||
|
||||
# --- Application Command Methods ---
|
||||
async def process_interaction(self, interaction: Interaction) -> None:
|
||||
"""Internal method to process an interaction from the gateway."""
|
||||
|
@ -20,7 +20,7 @@ from . import __version__ # For User-Agent
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .client import Client
|
||||
from .models import Message
|
||||
from .models import Message, Webhook
|
||||
from .interactions import ApplicationCommand, InteractionResponsePayload, Snowflake
|
||||
|
||||
# Discord API constants
|
||||
@ -323,6 +323,33 @@ class HTTPClient:
|
||||
"""Fetches a channel by ID."""
|
||||
return await self.request("GET", f"/channels/{channel_id}")
|
||||
|
||||
async def create_webhook(
|
||||
self, channel_id: "Snowflake", payload: Dict[str, Any]
|
||||
) -> "Webhook":
|
||||
"""Creates a webhook in the specified channel."""
|
||||
|
||||
data = await self.request(
|
||||
"POST", f"/channels/{channel_id}/webhooks", payload=payload
|
||||
)
|
||||
from .models import Webhook
|
||||
|
||||
return Webhook(data)
|
||||
|
||||
async def edit_webhook(
|
||||
self, webhook_id: "Snowflake", payload: Dict[str, Any]
|
||||
) -> "Webhook":
|
||||
"""Edits an existing webhook."""
|
||||
|
||||
data = await self.request("PATCH", f"/webhooks/{webhook_id}", payload=payload)
|
||||
from .models import Webhook
|
||||
|
||||
return Webhook(data)
|
||||
|
||||
async def delete_webhook(self, webhook_id: "Snowflake") -> None:
|
||||
"""Deletes a webhook."""
|
||||
|
||||
await self.request("DELETE", f"/webhooks/{webhook_id}")
|
||||
|
||||
async def get_user(self, user_id: "Snowflake") -> Dict[str, Any]:
|
||||
"""Fetches a user object for a given user ID."""
|
||||
return await self.request("GET", f"/users/{user_id}")
|
||||
|
@ -1094,6 +1094,28 @@ class PartialChannel:
|
||||
return f"<PartialChannel id='{self.id}' name='{self.name}' type='{type_name}'>"
|
||||
|
||||
|
||||
class Webhook:
|
||||
"""Represents a Discord Webhook."""
|
||||
|
||||
def __init__(
|
||||
self, data: Dict[str, Any], client_instance: Optional["Client"] = None
|
||||
):
|
||||
self._client: Optional["Client"] = client_instance
|
||||
self.id: str = data["id"]
|
||||
self.type: int = int(data.get("type", 1))
|
||||
self.guild_id: Optional[str] = data.get("guild_id")
|
||||
self.channel_id: Optional[str] = data.get("channel_id")
|
||||
self.name: Optional[str] = data.get("name")
|
||||
self.avatar: Optional[str] = data.get("avatar")
|
||||
self.token: Optional[str] = data.get("token")
|
||||
self.application_id: Optional[str] = data.get("application_id")
|
||||
self.url: Optional[str] = data.get("url")
|
||||
self.user: Optional[User] = User(data["user"]) if data.get("user") else None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Webhook id='{self.id}' name='{self.name}'>"
|
||||
|
||||
|
||||
# --- Message Components ---
|
||||
|
||||
|
||||
|
@ -9,7 +9,7 @@ from disagreement.http import HTTPClient
|
||||
|
||||
http = HTTPClient(token="TOKEN")
|
||||
payload = {"name": "My Webhook"}
|
||||
webhook_data = await http.create_webhook("123", payload)
|
||||
webhook = await http.create_webhook("123", payload)
|
||||
```
|
||||
|
||||
## Edit a webhook
|
||||
@ -24,11 +24,10 @@ await http.edit_webhook("456", {"name": "Renamed"})
|
||||
await http.delete_webhook("456")
|
||||
```
|
||||
|
||||
The methods return the raw webhook JSON. You can construct a `Webhook` model if needed:
|
||||
The methods now return a `Webhook` object directly:
|
||||
|
||||
```python
|
||||
from disagreement.models import Webhook
|
||||
|
||||
webhook = Webhook(webhook_data)
|
||||
print(webhook.id, webhook.name)
|
||||
```
|
||||
|
@ -42,3 +42,99 @@ async def test_delete_followup_message_calls_request():
|
||||
f"/webhooks/app_id/token/messages/456",
|
||||
use_auth_header=False,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_webhook_returns_model_and_calls_request():
|
||||
http = HTTPClient(token="t")
|
||||
http.request = AsyncMock(return_value={"id": "1"})
|
||||
payload = {"name": "wh"}
|
||||
webhook = await http.create_webhook("123", payload)
|
||||
http.request.assert_called_once_with(
|
||||
"POST",
|
||||
"/channels/123/webhooks",
|
||||
payload=payload,
|
||||
)
|
||||
from disagreement.models import Webhook
|
||||
|
||||
assert isinstance(webhook, Webhook)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_edit_webhook_returns_model_and_calls_request():
|
||||
http = HTTPClient(token="t")
|
||||
http.request = AsyncMock(return_value={"id": "1"})
|
||||
payload = {"name": "rename"}
|
||||
webhook = await http.edit_webhook("1", payload)
|
||||
http.request.assert_called_once_with(
|
||||
"PATCH",
|
||||
"/webhooks/1",
|
||||
payload=payload,
|
||||
)
|
||||
from disagreement.models import Webhook
|
||||
|
||||
assert isinstance(webhook, Webhook)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_webhook_calls_request():
|
||||
http = HTTPClient(token="t")
|
||||
http.request = AsyncMock()
|
||||
await http.delete_webhook("1")
|
||||
http.request.assert_called_once_with(
|
||||
"DELETE",
|
||||
"/webhooks/1",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_client_create_webhook_returns_model():
|
||||
from types import SimpleNamespace
|
||||
from disagreement.client import Client
|
||||
from disagreement.models import Webhook
|
||||
|
||||
http = SimpleNamespace(create_webhook=AsyncMock(return_value={"id": "1"}))
|
||||
client = Client.__new__(Client)
|
||||
client._http = http
|
||||
client._closed = False
|
||||
client._webhooks = {}
|
||||
|
||||
webhook = await client.create_webhook("123", {"name": "wh"})
|
||||
|
||||
http.create_webhook.assert_awaited_once_with("123", {"name": "wh"})
|
||||
assert isinstance(webhook, Webhook)
|
||||
assert client._webhooks["1"] is webhook
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_client_edit_webhook_returns_model():
|
||||
from types import SimpleNamespace
|
||||
from disagreement.client import Client
|
||||
from disagreement.models import Webhook
|
||||
|
||||
http = SimpleNamespace(edit_webhook=AsyncMock(return_value={"id": "1"}))
|
||||
client = Client.__new__(Client)
|
||||
client._http = http
|
||||
client._closed = False
|
||||
client._webhooks = {}
|
||||
|
||||
webhook = await client.edit_webhook("1", {"name": "rename"})
|
||||
|
||||
http.edit_webhook.assert_awaited_once_with("1", {"name": "rename"})
|
||||
assert isinstance(webhook, Webhook)
|
||||
assert client._webhooks["1"] is webhook
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_client_delete_webhook_calls_http():
|
||||
from types import SimpleNamespace
|
||||
from disagreement.client import Client
|
||||
|
||||
http = SimpleNamespace(delete_webhook=AsyncMock())
|
||||
client = Client.__new__(Client)
|
||||
client._http = http
|
||||
client._closed = False
|
||||
|
||||
await client.delete_webhook("1")
|
||||
|
||||
http.delete_webhook.assert_awaited_once_with("1")
|
||||
|
Loading…
x
Reference in New Issue
Block a user