From 4b455eb58ee4bd5eb69c62fd639f3d18699538a0 Mon Sep 17 00:00:00 2001 From: Salastil Date: Sat, 28 Feb 2026 18:37:52 -0500 Subject: [PATCH] 4hr cookie refresh + message_uuid changes --- README.md | 11 ++-------- cookie/fetcher.go | 39 ++++++++++++++++++++++++++++++++++ main.go | 6 ++++++ sneed/client.go | 53 +++++++++++++++++++++++++++-------------------- sneed/types.go | 1 + 5 files changed, 79 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 4cdd192..72d5472 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ A high-performance bridge written in Go that synchronizes messages between Kiwi ## Requirements -- **Go 1.23 or higher** +- **Go 1.25.6 or higher** - **Discord Bot Token** with proper permissions - **Discord Webhook URL** - **Kiwi Farms account** with Sneedchat access @@ -35,7 +35,7 @@ sudo apt update sudo apt install golang git # Verify installation -go version # Should show 1.23 or higher +go version # Should show 1.25.6 or higher ``` ### 2. Clone and Build @@ -271,10 +271,3 @@ If messages aren't appearing: ## License This bridge is provided as-is. Use responsibly and in accordance with Kiwi Farms and Discord Terms of Service. - -## Credits - -Built with: -- [discordgo](https://github.com/bwmarrin/discordgo) - Discord API -- [gorilla/websocket](https://github.com/gorilla/websocket) - WebSocket client -- [godotenv](https://github.com/joho/godotenv) - Environment loading \ No newline at end of file diff --git a/cookie/fetcher.go b/cookie/fetcher.go index 9f359b2..5ac1c74 100644 --- a/cookie/fetcher.go +++ b/cookie/fetcher.go @@ -13,6 +13,7 @@ import ( "slices" "strings" "sync" + "time" ) // SessionService manages XenForo session cookies as plain strings. @@ -25,6 +26,7 @@ type SessionService struct { username string password string tr *http.Transport // shared transport, TLS config applied once + stopCh chan struct{} } // NewSessionService creates a service, performs initial login, and returns. @@ -40,6 +42,7 @@ func NewSessionService(ctx context.Context, host, username, password string) (*S username: username, password: password, tr: tr, + stopCh: make(chan struct{}), } log.Println("⏳ Logging in to Kiwi Farms...") @@ -47,9 +50,45 @@ func NewSessionService(ctx context.Context, host, username, password string) (*S return nil, fmt.Errorf("initial login: %w", err) } log.Println("✅ Login successful") + + go s.refreshLoop(ctx) + return s, nil } +// Close stops the background refresh loop. Call at shutdown after +// sneedClient.Disconnect(). +func (s *SessionService) Close() { + close(s.stopCh) +} + +// refreshLoop proactively renews all session cookies every 4 hours. +// Prevents xf_session and xf_user from expiring mid-run so reconnect +// attempts always have valid credentials. ttrs_clearance is cleared +// so the next WebSocket dial solves it fresh. +func (s *SessionService) refreshLoop(ctx context.Context) { + ticker := time.NewTicker(4 * time.Hour) + defer ticker.Stop() + for { + select { + case <-ticker.C: + log.Println("🔄 Proactive session refresh (4h timer)...") + s.mu.Lock() + s.deleteCookie("ttrs_clearance") + if err := s.login(ctx); err != nil { + log.Printf("⚠️ Proactive session refresh failed: %v", err) + } else { + log.Println("✅ Proactive session refresh complete") + } + s.mu.Unlock() + case <-s.stopCh: + return + case <-ctx.Done(): + return + } + } +} + // tlsConfig mirrors sockchat's socketTLSConfig exactly: // concatenate secure + insecure cipher suites so KiwiFlare TLS fingerprinting // doesn't trigger. "The insecure ones appear to be necessary for consistently diff --git a/main.go b/main.go index cd2e4c8..7726f76 100644 --- a/main.go +++ b/main.go @@ -15,16 +15,21 @@ import ( func main() { envFile := ".env" + debugFlag := false for i, a := range os.Args { if a == "--env" && i+1 < len(os.Args) { envFile = os.Args[i+1] } + if a == "--debug" { + debugFlag = true + } } cfg, err := config.Load(envFile) if err != nil { log.Fatalf("Failed to load config: %v", err) } + cfg.Debug = cfg.Debug || debugFlag log.Printf("Using .env file: %s", envFile) log.Printf("Using Sneedchat room ID: %d", cfg.SneedchatRoomID) log.Printf("Bridge username: %s", cfg.BridgeUsername) @@ -63,5 +68,6 @@ func main() { log.Println("Shutdown signal received, cleaning up...") bridge.Stop() sneedClient.Disconnect() + session.Close() log.Println("Bridge stopped successfully") } diff --git a/sneed/client.go b/sneed/client.go index 4edd7bb..abf28a3 100644 --- a/sneed/client.go +++ b/sneed/client.go @@ -53,9 +53,9 @@ type Client struct { stopCh chan struct{} wg sync.WaitGroup - processedMu sync.Mutex - processedMessageIDs []int - messageEditDates *utils.BoundedMap + processedMu sync.Mutex + processedUUIDs []string + messageEditDates *utils.BoundedMap OnMessage func(map[string]interface{}) OnEdit func(int, string) @@ -86,7 +86,7 @@ func NewClient(roomID int, session *cookie.SessionService, debug bool) *Client { TLSClientConfig: tr.TLSClientConfig, }, stopCh: make(chan struct{}), - processedMessageIDs: make([]int, 0, ProcessedCacheSize), + processedUUIDs: make([]string, 0, ProcessedCacheSize), messageEditDates: utils.NewBoundedMap(MappingCacheSize, MappingMaxAge), lastMessage: time.Now(), } @@ -381,7 +381,6 @@ func (c *Client) handleIncoming(raw string) { } for _, id := range ids { c.messageEditDates.Delete(id) - c.removeFromProcessed(id) if c.OnDelete != nil { c.OnDelete(id) } @@ -415,11 +414,12 @@ func (c *Client) processMessage(m SneedMessage) { } messageText = html.UnescapeString(messageText) + uuid := m.MessageUUID editDate := m.MessageEditDate deleted := m.Deleted || m.IsDeleted if deleted { c.messageEditDates.Delete(m.MessageID) - c.removeFromProcessed(m.MessageID) + c.removeFromProcessed(uuid) if c.OnDelete != nil { c.OnDelete(m.MessageID) } @@ -428,7 +428,7 @@ func (c *Client) processMessage(m SneedMessage) { if (c.bridgeUserID > 0 && userID == c.bridgeUserID) || (c.bridgeUsername != "" && username == c.bridgeUsername) { - if m.MessageID > 0 && c.recentOutboundIter != nil && c.mapDiscordSneed != nil { + if uuid != "" && c.recentOutboundIter != nil && c.mapDiscordSneed != nil { now := time.Now() for _, entry := range c.recentOutboundIter() { if mapped, ok := entry["mapped"].(bool); ok && mapped { @@ -446,12 +446,12 @@ func (c *Client) processMessage(m SneedMessage) { } } } - c.addToProcessed(m.MessageID) + c.addToProcessed(uuid) c.messageEditDates.Set(m.MessageID, editDate) return } - if c.isProcessed(m.MessageID) { + if c.isProcessed(uuid) { if prev, exists := c.messageEditDates.Get(m.MessageID); exists { if editDate > prev.(int) { c.messageEditDates.Set(m.MessageID, editDate) @@ -463,7 +463,7 @@ func (c *Client) processMessage(m SneedMessage) { return } - c.addToProcessed(m.MessageID) + c.addToProcessed(uuid) c.messageEditDates.Set(m.MessageID, editDate) if c.OnMessage != nil { @@ -477,33 +477,42 @@ func (c *Client) processMessage(m SneedMessage) { } } -func (c *Client) isProcessed(id int) bool { +func (c *Client) isProcessed(uuid string) bool { + if uuid == "" { + return false + } c.processedMu.Lock() defer c.processedMu.Unlock() - for _, x := range c.processedMessageIDs { - if x == id { + for _, x := range c.processedUUIDs { + if x == uuid { return true } } return false } -func (c *Client) addToProcessed(id int) { +func (c *Client) addToProcessed(uuid string) { + if uuid == "" { + return + } c.processedMu.Lock() defer c.processedMu.Unlock() - c.processedMessageIDs = append(c.processedMessageIDs, id) - if len(c.processedMessageIDs) > ProcessedCacheSize { - excess := len(c.processedMessageIDs) - ProcessedCacheSize - c.processedMessageIDs = c.processedMessageIDs[excess:] + c.processedUUIDs = append(c.processedUUIDs, uuid) + if len(c.processedUUIDs) > ProcessedCacheSize { + excess := len(c.processedUUIDs) - ProcessedCacheSize + c.processedUUIDs = c.processedUUIDs[excess:] } } -func (c *Client) removeFromProcessed(id int) { +func (c *Client) removeFromProcessed(uuid string) { + if uuid == "" { + return + } c.processedMu.Lock() defer c.processedMu.Unlock() - for i, x := range c.processedMessageIDs { - if x == id { - c.processedMessageIDs = append(c.processedMessageIDs[:i], c.processedMessageIDs[i+1:]...) + for i, x := range c.processedUUIDs { + if x == uuid { + c.processedUUIDs = append(c.processedUUIDs[:i], c.processedUUIDs[i+1:]...) return } } diff --git a/sneed/types.go b/sneed/types.go index 1c8747c..b19f287 100644 --- a/sneed/types.go +++ b/sneed/types.go @@ -2,6 +2,7 @@ package sneed type SneedMessage struct { MessageID int `json:"message_id"` + MessageUUID string `json:"message_uuid"` Message string `json:"message"` MessageRaw string `json:"message_raw"` MessageEditDate int `json:"message_edit_date"`