working v1
This commit is contained in:
218
bridge/kosmi/kosmi.go
Normal file
218
bridge/kosmi/kosmi.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package bkosmi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultWebSocketURL = "wss://engine.kosmi.io/gql-ws"
|
||||
)
|
||||
|
||||
// KosmiClient interface for different client implementations
|
||||
type KosmiClient interface {
|
||||
Connect() error
|
||||
Disconnect() error
|
||||
SendMessage(text string) error
|
||||
OnMessage(callback func(*NewMessagePayload))
|
||||
IsConnected() bool
|
||||
}
|
||||
|
||||
// Bkosmi represents the Kosmi bridge
|
||||
type Bkosmi struct {
|
||||
*bridge.Config
|
||||
client KosmiClient
|
||||
roomID string
|
||||
roomURL string
|
||||
connected bool
|
||||
msgChannel chan config.Message
|
||||
}
|
||||
|
||||
// New creates a new Kosmi bridge instance
|
||||
func New(cfg *bridge.Config) bridge.Bridger {
|
||||
b := &Bkosmi{
|
||||
Config: cfg,
|
||||
msgChannel: make(chan config.Message, 100),
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// Connect establishes connection to the Kosmi room
|
||||
func (b *Bkosmi) Connect() error {
|
||||
b.Log.Info("Connecting to Kosmi")
|
||||
|
||||
// Get room URL from config
|
||||
b.roomURL = b.GetString("RoomURL")
|
||||
if b.roomURL == "" {
|
||||
return fmt.Errorf("RoomURL is required in configuration")
|
||||
}
|
||||
|
||||
// Extract room ID from URL
|
||||
roomID, err := extractRoomID(b.roomURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract room ID from URL %s: %w", b.roomURL, err)
|
||||
}
|
||||
b.roomID = roomID
|
||||
b.Log.Infof("Extracted room ID: %s", b.roomID)
|
||||
|
||||
// Create Native client (Playwright establishes WebSocket, we control it directly)
|
||||
b.client = NewNativeClient(b.roomURL, b.roomID, b.Log)
|
||||
|
||||
// Register message handler
|
||||
b.client.OnMessage(b.handleIncomingMessage)
|
||||
|
||||
// Connect to Kosmi
|
||||
if err := b.client.Connect(); err != nil {
|
||||
return fmt.Errorf("failed to connect to Kosmi: %w", err)
|
||||
}
|
||||
|
||||
b.connected = true
|
||||
b.Log.Info("Successfully connected to Kosmi")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect closes the connection to Kosmi
|
||||
func (b *Bkosmi) Disconnect() error {
|
||||
b.Log.Info("Disconnecting from Kosmi")
|
||||
|
||||
if b.client != nil {
|
||||
if err := b.client.Disconnect(); err != nil {
|
||||
b.Log.Errorf("Error closing Kosmi client: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
close(b.msgChannel)
|
||||
b.connected = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// JoinChannel joins a Kosmi room (no-op as we connect to a specific room)
|
||||
func (b *Bkosmi) JoinChannel(channel config.ChannelInfo) error {
|
||||
// Kosmi doesn't have a concept of joining channels after connection
|
||||
// The room is specified in the configuration and joined on Connect()
|
||||
b.Log.Infof("Channel %s is already connected via room URL", channel.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send sends a message to Kosmi
|
||||
func (b *Bkosmi) Send(msg config.Message) (string, error) {
|
||||
b.Log.Debugf("=> Sending message to Kosmi: %#v", msg)
|
||||
|
||||
// Ignore delete messages
|
||||
if msg.Event == config.EventMsgDelete {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Check if we're connected
|
||||
if !b.connected || b.client == nil {
|
||||
b.Log.Error("Not connected to Kosmi, dropping message")
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// The gateway already formatted the username with RemoteNickFormat
|
||||
// So msg.Username contains the formatted string like "[irc] <cottongin>"
|
||||
// Just send: username + text
|
||||
formattedMsg := fmt.Sprintf("%s%s", msg.Username, msg.Text)
|
||||
|
||||
// Send message to Kosmi
|
||||
if err := b.client.SendMessage(formattedMsg); err != nil {
|
||||
b.Log.Errorf("Failed to send message to Kosmi: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// handleIncomingMessage processes messages received from Kosmi
|
||||
func (b *Bkosmi) handleIncomingMessage(payload *NewMessagePayload) {
|
||||
// Extract message details
|
||||
body := payload.Data.NewMessage.Body
|
||||
username := payload.Data.NewMessage.User.DisplayName
|
||||
if username == "" {
|
||||
username = payload.Data.NewMessage.User.Username
|
||||
}
|
||||
if username == "" {
|
||||
username = "Unknown"
|
||||
}
|
||||
|
||||
timestamp := time.Unix(payload.Data.NewMessage.Time, 0)
|
||||
|
||||
b.Log.Infof("Received message from Kosmi: [%s] %s: %s", timestamp.Format(time.RFC3339), username, body)
|
||||
|
||||
// Check if this is our own message (to avoid echo)
|
||||
// Messages we send have [irc] prefix (from RemoteNickFormat)
|
||||
if strings.HasPrefix(body, "[irc]") {
|
||||
b.Log.Debug("Ignoring our own echoed message (has [irc] prefix)")
|
||||
return
|
||||
}
|
||||
|
||||
// Create Matterbridge message
|
||||
// Use "main" as the channel name for gateway matching
|
||||
// Don't add prefix here - let the gateway's RemoteNickFormat handle it
|
||||
rmsg := config.Message{
|
||||
Username: username,
|
||||
Text: body,
|
||||
Channel: "main",
|
||||
Account: b.Account,
|
||||
UserID: username,
|
||||
Timestamp: timestamp,
|
||||
Protocol: "kosmi",
|
||||
}
|
||||
|
||||
// Send to Matterbridge
|
||||
b.Log.Debugf("Forwarding to Matterbridge channel=%s account=%s: %s", rmsg.Channel, rmsg.Account, rmsg.Text)
|
||||
|
||||
if b.Remote == nil {
|
||||
b.Log.Error("Remote channel is nil! Cannot forward message")
|
||||
return
|
||||
}
|
||||
|
||||
b.Remote <- rmsg
|
||||
}
|
||||
|
||||
// extractRoomID extracts the room ID from a Kosmi room URL
|
||||
// Supports formats:
|
||||
// - https://app.kosmi.io/room/@roomname
|
||||
// - https://app.kosmi.io/room/roomid
|
||||
func extractRoomID(url string) (string, error) {
|
||||
// Remove trailing slash if present
|
||||
url = strings.TrimSuffix(url, "/")
|
||||
|
||||
// Pattern to match Kosmi room URLs
|
||||
patterns := []string{
|
||||
`https?://app\.kosmi\.io/room/(@?[a-zA-Z0-9_-]+)`,
|
||||
`app\.kosmi\.io/room/(@?[a-zA-Z0-9_-]+)`,
|
||||
`/room/(@?[a-zA-Z0-9_-]+)`,
|
||||
}
|
||||
|
||||
for _, pattern := range patterns {
|
||||
re := regexp.MustCompile(pattern)
|
||||
matches := re.FindStringSubmatch(url)
|
||||
if len(matches) >= 2 {
|
||||
roomID := matches[1]
|
||||
// Remove @ prefix if present (Kosmi uses both formats)
|
||||
return strings.TrimPrefix(roomID, "@"), nil
|
||||
}
|
||||
}
|
||||
|
||||
// If no pattern matches, assume the entire string is the room ID
|
||||
// This allows for simple room ID configuration
|
||||
parts := strings.Split(url, "/")
|
||||
if len(parts) > 0 {
|
||||
lastPart := parts[len(parts)-1]
|
||||
if lastPart != "" {
|
||||
return strings.TrimPrefix(lastPart, "@"), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("could not extract room ID from URL: %s", url)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user