Skip to content

Commit 21076d5

Browse files
authored
feat: slack webhook handler (#516)
1 parent 93f0d4c commit 21076d5

File tree

4 files changed

+109
-2
lines changed

4 files changed

+109
-2
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ dependencies = [
7171
"mcp[cli]",
7272
"neo4j",
7373
"modal>=0.73.45",
74+
"slack-sdk",
7475
]
7576

7677
license = { text = "Apache-2.0" }

src/codegen/extensions/events/app.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,22 @@
33
import modal # deptry: ignore
44

55
from codegen.extensions.events.linear import Linear
6+
from codegen.extensions.events.slack import Slack
67

78
logger = logging.getLogger(__name__)
89

910

1011
class CodegenApp(modal.App):
1112
linear: Linear
13+
slack: Slack
1214

13-
def __init__(self, name, modal_api_key, image: modal.Image):
15+
def __init__(self, name: str, modal_api_key: str, image: modal.Image):
1416
self._modal_api_key = modal_api_key
1517
self._image = image
1618
self._name = name
1719

1820
super().__init__(name=name, image=image)
1921

20-
# Expose a attribute that provides the event decorator for different providers.
22+
# Expose attributes that provide event decorators for different providers.
2123
self.linear = Linear(self)
24+
self.slack = Slack(self)
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import logging
2+
import os
3+
from typing import Literal
4+
5+
from pydantic import BaseModel, Field
6+
from slack_sdk import WebClient
7+
8+
from codegen.extensions.events.interface import EventHandlerManagerProtocol
9+
10+
logger = logging.getLogger(__name__)
11+
logger.setLevel(logging.DEBUG)
12+
13+
14+
class RichTextElement(BaseModel):
15+
type: str
16+
user_id: str | None = None
17+
text: str | None = None
18+
19+
20+
class RichTextSection(BaseModel):
21+
type: Literal["rich_text_section"]
22+
elements: list[RichTextElement]
23+
24+
25+
class Block(BaseModel):
26+
type: Literal["rich_text"]
27+
block_id: str
28+
elements: list[RichTextSection]
29+
30+
31+
class SlackEvent(BaseModel):
32+
user: str
33+
type: str
34+
ts: str
35+
client_msg_id: str
36+
text: str
37+
team: str
38+
blocks: list[Block]
39+
channel: str
40+
event_ts: str
41+
42+
43+
class SlackWebhookPayload(BaseModel):
44+
token: str | None = Field(None)
45+
team_id: str | None = Field(None)
46+
api_app_id: str | None = Field(None)
47+
event: SlackEvent | None = Field(None)
48+
type: str | None = Field(None)
49+
event_id: str | None = Field(None)
50+
event_time: int | None = Field(None)
51+
challenge: str | None = Field(None)
52+
subtype: str | None = Field(None)
53+
54+
55+
class Slack(EventHandlerManagerProtocol):
56+
_client: WebClient | None = None
57+
58+
def __init__(self, app):
59+
self.registered_handlers = {}
60+
61+
@property
62+
def client(self) -> WebClient:
63+
if not self._client:
64+
self._client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
65+
return self._client
66+
67+
def unsubscribe_all_handlers(self):
68+
logger.info("[HANDLERS] Clearing all handlers")
69+
self.registered_handlers.clear()
70+
71+
def handle(self, event: SlackWebhookPayload):
72+
logger.info("[HANDLER] Handling Slack event")
73+
if event.type == "url_verification":
74+
return {"challenge": event.challenge}
75+
elif event.type == "event_callback":
76+
event = event.event
77+
if event.type not in self.registered_handlers:
78+
logger.info(f"[HANDLER] No handler found for event type: {event.type}")
79+
return {"message": "Event handled successfully"}
80+
else:
81+
handler = self.registered_handlers[event.type]
82+
return handler(event)
83+
else:
84+
logger.info(f"[HANDLER] No handler found for event type: {event.type}")
85+
return {"message": "Event handled successfully"}
86+
87+
def event(self, event_name: str):
88+
"""Decorator for registering a Slack event handler."""
89+
logger.info(f"[EVENT] Registering handler for {event_name}")
90+
91+
def register_handler(func):
92+
# Register the handler with the app's registry
93+
func_name = func.__qualname__
94+
logger.info(f"[EVENT] Registering function {func_name} for {event_name}")
95+
96+
def new_func(event):
97+
return func(self.client, event)
98+
99+
self.registered_handlers[event_name] = new_func
100+
return new_func
101+
102+
return register_handler

uv.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)