This commit is contained in:
parent
9c78ea2d48
commit
7c684af8c3
79 changed files with 3594 additions and 3257 deletions
212
internal/platform/slack/slack.go
Normal file
212
internal/platform/slack/slack.go
Normal file
|
@ -0,0 +1,212 @@
|
|||
package slack
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.nakama.town/fmartingr/butterrobot/internal/config"
|
||||
"git.nakama.town/fmartingr/butterrobot/internal/model"
|
||||
)
|
||||
|
||||
// SlackPlatform implements the Platform interface for Slack
|
||||
type SlackPlatform struct {
|
||||
config *config.SlackConfig
|
||||
}
|
||||
|
||||
// New creates a new SlackPlatform instance
|
||||
func New(cfg *config.SlackConfig) *SlackPlatform {
|
||||
return &SlackPlatform{
|
||||
config: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes the Slack platform
|
||||
func (s *SlackPlatform) Init(_ *config.Config) error {
|
||||
// Validate config
|
||||
if s.config.Token == "" || s.config.BotOAuthAccessToken == "" {
|
||||
return model.ErrPlatformInit
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseIncomingMessage parses an incoming Slack message
|
||||
func (s *SlackPlatform) ParseIncomingMessage(r *http.Request) (*model.Message, error) {
|
||||
// Read request body
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
// Parse JSON
|
||||
var requestData map[string]interface{}
|
||||
if err := json.Unmarshal(body, &requestData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Verify Slack request
|
||||
// This is a simplified version, production should include signature verification
|
||||
urlVerify, ok := requestData["type"]
|
||||
if ok && urlVerify == "url_verification" {
|
||||
return nil, errors.New("url verification") // Handle separately
|
||||
}
|
||||
|
||||
// Process event
|
||||
event, ok := requestData["event"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, errors.New("invalid event")
|
||||
}
|
||||
|
||||
// Create message
|
||||
msg := &model.Message{
|
||||
Raw: requestData,
|
||||
}
|
||||
|
||||
// Get text
|
||||
if text, ok := event["text"].(string); ok {
|
||||
msg.Text = text
|
||||
}
|
||||
|
||||
// Get channel
|
||||
if channel, ok := event["channel"].(string); ok {
|
||||
msg.Chat = channel
|
||||
|
||||
// Create Channel object
|
||||
channelRaw, err := s.ParseChannelFromMessage(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg.Channel = &model.Channel{
|
||||
Platform: "slack",
|
||||
PlatformChannelID: channel,
|
||||
ChannelRaw: channelRaw,
|
||||
}
|
||||
}
|
||||
|
||||
// Check if from bot
|
||||
if botID, ok := event["bot_id"].(string); ok && botID != "" {
|
||||
msg.FromBot = true
|
||||
}
|
||||
|
||||
// Get user
|
||||
if user, ok := event["user"].(string); ok {
|
||||
msg.Author = user
|
||||
}
|
||||
|
||||
// Get timestamp
|
||||
if ts, ok := event["ts"].(string); ok {
|
||||
// Convert Unix timestamp
|
||||
parts := strings.Split(ts, ".")
|
||||
if len(parts) > 0 {
|
||||
if sec, err := parseInt64(parts[0]); err == nil {
|
||||
msg.Date = time.Unix(sec, 0)
|
||||
msg.ID = ts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// ParseChannelNameFromRaw extracts a human-readable channel name from raw data
|
||||
func (s *SlackPlatform) ParseChannelNameFromRaw(channelRaw map[string]interface{}) string {
|
||||
// Extract name from channel raw data
|
||||
if name, ok := channelRaw["name"].(string); ok {
|
||||
return name
|
||||
}
|
||||
|
||||
// Fallback to ID if available
|
||||
if id, ok := channelRaw["id"].(string); ok {
|
||||
return id
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// ParseChannelFromMessage extracts channel data from a message
|
||||
func (s *SlackPlatform) ParseChannelFromMessage(body []byte) (map[string]any, error) {
|
||||
// Parse JSON
|
||||
var requestData map[string]interface{}
|
||||
if err := json.Unmarshal(body, &requestData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Extract channel info from event
|
||||
event, ok := requestData["event"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, errors.New("invalid event data")
|
||||
}
|
||||
|
||||
channelID, ok := event["channel"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("channel ID not found")
|
||||
}
|
||||
|
||||
// In a real implementation, you might want to fetch more details about the channel
|
||||
// using the Slack API, but for simplicity we'll just return the ID
|
||||
channelRaw := map[string]interface{}{
|
||||
"id": channelID,
|
||||
}
|
||||
|
||||
return channelRaw, nil
|
||||
}
|
||||
|
||||
// SendMessage sends a message to Slack
|
||||
func (s *SlackPlatform) SendMessage(msg *model.Message) error {
|
||||
if s.config.BotOAuthAccessToken == "" {
|
||||
return errors.New("bot token not configured")
|
||||
}
|
||||
|
||||
// Prepare payload
|
||||
payload := map[string]interface{}{
|
||||
"channel": msg.Chat,
|
||||
"text": msg.Text,
|
||||
}
|
||||
|
||||
// Add thread_ts if it's a reply
|
||||
if msg.ReplyTo != "" {
|
||||
payload["thread_ts"] = msg.ReplyTo
|
||||
}
|
||||
|
||||
// Convert payload to JSON
|
||||
data, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Send HTTP request
|
||||
req, err := http.NewRequest("POST", "https://slack.com/api/chat.postMessage", strings.NewReader(string(data)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.config.BotOAuthAccessToken))
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check response
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("slack API error: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper function to parse int64
|
||||
func parseInt64(s string) (int64, error) {
|
||||
var n int64
|
||||
_, err := fmt.Sscanf(s, "%d", &n)
|
||||
return n, err
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue