Fix stale cmd/ scripts, export LoginWithChromedp, clean up vet warnings

Update 5 cmd/ test utilities that referenced APIs that drifted after
bridge refactors (NewBrowserAuthManager, bridge.NewConfig, changed
function signatures). Rewrite test-kosmi and test-native to use
NewGraphQLWSClient directly. Export LoginWithChromedp for use by cmd
scripts. Fix redundant-newline vet warnings across 5 cmd/ files.
Remove unreachable code and replace deprecated ioutil calls in
bridge/helper/lottie_convert.go.

Made-with: Cursor
This commit is contained in:
cottongin
2026-04-05 05:49:26 -04:00
parent 4fc7f08b24
commit d314193540
12 changed files with 213 additions and 74 deletions

View File

@@ -3,7 +3,6 @@
package helper package helper
import ( import (
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
@@ -23,7 +22,7 @@ func CanConvertTgsToX() error {
// This relies on an external command, which is ugly, but works. // This relies on an external command, which is ugly, but works.
func ConvertTgsToX(data *[]byte, outputFormat string, logger *logrus.Entry) error { func ConvertTgsToX(data *[]byte, outputFormat string, logger *logrus.Entry) error {
// lottie can't handle input from a pipe, so write to a temporary file: // lottie can't handle input from a pipe, so write to a temporary file:
tmpInFile, err := ioutil.TempFile(os.TempDir(), "matterbridge-lottie-input-*.tgs") tmpInFile, err := os.CreateTemp(os.TempDir(), "matterbridge-lottie-input-*.tgs")
if err != nil { if err != nil {
return err return err
} }
@@ -35,7 +34,7 @@ func ConvertTgsToX(data *[]byte, outputFormat string, logger *logrus.Entry) erro
}() }()
// lottie can handle writing to a pipe, but there is no way to do that platform-independently. // lottie can handle writing to a pipe, but there is no way to do that platform-independently.
// "/dev/stdout" won't work on Windows, and "-" upsets Cairo for some reason. So we need another file: // "/dev/stdout" won't work on Windows, and "-" upsets Cairo for some reason. So we need another file:
tmpOutFile, err := ioutil.TempFile(os.TempDir(), "matterbridge-lottie-output-*.data") tmpOutFile, err := os.CreateTemp(os.TempDir(), "matterbridge-lottie-output-*.data")
if err != nil { if err != nil {
return err return err
} }
@@ -64,7 +63,7 @@ func ConvertTgsToX(data *[]byte, outputFormat string, logger *logrus.Entry) erro
// 'stderr' already contains some parts of Stderr, because it was set to 'nil'. // 'stderr' already contains some parts of Stderr, because it was set to 'nil'.
return stderr return stderr
} }
dataContents, err := ioutil.ReadFile(tmpOutFileName) dataContents, err := os.ReadFile(tmpOutFileName)
if err != nil { if err != nil {
return err return err
} }
@@ -82,7 +81,6 @@ func SupportsFormat(format string) bool {
default: default:
return false return false
} }
return false
} }
func LottieBackend() string { func LottieBackend() string {

View File

@@ -9,9 +9,9 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
// loginWithChromedp uses browser automation to log in and extract the JWT token. // LoginWithChromedp uses browser automation to log in and extract the JWT token.
// This is the proven implementation that successfully authenticates users. // This is the proven implementation that successfully authenticates users.
func loginWithChromedp(email, password string, log *logrus.Entry) (string, error) { func LoginWithChromedp(email, password string, log *logrus.Entry) (string, error) {
log.Info("Starting browser automation for authentication...") log.Info("Starting browser automation for authentication...")
// Create context with timeout // Create context with timeout

View File

@@ -82,7 +82,7 @@ func (b *Bkosmi) Connect() error {
} else { } else {
// No valid cache, authenticate with browser // No valid cache, authenticate with browser
b.Log.Info("Authenticating with email/password...") b.Log.Info("Authenticating with email/password...")
token, err = loginWithChromedp(email, password, b.Log) token, err = LoginWithChromedp(email, password, b.Log)
if err != nil { if err != nil {
return fmt.Errorf("authentication failed: %w", err) return fmt.Errorf("authentication failed: %w", err)
} }

View File

@@ -1,11 +1,14 @@
package main package main
import ( import (
"bytes"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http"
"os" "os"
"strings" "strings"
"time"
bkosmi "github.com/42wim/matterbridge/bridge/kosmi" bkosmi "github.com/42wim/matterbridge/bridge/kosmi"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@@ -36,10 +39,9 @@ func main() {
fmt.Println(strings.Repeat("=", 80)) fmt.Println(strings.Repeat("=", 80))
fmt.Println() fmt.Println()
// Get anonymous token // Get anonymous token via HTTP (same mutation the client uses internally)
fmt.Println("📝 Step 1: Getting anonymous token...") fmt.Println("📝 Step 1: Getting anonymous token...")
client := bkosmi.NewGraphQLWSClient("https://app.kosmi.io/room/@test", "@test", entry) anonToken, err := getAnonymousToken()
anonToken, err := client.GetAnonymousTokenForTest()
if err != nil { if err != nil {
fmt.Printf("❌ Failed to get anonymous token: %v\n", err) fmt.Printf("❌ Failed to get anonymous token: %v\n", err)
os.Exit(1) os.Exit(1)
@@ -47,10 +49,9 @@ func main() {
fmt.Printf("✅ Anonymous token obtained (length: %d)\n", len(anonToken)) fmt.Printf("✅ Anonymous token obtained (length: %d)\n", len(anonToken))
fmt.Println() fmt.Println()
// Get authenticated token // Get authenticated token via browser automation
fmt.Println("📝 Step 2: Getting authenticated token via browser...") fmt.Println("📝 Step 2: Getting authenticated token via browser...")
browserAuth := bkosmi.NewBrowserAuthManager(email, password, entry) authToken, err := bkosmi.LoginWithChromedp(email, password, entry)
authToken, err := browserAuth.GetToken()
if err != nil { if err != nil {
fmt.Printf("❌ Failed to get authenticated token: %v\n", err) fmt.Printf("❌ Failed to get authenticated token: %v\n", err)
os.Exit(1) os.Exit(1)
@@ -134,6 +135,49 @@ func printClaims(claims map[string]interface{}) {
} }
} }
func getAnonymousToken() (string, error) {
mutation := map[string]interface{}{
"query": `mutation { anonLogin { token } }`,
}
jsonBody, err := json.Marshal(mutation)
if err != nil {
return "", err
}
client := &http.Client{Timeout: 10 * time.Second}
req, err := http.NewRequest("POST", "https://engine.kosmi.io/", bytes.NewReader(jsonBody))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Referer", "https://app.kosmi.io/")
req.ContentLength = int64(len(jsonBody))
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return "", fmt.Errorf("HTTP %d", resp.StatusCode)
}
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", err
}
if data, ok := result["data"].(map[string]interface{}); ok {
if anonLogin, ok := data["anonLogin"].(map[string]interface{}); ok {
if token, ok := anonLogin["token"].(string); ok {
return token, nil
}
}
}
return "", fmt.Errorf("no token in response")
}
func compareClaims(anon, auth map[string]interface{}) { func compareClaims(anon, auth map[string]interface{}) {
allKeys := make(map[string]bool) allKeys := make(map[string]bool)
for k := range anon { for k := range anon {

View File

@@ -36,7 +36,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
fmt.Println("=== WebSocket Messages ===\n") fmt.Println("\n=== WebSocket Messages ===")
for _, entry := range har.Log.Entries { for _, entry := range har.Log.Entries {
for i, msg := range entry.WebSocketMessages { for i, msg := range entry.WebSocketMessages {

View File

@@ -68,7 +68,7 @@ func main() {
} }
} }
fmt.Println("=== WebSocket Operations (in order) ===\n") fmt.Println("\n=== WebSocket Operations (in order) ===")
msgCount := 0 msgCount := 0
for _, entry := range har.Log.Entries { for _, entry := range har.Log.Entries {

View File

@@ -1,8 +1,11 @@
package main package main
import ( import (
"encoding/base64"
"encoding/json"
"fmt" "fmt"
"os" "os"
"strings"
bkosmi "github.com/42wim/matterbridge/bridge/kosmi" bkosmi "github.com/42wim/matterbridge/bridge/kosmi"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@@ -23,7 +26,6 @@ func main() {
email := os.Args[1] email := os.Args[1]
password := os.Args[2] password := os.Args[2]
// Set up logging
log := logrus.New() log := logrus.New()
log.SetLevel(logrus.DebugLevel) log.SetLevel(logrus.DebugLevel)
entry := logrus.NewEntry(log) entry := logrus.NewEntry(log)
@@ -31,11 +33,7 @@ func main() {
fmt.Println("🚀 Testing browser-based authentication...") fmt.Println("🚀 Testing browser-based authentication...")
fmt.Println() fmt.Println()
// Create browser auth manager token, err := bkosmi.LoginWithChromedp(email, password, entry)
browserAuth := bkosmi.NewBrowserAuthManager(email, password, entry)
// Get token
token, err := browserAuth.GetToken()
if err != nil { if err != nil {
fmt.Printf("❌ Authentication failed: %v\n", err) fmt.Printf("❌ Authentication failed: %v\n", err)
os.Exit(1) os.Exit(1)
@@ -48,27 +46,32 @@ func main() {
fmt.Printf("Token length: %d characters\n", len(token)) fmt.Printf("Token length: %d characters\n", len(token))
fmt.Println() fmt.Println()
// Check if authenticated userID := extractUserIDFromJWT(token)
if browserAuth.IsAuthenticated() {
fmt.Println("✅ Token is valid")
} else {
fmt.Println("❌ Token is invalid or expired")
}
// Get user ID
userID := browserAuth.GetUserID()
if userID != "" { if userID != "" {
fmt.Printf("User ID: %s\n", userID) fmt.Printf("User ID: %s\n", userID)
} else {
fmt.Println("(Could not extract user ID from token)")
} }
fmt.Println() fmt.Println()
fmt.Println("🎉 Test completed successfully!") fmt.Println("🎉 Test completed successfully!")
} }
func min(a, b int) int { func extractUserIDFromJWT(token string) string {
if a < b { parts := strings.Split(token, ".")
return a if len(parts) != 3 {
return ""
} }
return b payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return ""
}
var claims map[string]interface{}
if err := json.Unmarshal(payload, &claims); err != nil {
return ""
}
if sub, ok := claims["sub"].(string); ok {
return sub
}
return ""
} }

View File

@@ -9,11 +9,11 @@ import (
) )
func main() { func main() {
fmt.Println("=== Kosmi Image Upload Test ===\n") fmt.Println("=== Kosmi Image Upload Test ===")
// Test 1: Generate a room code image // Test 1: Generate a room code image
fmt.Println("1. Generating room code image for 'TEST'...") fmt.Println("1. Generating room code image for 'TEST'...")
imageData, err := jackbox.GenerateRoomCodeImage("TEST") imageData, err := jackbox.GenerateRoomCodeImage("TEST", "Test Game")
if err != nil { if err != nil {
log.Fatalf("Failed to generate image: %v", err) log.Fatalf("Failed to generate image: %v", err)
} }

90
cmd/test-kosmi/main.go Normal file
View File

@@ -0,0 +1,90 @@
package main
import (
"flag"
"fmt"
"os"
"os/signal"
"regexp"
"strings"
"syscall"
"time"
bkosmi "github.com/42wim/matterbridge/bridge/kosmi"
"github.com/sirupsen/logrus"
)
func main() {
roomURL := flag.String("room", "https://app.kosmi.io/room/@hyperspaceout", "Kosmi room URL")
debug := flag.Bool("debug", false, "Enable debug logging")
flag.Parse()
log := logrus.New()
if *debug {
log.SetLevel(logrus.DebugLevel)
} else {
log.SetLevel(logrus.InfoLevel)
}
log.SetFormatter(&logrus.TextFormatter{FullTimestamp: true})
logger := log.WithField("bridge", "kosmi-test")
logger.Info("Starting Kosmi bridge test")
logger.Infof("Room URL: %s", *roomURL)
roomID, err := extractRoomID(*roomURL)
if err != nil {
logger.Fatalf("Failed to extract room ID: %v", err)
}
// Empty token = anonymous access
client := bkosmi.NewGraphQLWSClient(*roomURL, roomID, "", logger)
client.OnMessage(func(payload *bkosmi.NewMessagePayload) {
username := payload.Data.NewMessage.User.DisplayName
if username == "" {
username = payload.Data.NewMessage.User.Username
}
ts := time.Unix(payload.Data.NewMessage.Time, 0)
logger.Infof("Received message: [%s] %s: %s",
ts.Format("15:04:05"), username, payload.Data.NewMessage.Body)
})
logger.Info("Connecting to Kosmi...")
if err := client.Connect(); err != nil {
logger.Fatalf("Failed to connect to Kosmi: %v", err)
}
logger.Info("Successfully connected to Kosmi!")
logger.Info("Listening for messages... Press Ctrl+C to exit")
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
time.Sleep(5 * time.Second)
logger.Info("Bridge is running. Messages from Kosmi will appear above.")
}()
<-sigChan
logger.Info("Shutting down...")
if err := client.Disconnect(); err != nil {
logger.Errorf("Error disconnecting: %v", err)
}
logger.Info("Goodbye!")
}
func extractRoomID(url string) (string, error) {
url = strings.TrimSuffix(url, "/")
re := regexp.MustCompile(`/room/(@?[a-zA-Z0-9_-]+)`)
matches := re.FindStringSubmatch(url)
if len(matches) >= 2 {
roomID := matches[1]
if !strings.HasPrefix(roomID, "@") {
roomID = "@" + roomID
}
return roomID, nil
}
return "", fmt.Errorf("could not extract room ID from URL: %s", url)
}

View File

@@ -2,84 +2,88 @@ package main
import ( import (
"flag" "flag"
"fmt"
"os" "os"
"os/signal" "os/signal"
"regexp"
"strings"
"syscall" "syscall"
"time" "time"
"github.com/42wim/matterbridge/bridge"
bkosmi "github.com/42wim/matterbridge/bridge/kosmi" bkosmi "github.com/42wim/matterbridge/bridge/kosmi"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
func main() { func main() {
// Parse command line flags
roomURL := flag.String("room", "https://app.kosmi.io/room/@hyperspaceout", "Kosmi room URL") roomURL := flag.String("room", "https://app.kosmi.io/room/@hyperspaceout", "Kosmi room URL")
debug := flag.Bool("debug", false, "Enable debug logging") debug := flag.Bool("debug", false, "Enable debug logging")
flag.Parse() flag.Parse()
// Set up logger
log := logrus.New() log := logrus.New()
if *debug { if *debug {
log.SetLevel(logrus.DebugLevel) log.SetLevel(logrus.DebugLevel)
} else { } else {
log.SetLevel(logrus.InfoLevel) log.SetLevel(logrus.InfoLevel)
} }
log.SetFormatter(&logrus.TextFormatter{ log.SetFormatter(&logrus.TextFormatter{FullTimestamp: true})
FullTimestamp: true,
})
logger := log.WithField("bridge", "kosmi-test") logger := log.WithField("bridge", "kosmi-native-test")
logger.Info("Starting Kosmi bridge test") logger.Info("Starting Kosmi native WebSocket test")
logger.Infof("Room URL: %s", *roomURL) logger.Infof("Room URL: %s", *roomURL)
// Create bridge configuration roomID, err := extractRoomID(*roomURL)
cfg := bridge.NewConfig("kosmi.test", logger) if err != nil {
cfg.SetString("RoomURL", *roomURL) logger.Fatalf("Failed to extract room ID: %v", err)
cfg.SetBool("Debug", *debug)
// Create Kosmi bridge
b := bkosmi.New(cfg)
// Connect to Kosmi
logger.Info("Connecting to Kosmi...")
if err := b.Connect(); err != nil {
logger.Fatalf("Failed to connect to Kosmi: %v", err)
} }
logger.Info("Successfully connected to Kosmi!") client := bkosmi.NewGraphQLWSClient(*roomURL, roomID, "", logger)
// Start message listener client.OnMessage(func(payload *bkosmi.NewMessagePayload) {
go func() { username := payload.Data.NewMessage.User.DisplayName
for msg := range cfg.Remote { if username == "" {
username = payload.Data.NewMessage.User.Username
}
ts := time.Unix(payload.Data.NewMessage.Time, 0)
logger.Infof("Received message: [%s] %s: %s", logger.Infof("Received message: [%s] %s: %s",
msg.Timestamp.Format("15:04:05"), ts.Format("15:04:05"), username, payload.Data.NewMessage.Body)
msg.Username, })
msg.Text)
} logger.Info("Connecting to Kosmi via native WebSocket...")
}() if err := client.Connect(); err != nil {
logger.Fatalf("Failed to connect: %v", err)
}
logger.Info("Successfully connected!")
// Wait for interrupt signal
logger.Info("Listening for messages... Press Ctrl+C to exit") logger.Info("Listening for messages... Press Ctrl+C to exit")
sigChan := make(chan os.Signal, 1) sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
// Optional: Send a test message after 5 seconds
go func() { go func() {
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
logger.Info("Bridge is running. Messages from Kosmi will appear above.") logger.Info("Bridge is running. Messages from Kosmi will appear above.")
logger.Info("To test sending messages, integrate with IRC or use the full Matterbridge setup")
}() }()
<-sigChan <-sigChan
logger.Info("Shutting down...") logger.Info("Shutting down...")
// Disconnect if err := client.Disconnect(); err != nil {
if err := b.Disconnect(); err != nil {
logger.Errorf("Error disconnecting: %v", err) logger.Errorf("Error disconnecting: %v", err)
} }
logger.Info("Goodbye!") logger.Info("Goodbye!")
} }
func extractRoomID(url string) (string, error) {
url = strings.TrimSuffix(url, "/")
re := regexp.MustCompile(`/room/(@?[a-zA-Z0-9_-]+)`)
matches := re.FindStringSubmatch(url)
if len(matches) >= 2 {
roomID := matches[1]
if !strings.HasPrefix(roomID, "@") {
roomID = "@" + roomID
}
return roomID, nil
}
return "", fmt.Errorf("could not extract room ID from URL: %s", url)
}

View File

@@ -68,7 +68,7 @@ func main() {
fmt.Println("✅ WebSocket connected!") fmt.Println("✅ WebSocket connected!")
// Step 3: Listen for messages // Step 3: Listen for messages
fmt.Println("\n👂 Listening for messages (press Ctrl+C to exit)...\n") fmt.Println("\n👂 Listening for messages (press Ctrl+C to exit)...")
messageCount := 0 messageCount := 0
for { for {

View File

@@ -61,7 +61,7 @@ func main() {
fmt.Println("✅ Subscribed!") fmt.Println("✅ Subscribed!")
// Listen for messages // Listen for messages
fmt.Println("\n👂 Listening for messages (press Ctrl+C to exit)...\n") fmt.Println("\n👂 Listening for messages (press Ctrl+C to exit)...")
messageCount := 0 messageCount := 0
for { for {