179 lines
5.2 KiB
Go
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"
|
|
}
|