Files
2025-11-18 02:07:08 -05:00

179 lines
5.2 KiB
Go

package main
import (
"bytes"
"crypto/rand"
"encoding/hex"
"flag"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"local/sneedchatbridge/config"
"local/sneedchatbridge/cookie"
"local/sneedchatbridge/matrix"
"local/sneedchatbridge/sneed"
)
func main() {
// ------------------------------------------------------------
// COMMAND-LINE FLAG HANDLING
// ------------------------------------------------------------
genReg := flag.Bool("generate-registration", false, "Generate registration.yaml and exit")
outPath := flag.String("out", "registration.yaml", "Output path for generated registration.yaml")
flag.Parse()
// ------------------------------------------------------------
// LOAD CONFIG
// ------------------------------------------------------------
cfg, err := config.Load(".env")
if err != nil {
log.Fatalf("❌ Failed to load configuration: %v", err)
}
// If --generate-registration was passed, output registration.yaml and exit.
if *genReg {
err := generateRegistrationFile(cfg, *outPath)
if err != nil {
log.Fatalf("❌ Failed to generate registration file: %v", err)
}
log.Printf("🟢 Registration file written to %s", *outPath)
return
}
// ------------------------------------------------------------
// NORMAL BRIDGE STARTUP (APPSERVICE MODE ONLY)
// ------------------------------------------------------------
fmt.Println("===============================================")
fmt.Println(" Matrix Appservice ↔ Sneedchat Bridge")
fmt.Println("===============================================")
log.Printf("Using Sneedchat room ID: %d", cfg.SneedchatRoomID)
log.Printf("Matrix Appservice listen address: %s", cfg.AppserviceListenAddr)
log.Printf("Ghost MXID domain: %s (prefix=%q)", cfg.MatrixGhostUserDomain, cfg.MatrixGhostUserPrefix)
// ------------------------------------------------------------
// COOKIE REFRESH SERVICE
// ------------------------------------------------------------
ck, err := cookie.NewCookieRefreshServiceWithDebug(
cfg.BridgeUsername,
cfg.BridgePassword,
kiwiDomain(),
cfg.Debug,
)
if err != nil {
log.Fatalf("❌ Cannot create cookie refresh service: %v", err)
}
ck.Start()
ck.WaitForCookie()
log.Println("🟢 Initial XenForo session cookie acquired.")
// ------------------------------------------------------------
// SNEEDCHAT CLIENT INIT
// ------------------------------------------------------------
sneedClient := sneed.NewClient(cfg.SneedchatRoomID, ck)
// ------------------------------------------------------------
// MATRIX BRIDGE INIT (APPSERVICE)
// ------------------------------------------------------------
bridge := matrix.NewBridge(cfg, sneedClient)
// ------------------------------------------------------------
// START COMPONENTS
// ------------------------------------------------------------
// Start Sneedchat WebSocket client
if err := sneedClient.Connect(); err != nil {
log.Fatalf("❌ Sneedchat initial connect failed: %v", err)
}
// Start Matrix Appservice HTTP server
go func() {
if err := bridge.StartAppserviceServer(cfg); err != nil {
log.Fatalf("❌ Appservice HTTP server error: %v", err)
}
}()
// ------------------------------------------------------------
// WAIT FOR SHUTDOWN SIGNAL
// ------------------------------------------------------------
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
s := <-sigCh
log.Printf("🔻 Shutdown signal received: %v", s)
// ------------------------------------------------------------
// SHUT DOWN GRACEFULLY
// ------------------------------------------------------------
// If Bridge implements Stop() we call it. (Optional)
if stopper, ok := interface{}(bridge).(interface{ Stop() }); ok {
stopper.Stop()
}
sneedClient.Disconnect()
ck.Stop()
log.Println("🟡 Bridge stopped.")
}
// ------------------------------------------------------------
// REGISTRATION GENERATOR
// ------------------------------------------------------------
// generateRegistrationFile writes the appservice registration.yaml
// based on values loaded from .env.
func generateRegistrationFile(cfg *config.Config, outPath string) error {
template := `id: sneedchat
url: "http://127.0.0.1:29333"
as_token: "%s"
hs_token: "%s"
sender_localpart: "sneedbridge"
rate_limited: false
namespaces:
users:
- regex: "^@sneedbridge:%s$"
exclusive: true
- regex: "^@.*:%s$"
exclusive: true
aliases: []
rooms: []
push_ephemeral: true
de.sorunome.msc2409.push_ephemeral: true
`
var buf bytes.Buffer
hsToken := randomHex(64) // Synapse expects strong token
buf.WriteString(fmt.Sprintf(
template,
cfg.MatrixAppserviceToken,
hsToken,
cfg.MatrixGhostUserDomain,
cfg.MatrixGhostUserDomain,
))
return os.WriteFile(outPath, buf.Bytes(), 0600)
}
// randomHex generates a cryptographically random hex token.
func randomHex(n int) string {
b := make([]byte, n/2)
_, err := rand.Read(b)
if err != nil {
panic(err)
}
return hex.EncodeToString(b)
}
// kiwiDomain returns the XenForo/Sneedchat domain name.
func kiwiDomain() string {
return "kiwifarms.st"
}