- Add Telegram integration support by introducing a dispatcher for handling Telegram events and messages. - Implement event notifications for robot configuration changes (creation, update, deletion) to facilitate integration with external services. - Refactor the robot initialization process to load robots into cache and start the dispatcher, improving the overall system setup. - Update the delivery event structure to include additional metadata for better context during message handling. - Enhance logging capabilities for better observability during robot execution and event processing.
99 lines
2.7 KiB
Go
99 lines
2.7 KiB
Go
package integrations
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/yaoapp/kun/log"
|
|
"github.com/yaoapp/yao/event"
|
|
"github.com/yaoapp/yao/openapi/response"
|
|
)
|
|
|
|
// WebhookPayload is the event payload pushed to "integration.webhook.{provider}".
|
|
// Subscribers receive this and handle it according to their own protocol.
|
|
type WebhookPayload struct {
|
|
Provider string `json:"provider"`
|
|
AppID string `json:"app_id"`
|
|
Method string `json:"method"`
|
|
Body []byte `json:"body,omitempty"`
|
|
Headers map[string]string `json:"headers,omitempty"`
|
|
Query map[string]string `json:"query,omitempty"`
|
|
}
|
|
|
|
// Attach registers the integrations webhook endpoints.
|
|
// These are public endpoints (no OAuth) since external platforms push here.
|
|
func Attach(group *gin.RouterGroup) {
|
|
group.GET("/:provider/:app_id", webhookHandler)
|
|
group.POST("/:provider/:app_id", webhookHandler)
|
|
}
|
|
|
|
// webhookHandler receives webhooks from external platforms, packs the raw
|
|
// request into a WebhookPayload, and pushes an event for async processing.
|
|
// It returns HTTP 200 immediately — subscribers handle the rest.
|
|
func webhookHandler(c *gin.Context) {
|
|
provider := c.Param("provider")
|
|
appID := c.Param("app_id")
|
|
|
|
if provider == "" || appID == "" {
|
|
response.RespondWithError(c, response.StatusBadRequest, &response.ErrorResponse{
|
|
Code: response.ErrInvalidRequest.Code,
|
|
ErrorDescription: "provider and app_id are required",
|
|
})
|
|
return
|
|
}
|
|
|
|
payload := WebhookPayload{
|
|
Provider: provider,
|
|
AppID: appID,
|
|
Method: c.Request.Method,
|
|
}
|
|
|
|
// Read body for POST/PUT/PATCH
|
|
if c.Request.Body != nil && c.Request.Method != http.MethodGet {
|
|
body, err := io.ReadAll(c.Request.Body)
|
|
if err != nil {
|
|
log.Error("integrations webhook: read body failed provider=%s app_id=%s: %v", provider, appID, err)
|
|
c.Status(http.StatusOK)
|
|
return
|
|
}
|
|
payload.Body = body
|
|
}
|
|
|
|
payload.Headers = flattenHeaders(c.Request.Header)
|
|
payload.Query = flattenQuery(c.Request.URL.Query())
|
|
|
|
c.Status(http.StatusOK)
|
|
|
|
eventType := "integration.webhook." + provider
|
|
if _, err := event.Push(context.Background(), eventType, payload); err != nil {
|
|
log.Error("integrations webhook: event.Push failed type=%s app_id=%s: %v", eventType, appID, err)
|
|
}
|
|
}
|
|
|
|
func flattenHeaders(h http.Header) map[string]string {
|
|
if len(h) == 0 {
|
|
return nil
|
|
}
|
|
out := make(map[string]string, len(h))
|
|
for k, v := range h {
|
|
if len(v) > 0 {
|
|
out[k] = v[0]
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func flattenQuery(q map[string][]string) map[string]string {
|
|
if len(q) == 0 {
|
|
return nil
|
|
}
|
|
out := make(map[string]string, len(q))
|
|
for k, v := range q {
|
|
if len(v) > 0 {
|
|
out[k] = v[0]
|
|
}
|
|
}
|
|
return out
|
|
}
|