Skip to content

Commit 8149174

Browse files
committed
Phase 4: Code quality - DRY refactoring and constants
- Add _build_webhook_urls() and _validate_token_exists() helpers to webhook_service.py - Add named constants (DEFAULT_REQUEST_LIMIT, DEFAULT_TIMEOUT_SECONDS, POLL_INTERVAL_SECONDS) to request_service.py - Add proper type hints to tool_handlers.py _get_handler() method - Eliminates ~80+ lines of duplicate code
1 parent f44faf4 commit 8149174

File tree

3 files changed

+81
-94
lines changed

3 files changed

+81
-94
lines changed

handlers/tool_handlers.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
from __future__ import annotations
99

1010
import logging
11-
from typing import Any
11+
from typing import Any, Callable, Coroutine
1212

1313
from mcp.types import TextContent
1414

15-
from models.schemas import WebhookConfig, SearchFilters, DeleteFilters
15+
from models.schemas import WebhookConfig, SearchFilters, DeleteFilters, ToolResult
1616
from services.webhook_service import WebhookService
1717
from services.request_service import RequestService
1818
from services.bugbounty_service import BugBountyService
@@ -84,7 +84,9 @@ async def handle(
8484
"An unexpected error occurred. Please report this issue."
8585
)
8686

87-
def _get_handler(self, name: str):
87+
def _get_handler(
88+
self, name: str
89+
) -> Callable[[dict[str, Any]], Coroutine[Any, Any, ToolResult]] | None:
8890
"""Get the handler method for a tool name.
8991
9092
Args:
@@ -93,7 +95,7 @@ def _get_handler(self, name: str):
9395
Returns:
9496
Handler coroutine or None if not found
9597
"""
96-
handlers = {
98+
handlers: dict[str, Callable[[dict[str, Any]], Coroutine[Any, Any, ToolResult]]] = {
9799
"create_webhook": self._handle_create_webhook,
98100
"create_webhook_with_config": self._handle_create_webhook_with_config,
99101
"send_to_webhook": self._handle_send_to_webhook,

services/request_service.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
from models.schemas import SearchFilters, DeleteFilters, ToolResult
1515
from utils.http_client import WebhookHttpClient
1616

17+
# Constants for request handling
18+
DEFAULT_REQUEST_LIMIT = 10
19+
DEFAULT_TIMEOUT_SECONDS = 60
20+
POLL_INTERVAL_SECONDS = 2.0
21+
1722

1823
class RequestService:
1924
"""Service for webhook request operations.
@@ -37,7 +42,7 @@ def __init__(self, client: WebhookHttpClient) -> None:
3742
async def get_all(
3843
self,
3944
webhook_token: str,
40-
limit: int = 10,
45+
limit: int = DEFAULT_REQUEST_LIMIT,
4146
request_type: str | None = None,
4247
) -> ToolResult:
4348
"""Get all requests sent to a webhook.
@@ -232,7 +237,7 @@ def _format_request(req: dict[str, Any]) -> dict[str, Any]:
232237
async def wait_for_request(
233238
self,
234239
webhook_token: str,
235-
timeout_seconds: int = 60,
240+
timeout_seconds: int = DEFAULT_TIMEOUT_SECONDS,
236241
request_type: str | None = None,
237242
) -> ToolResult:
238243
"""Wait for a new HTTP request to be received by the webhook.
@@ -251,7 +256,7 @@ async def wait_for_request(
251256
from utils.http_client import WebhookApiError
252257

253258
start_time = time.time()
254-
poll_interval = 2.0 # Poll every 2 seconds
259+
poll_interval = POLL_INTERVAL_SECONDS
255260
max_retries = 3
256261
retry_count = 0
257262

@@ -321,7 +326,7 @@ async def wait_for_request(
321326
async def wait_for_email(
322327
self,
323328
webhook_token: str,
324-
timeout_seconds: int = 60,
329+
timeout_seconds: int = DEFAULT_TIMEOUT_SECONDS,
325330
extract_links: bool = True,
326331
) -> ToolResult:
327332
"""Wait for an email to be received at the webhook's email address.
@@ -342,7 +347,7 @@ async def wait_for_email(
342347
from utils.http_client import WebhookApiError
343348

344349
start_time = time.time()
345-
poll_interval = 2.0 # Poll every 2 seconds
350+
poll_interval = POLL_INTERVAL_SECONDS
346351
max_retries = 3
347352
retry_count = 0
348353

services/webhook_service.py

Lines changed: 65 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,56 @@ def __init__(self, client: WebhookHttpClient) -> None:
3232
"""
3333
self._client = client
3434

35+
def _build_webhook_urls(self, token: str, alias: str | None = None) -> dict[str, str]:
36+
"""Build all URL variants for a webhook token.
37+
38+
Args:
39+
token: The webhook UUID
40+
alias: Optional custom alias
41+
42+
Returns:
43+
Dict with url, subdomain_url, api_url, email, and dns keys
44+
"""
45+
identifier = alias if alias else token
46+
return {
47+
"url": f"{WEBHOOK_SITE_API}/{identifier}",
48+
"subdomain_url": f"https://{token}.webhook.site",
49+
"api_url": f"{WEBHOOK_SITE_API}/token/{token}",
50+
"email": f"{token}@email.webhook.site",
51+
"dns": f"{token}.dnshook.site",
52+
}
53+
54+
async def _validate_token_exists(self, webhook_token: str) -> ToolResult | None:
55+
"""Validate that a webhook token exists.
56+
57+
Args:
58+
webhook_token: The webhook UUID to validate
59+
60+
Returns:
61+
None if token is valid, ToolResult with error if invalid
62+
"""
63+
try:
64+
await self._client.get(f"/token/{webhook_token}")
65+
return None # Token is valid
66+
except WebhookApiError as e:
67+
if e.status_code == 404:
68+
return ToolResult(
69+
success=False,
70+
message=f"Token '{webhook_token}' not found or expired",
71+
data=None
72+
)
73+
return ToolResult(
74+
success=False,
75+
message=f"Failed to validate token: {str(e)}",
76+
data=None
77+
)
78+
except Exception as e:
79+
return ToolResult(
80+
success=False,
81+
message=f"Failed to validate token: {str(e)}",
82+
data=None
83+
)
84+
3585
async def create(self) -> ToolResult:
3686
"""Create a new webhook with default settings.
3787
@@ -44,23 +94,15 @@ async def create(self) -> ToolResult:
4494

4595
token = data.get("uuid")
4696
alias = data.get("alias")
47-
url = f"{WEBHOOK_SITE_API}/{alias if alias else token}"
48-
subdomain_url = f"https://{token}.webhook.site"
49-
email = f"{token}@email.webhook.site"
50-
api_url = f"{WEBHOOK_SITE_API}/token/{token}"
51-
dns = f"{token}.dnshook.site"
97+
urls = self._build_webhook_urls(token, alias)
5298

5399
return ToolResult(
54100
success=True,
55-
message=f"Webhook created! Send requests to: {url}",
101+
message=f"Webhook created! Send requests to: {urls['url']}",
56102
data={
57103
"token": token,
58104
"alias": alias,
59-
"url": url,
60-
"subdomain_url": subdomain_url,
61-
"api_url": api_url,
62-
"email": email,
63-
"dns": dns,
105+
**urls,
64106
"expires_at": data.get("expires_at"),
65107
"default_status": data.get("default_status"),
66108
"default_content": data.get("default_content"),
@@ -87,23 +129,15 @@ async def create_with_config(self, config: WebhookConfig) -> ToolResult:
87129

88130
token = data.get("uuid")
89131
alias = data.get("alias")
90-
url = f"{WEBHOOK_SITE_API}/{alias if alias else token}"
91-
subdomain_url = f"https://{token}.webhook.site"
92-
email = f"{token}@email.webhook.site"
93-
api_url = f"{WEBHOOK_SITE_API}/token/{token}"
94-
dns = f"{token}.dnshook.site"
132+
urls = self._build_webhook_urls(token, alias)
95133

96134
return ToolResult(
97135
success=True,
98-
message=f"Webhook created with custom config! URL: {url}",
136+
message=f"Webhook created with custom config! URL: {urls['url']}",
99137
data={
100138
"token": token,
101139
"alias": alias,
102-
"url": url,
103-
"subdomain_url": subdomain_url,
104-
"api_url": api_url,
105-
"email": email,
106-
"dns": dns,
140+
**urls,
107141
"default_status": data.get("default_status"),
108142
"default_content": data.get("default_content"),
109143
"default_content_type": data.get("default_content_type"),
@@ -241,27 +275,9 @@ async def get_url(self, webhook_token: str, validate: bool = False) -> ToolResul
241275
ToolResult with token and full URL
242276
"""
243277
if validate:
244-
try:
245-
await self._client.get(f"/token/{webhook_token}")
246-
# If we get here, token is valid
247-
except WebhookApiError as e:
248-
if e.status_code == 404:
249-
return ToolResult(
250-
success=False,
251-
message=f"Token '{webhook_token}' not found or expired",
252-
data=None
253-
)
254-
return ToolResult(
255-
success=False,
256-
message=f"Failed to validate token: {str(e)}",
257-
data=None
258-
)
259-
except Exception as e:
260-
return ToolResult(
261-
success=False,
262-
message=f"Failed to validate token: {str(e)}",
263-
data=None
264-
)
278+
validation_error = await self._validate_token_exists(webhook_token)
279+
if validation_error:
280+
return validation_error
265281

266282
url = f"{WEBHOOK_SITE_API}/{webhook_token}"
267283

@@ -287,27 +303,9 @@ async def get_email(self, webhook_token: str, validate: bool = False) -> ToolRes
287303
ToolResult with token, email address, and URL
288304
"""
289305
if validate:
290-
try:
291-
await self._client.get(f"/token/{webhook_token}")
292-
# If we get here, token is valid
293-
except WebhookApiError as e:
294-
if e.status_code == 404:
295-
return ToolResult(
296-
success=False,
297-
message=f"Token '{webhook_token}' not found or expired",
298-
data=None
299-
)
300-
return ToolResult(
301-
success=False,
302-
message=f"Failed to validate token: {str(e)}",
303-
data=None
304-
)
305-
except Exception as e:
306-
return ToolResult(
307-
success=False,
308-
message=f"Failed to validate token: {str(e)}",
309-
data=None
310-
)
306+
validation_error = await self._validate_token_exists(webhook_token)
307+
if validation_error:
308+
return validation_error
311309

312310
email = f"{webhook_token}@email.webhook.site"
313311
url = f"{WEBHOOK_SITE_API}/{webhook_token}"
@@ -336,27 +334,9 @@ async def get_dns(self, webhook_token: str, validate: bool = False) -> ToolResul
336334
ToolResult with token, DNS domain, example subdomain usage, and URL
337335
"""
338336
if validate:
339-
try:
340-
await self._client.get(f"/token/{webhook_token}")
341-
# If we get here, token is valid
342-
except WebhookApiError as e:
343-
if e.status_code == 404:
344-
return ToolResult(
345-
success=False,
346-
message=f"Token '{webhook_token}' not found or expired",
347-
data=None
348-
)
349-
return ToolResult(
350-
success=False,
351-
message=f"Failed to validate token: {str(e)}",
352-
data=None
353-
)
354-
except Exception as e:
355-
return ToolResult(
356-
success=False,
357-
message=f"Failed to validate token: {str(e)}",
358-
data=None
359-
)
337+
validation_error = await self._validate_token_exists(webhook_token)
338+
if validation_error:
339+
return validation_error
360340

361341
dns_domain = f"{webhook_token}.dnshook.site"
362342
url = f"{WEBHOOK_SITE_API}/{webhook_token}"

0 commit comments

Comments
 (0)