175 lines
4.5 KiB
Go
175 lines
4.5 KiB
Go
package connectors
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/mudler/LocalAgent/core/agent"
|
|
"github.com/mudler/LocalAgent/pkg/xlog"
|
|
"github.com/mudler/LocalAgent/services/actions"
|
|
irc "github.com/thoj/go-ircevent"
|
|
)
|
|
|
|
type IRC struct {
|
|
server string
|
|
port string
|
|
nickname string
|
|
channel string
|
|
conn *irc.Connection
|
|
alwaysReply bool
|
|
}
|
|
|
|
func NewIRC(config map[string]string) *IRC {
|
|
return &IRC{
|
|
server: config["server"],
|
|
port: config["port"],
|
|
nickname: config["nickname"],
|
|
channel: config["channel"],
|
|
alwaysReply: config["alwaysReply"] == "true",
|
|
}
|
|
}
|
|
|
|
func (i *IRC) AgentResultCallback() func(state agent.ActionState) {
|
|
return func(state agent.ActionState) {
|
|
// Send the result to the bot
|
|
}
|
|
}
|
|
|
|
func (i *IRC) AgentReasoningCallback() func(state agent.ActionCurrentState) bool {
|
|
return func(state agent.ActionCurrentState) bool {
|
|
// Send the reasoning to the bot
|
|
return true
|
|
}
|
|
}
|
|
|
|
// cleanUpUsernameFromMessage removes the bot's nickname from the message
|
|
func cleanUpMessage(message string, nickname string) string {
|
|
cleaned := strings.ReplaceAll(message, nickname+":", "")
|
|
cleaned = strings.ReplaceAll(cleaned, nickname+",", "")
|
|
cleaned = strings.TrimSpace(cleaned)
|
|
return cleaned
|
|
}
|
|
|
|
// isMentioned checks if the bot is mentioned in the message
|
|
func isMentioned(message string, nickname string) bool {
|
|
return strings.Contains(message, nickname+":") ||
|
|
strings.Contains(message, nickname+",") ||
|
|
strings.HasPrefix(message, nickname)
|
|
}
|
|
|
|
// Start connects to the IRC server and starts listening for messages
|
|
func (i *IRC) Start(a *agent.Agent) {
|
|
i.conn = irc.IRC(i.nickname, i.nickname)
|
|
if i.conn == nil {
|
|
xlog.Error("Failed to create IRC client")
|
|
return
|
|
}
|
|
i.conn.UseTLS = false
|
|
i.conn.AddCallback("001", func(e *irc.Event) {
|
|
xlog.Info("Connected to IRC server", "server", i.server)
|
|
i.conn.Join(i.channel)
|
|
xlog.Info("Joined channel", "channel", i.channel)
|
|
})
|
|
|
|
i.conn.AddCallback("JOIN", func(e *irc.Event) {
|
|
if e.Nick == i.nickname {
|
|
xlog.Info("Bot joined channel", "channel", e.Arguments[0])
|
|
time.Sleep(1 * time.Second) // Small delay to ensure join is complete
|
|
i.conn.Privmsg(e.Arguments[0], "Hello! I've just (re)started and am ready to assist.")
|
|
}
|
|
})
|
|
|
|
i.conn.AddCallback("PRIVMSG", func(e *irc.Event) {
|
|
message := e.Message()
|
|
sender := e.Nick
|
|
channel := e.Arguments[0]
|
|
isDirect := false
|
|
|
|
if channel == i.nickname {
|
|
channel = sender
|
|
isDirect = true
|
|
}
|
|
|
|
// Skip messages from ourselves
|
|
if sender == i.nickname {
|
|
return
|
|
}
|
|
|
|
if !(i.alwaysReply || isMentioned(message, i.nickname) || isDirect) {
|
|
return
|
|
}
|
|
|
|
xlog.Info("Recv message", "message", message, "sender", sender, "channel", channel)
|
|
cleanedMessage := "I am " + sender + ". " + cleanUpMessage(message, i.nickname)
|
|
|
|
go func() {
|
|
res := a.Ask(
|
|
agent.WithText(cleanedMessage),
|
|
)
|
|
|
|
xlog.Info("Sending message", "message", res.Response, "channel", channel)
|
|
|
|
// Split the response into multiple messages if it's too long
|
|
// IRC typically has a message length limit
|
|
maxLength := 400 // Safe limit for most IRC servers
|
|
response := res.Response
|
|
|
|
// Handle multiline responses
|
|
lines := strings.Split(response, "\n")
|
|
for _, line := range lines {
|
|
if line == "" {
|
|
continue
|
|
}
|
|
|
|
// Split long lines
|
|
for len(line) > 0 {
|
|
var chunk string
|
|
if len(line) > maxLength {
|
|
chunk = line[:maxLength]
|
|
line = line[maxLength:]
|
|
} else {
|
|
chunk = line
|
|
line = ""
|
|
}
|
|
|
|
// Send the message to the channel
|
|
i.conn.Privmsg(channel, chunk)
|
|
|
|
// Small delay to prevent flooding
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
// Handle any attachments or special content from actions
|
|
for _, state := range res.State {
|
|
// Handle URLs from search action
|
|
if urls, exists := state.Metadata[actions.MetadataUrls]; exists {
|
|
for _, url := range urls.([]string) {
|
|
i.conn.Privmsg(channel, fmt.Sprintf("URL: %s", url))
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
// Handle image URLs
|
|
if imagesUrls, exists := state.Metadata[actions.MetadataImages]; exists {
|
|
for _, url := range imagesUrls.([]string) {
|
|
i.conn.Privmsg(channel, fmt.Sprintf("Image: %s", url))
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
})
|
|
|
|
// Connect to the server
|
|
err := i.conn.Connect(i.server + ":" + i.port)
|
|
if err != nil {
|
|
xlog.Error("Failed to connect to IRC server", "error", err)
|
|
return
|
|
}
|
|
|
|
// Start the IRC client in a goroutine
|
|
go i.conn.Loop()
|
|
}
|