Files
goclaw/internal/channels/slack/utils.go
T
viettranx e85624ce96 refactor: split large Go files into smaller modules for maintainability
Split 11 Go files exceeding 500 lines into smaller, focused files:
- channels/manager.go → manager.go + dispatch.go + runs.go + events.go
- channels/slack/channel.go → channel.go + send.go + utils.go
- channels/slack/handlers.go → handlers.go + handlers_mention.go + handlers_files.go
- channels/telegram/handlers.go → handlers.go + handlers_utils.go
- channels/zalo/personal/channel.go → channel.go + send.go + listen.go + handlers.go
- gateway/methods/agents.go → agents.go + agents_create.go + agents_update.go + agents_delete.go
- http/summoner.go → summoner.go + summoner_regenerate.go + summoner_prompts.go + summoner_utils.go
- http/skills.go → skills.go + skills_upload.go + skills_versions.go + skills_grants.go
- http/mcp.go → mcp.go + mcp_tools.go + mcp_grants.go + mcp_requests.go
- store/pg/cron.go → cron.go + cron_crud.go + cron_update.go + cron_exec.go

No logic changes — pure file reorganization to keep files under 200 lines.
2026-03-09 10:41:54 +07:00

103 lines
2.8 KiB
Go

package slack
import (
"context"
"fmt"
"log/slog"
"regexp"
"strings"
"time"
"github.com/nextlevelbuilder/goclaw/internal/bus"
)
// HandleMessage overrides BaseChannel to allow messages when the chatID (Slack channel)
// is in the allowlist, enabling group-level allowlisting without requiring individual user IDs.
// This is Slack-specific: other channels only check senderID in BaseChannel.HandleMessage.
func (c *Channel) HandleMessage(senderID, chatID, content string, mediaPaths []string, metadata map[string]string, peerKind string) {
// Allow if either the sender or the Slack channel ID is in the allowlist.
if !c.IsAllowed(senderID) && !c.IsAllowed(chatID) {
return
}
userID := senderID
if idx := strings.IndexByte(senderID, '|'); idx > 0 {
userID = senderID[:idx]
}
var mediaFiles []bus.MediaFile
for _, p := range mediaPaths {
mediaFiles = append(mediaFiles, bus.MediaFile{Path: p})
}
c.Bus().PublishInbound(bus.InboundMessage{
Channel: c.Name(),
SenderID: senderID,
ChatID: chatID,
Content: content,
Media: mediaFiles,
PeerKind: peerKind,
UserID: userID,
Metadata: metadata,
AgentID: c.AgentID(),
})
}
// BlockReplyEnabled returns the per-channel block_reply override.
func (c *Channel) BlockReplyEnabled() *bool { return c.config.BlockReply }
// resolveDisplayName fetches and caches the Slack display name for a user ID.
func (c *Channel) resolveDisplayName(userID string) string {
c.userCacheMu.RLock()
cu, found := c.userCache[userID]
c.userCacheMu.RUnlock()
if found && time.Since(cu.fetchedAt) < userCacheTTL {
return cu.displayName
}
user, err := c.api.GetUserInfo(userID)
if err != nil {
slog.Debug("slack: failed to resolve user", "user_id", userID, "error", err)
return userID
}
name := user.Profile.DisplayName
if name == "" {
name = user.RealName
}
if name == "" {
name = user.Name
}
c.userCacheMu.Lock()
c.userCache[userID] = cachedUser{displayName: name, fetchedAt: time.Now()}
c.userCacheMu.Unlock()
return name
}
// nonRetryableAuthErrors matches Slack errors that indicate permanent auth failure.
var nonRetryableAuthErrors = regexp.MustCompile(
`(?i)(invalid_auth|token_revoked|account_inactive|not_authed|team_not_found|missing_scope)`,
)
func isNonRetryableAuthError(errMsg string) bool {
return nonRetryableAuthErrors.MatchString(errMsg)
}
// HealthProbe performs an auth.test call to verify the Slack connection is alive.
func (c *Channel) HealthProbe(ctx context.Context) (ok bool, elapsed time.Duration, err error) {
if c.api == nil {
return false, 0, fmt.Errorf("slack client not initialized (Start() not called)")
}
start := time.Now()
probeCtx, cancel := context.WithTimeout(ctx, healthProbeTimeout)
defer cancel()
_, err = c.api.AuthTestContext(probeCtx)
elapsed = time.Since(start)
return err == nil, elapsed, err
}