refactor: python -> go
All checks were successful
ci/woodpecker/tag/release Pipeline was successful

This commit is contained in:
Felipe M 2025-04-20 13:54:22 +02:00
parent 9c78ea2d48
commit 7c684af8c3
Signed by: fmartingr
GPG key ID: CCFBC5637D4000A8
79 changed files with 3594 additions and 3257 deletions

View 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
}