Fix cookie refresh and reconnection bugs

This commit is contained in:
Salastil
2025-11-03 22:28:47 -05:00
parent 0d92a3aeb0
commit 04f3ef2500
4 changed files with 115 additions and 55 deletions

View File

@@ -70,6 +70,8 @@ func (s *CookieRefreshService) Start() {
s.wg.Add(1)
go func() {
defer s.wg.Done()
// Initial fetch
log.Println("⏳ Fetching initial cookie...")
c, err := s.FetchFreshCookie()
if err != nil {
@@ -81,6 +83,30 @@ func (s *CookieRefreshService) Start() {
s.currentCookie = c
s.mu.Unlock()
s.readyOnce.Do(func() { close(s.readyCh) })
log.Println("✅ Initial cookie obtained")
// Continuous refresh loop
ticker := time.NewTicker(CookieRefreshInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
log.Println("🔄 Auto-refreshing cookie...")
newCookie, err := s.FetchFreshCookie()
if err != nil {
log.Printf("⚠️ Cookie auto-refresh failed: %v", err)
continue
}
s.mu.Lock()
s.currentCookie = newCookie
s.mu.Unlock()
log.Println("✅ Cookie auto-refresh successful")
case <-s.stopCh:
log.Println("Cookie refresh service stopping")
return
}
}
}()
}
@@ -98,6 +124,7 @@ func (s *CookieRefreshService) GetCurrentCookie() string {
defer s.mu.RUnlock()
return s.currentCookie
}
func (s *CookieRefreshService) FetchFreshCookie() (string, error) {
if s.debug {
log.Println("💡 Stage: Starting FetchFreshCookie")

17
main.go
View File

@@ -5,7 +5,6 @@ import (
"os"
"os/signal"
"syscall"
"time"
"local/sneedchatbridge/config"
"local/sneedchatbridge/cookie"
@@ -29,7 +28,7 @@ func main() {
log.Printf("Using Sneedchat room ID: %d", cfg.SneedchatRoomID)
log.Printf("Bridge username: %s", cfg.BridgeUsername)
// Cookie service
// Cookie service (now handles its own refresh loop)
cookieSvc, err := cookie.NewCookieRefreshService(cfg.BridgeUsername, cfg.BridgePassword, "kiwifarms.st")
if err != nil {
log.Fatalf("Failed to create cookie service: %v", err)
@@ -53,20 +52,6 @@ func main() {
}
log.Println("🌉 Discord-Sneedchat Bridge started successfully")
// Background 4h refresh loop
go func() {
t := time.NewTicker(4 * time.Hour)
defer t.Stop()
for range t.C {
log.Println("🔄 Starting automatic cookie refresh")
if _, err := cookieSvc.FetchFreshCookie(); err != nil {
log.Printf("⚠️ Cookie refresh failed: %v", err)
continue
}
log.Println("✅ Cookie refresh completed")
}
}()
// Connect to Sneedchat
go func() {
if err := sneedClient.Connect(); err != nil {

View File

@@ -16,7 +16,7 @@ import (
)
const (
ProcessedCacheSize = 250
ProcessedCacheSize = 1000 // Increased from 250
ReconnectInterval = 7 * time.Second
MappingCacheSize = 1000
MappingCleanupInterval = 5 * time.Minute
@@ -218,25 +218,45 @@ func (c *Client) handleDisconnect() {
c.OnDisconnect()
}
time.Sleep(ReconnectInterval)
// Reconnection loop with exponential backoff
delay := ReconnectInterval
maxDelay := 2 * time.Minute
attempt := 0
if err := c.Connect(); err != nil {
log.Printf("Reconnect attempt failed: %v", err)
return
for {
select {
case <-c.stopCh:
log.Println("Reconnection cancelled - bridge stopping")
return
case <-time.After(delay):
attempt++
log.Printf("🔄 Reconnection attempt #%d...", attempt)
if err := c.Connect(); err != nil {
log.Printf("⚠️ Reconnect attempt #%d failed: %v", attempt, err)
// Exponential backoff
delay *= 2
if delay > maxDelay {
delay = maxDelay
}
continue
}
log.Println("🟢 Reconnected successfully")
// Allow websocket to stabilize
time.Sleep(2 * time.Second)
// Re-join room
c.joinRoom()
c.Send("/ping")
log.Printf("📍 Rejoined Sneedchat room %d after reconnect", c.roomID)
return
}
}
log.Println("🟢 Reconnected successfully")
// 🚦 Allow websocket handshake to stabilize
time.Sleep(2 * time.Second)
// 🔁 Re-join chat room and verify connection
c.joinRoom()
c.Send("/ping")
log.Printf("🔁 Rejoined Sneedchat room %d after reconnect", c.roomID)
}
func (c *Client) Disconnect() {
close(c.stopCh)
c.mu.Lock()
@@ -378,9 +398,18 @@ func (c *Client) isProcessed(id int) bool {
func (c *Client) addToProcessed(id int) {
c.processedMu.Lock()
defer c.processedMu.Unlock()
c.processedMessageIDs = append(c.processedMessageIDs, id)
// Hard cap: keep only the most recent 1000 messages (FIFO)
if len(c.processedMessageIDs) > ProcessedCacheSize {
c.processedMessageIDs = c.processedMessageIDs[1:]
excess := len(c.processedMessageIDs) - ProcessedCacheSize
c.processedMessageIDs = c.processedMessageIDs[excess:]
// Log when significant eviction happens
if excess > 50 {
log.Printf("⚠️ Processed message cache full, evicted %d old entries", excess)
}
}
}
@@ -407,6 +436,6 @@ func ReplaceBridgeMention(content, bridgeUsername, pingID string) string {
if bridgeUsername == "" || pingID == "" {
return content
}
pat := regexp.MustCompile(fmt.Sprintf(`(?i)@%s(?:\\W|$)`, regexp.QuoteMeta(bridgeUsername)))
pat := regexp.MustCompile(fmt.Sprintf(`(?i)@%s(?:\W|$)`, regexp.QuoteMeta(bridgeUsername)))
return pat.ReplaceAllString(content, fmt.Sprintf("<@%s>", pingID))
}

View File

@@ -5,6 +5,27 @@ import (
"strings"
)
// Pre-compiled regex patterns (Go 1.19 compatible)
var (
imgPattern = regexp.MustCompile(`(?i)\[img\](.*?)\[/img\]`)
videoPattern = regexp.MustCompile(`(?i)\[video\](.*?)\[/video\]`)
urlPattern = regexp.MustCompile(`(?i)\[url=(.*?)\](.*?)\[/url\]`)
urlSimplePattern = regexp.MustCompile(`(?i)\[url\](.*?)\[/url\]`)
boldPattern = regexp.MustCompile(`(?i)\[(?:b|strong)\](.*?)\[/\s*(?:b|strong)\]`)
italicPattern = regexp.MustCompile(`(?i)\[(?:i|em)\](.*?)\[/\s*(?:i|em)\]`)
underlinePattern = regexp.MustCompile(`(?i)\[u\](.*?)\[/\s*u\]`)
strikePattern = regexp.MustCompile(`(?i)\[(?:s|strike)\](.*?)\[/\s*(?:s|strike)\]`)
codePattern = regexp.MustCompile(`(?i)\[code\](.*?)\[/code\]`)
codeBlockPattern = regexp.MustCompile(`(?i)\[(?:php|plain|code=\w+)\](.*?)\[/(?:php|plain|code)\]`)
quotePattern = regexp.MustCompile(`(?i)\[quote\](.*?)\[/quote\]`)
spoilerPattern = regexp.MustCompile(`(?i)\[spoiler\](.*?)\[/spoiler\]`)
colorSizePattern = regexp.MustCompile(`(?i)\[(?:color|size)=.*?\](.*?)\[/\s*(?:color|size)\]`)
listItemPattern = regexp.MustCompile(`(?m)^\[\*\]\s*`)
listTagPattern = regexp.MustCompile(`(?i)\[/?list\]`)
genericTagPattern = regexp.MustCompile(`\[/?[A-Za-z0-9\-=_]+\]`)
httpPattern = regexp.MustCompile(`(?i)^https?://`)
)
func BBCodeToMarkdown(text string) string {
if text == "" {
return ""
@@ -12,10 +33,9 @@ func BBCodeToMarkdown(text string) string {
text = strings.ReplaceAll(text, "\r\n", "\n")
text = strings.ReplaceAll(text, "\r", "\n")
text = regexp.MustCompile(`(?i)\[img\](.*?)\[/img\]`).ReplaceAllString(text, "$1")
text = regexp.MustCompile(`(?i)\[video\](.*?)\[/video\]`).ReplaceAllString(text, "$1")
text = imgPattern.ReplaceAllString(text, "$1")
text = videoPattern.ReplaceAllString(text, "$1")
urlPattern := regexp.MustCompile(`(?i)\[url=(.*?)\](.*?)\[/url\]`)
text = urlPattern.ReplaceAllStringFunc(text, func(match string) string {
parts := urlPattern.FindStringSubmatch(match)
if len(parts) < 3 {
@@ -23,21 +43,20 @@ func BBCodeToMarkdown(text string) string {
}
link := strings.TrimSpace(parts[1])
txt := strings.TrimSpace(parts[2])
if regexp.MustCompile(`(?i)^https?://`).MatchString(txt) {
if httpPattern.MatchString(txt) {
return txt
}
return "[" + txt + "](" + link + ")"
})
text = regexp.MustCompile(`(?i)\[url\](.*?)\[/url\]`).ReplaceAllString(text, "$1")
text = regexp.MustCompile(`(?i)\[(?:b|strong)\](.*?)\[/\s*(?:b|strong)\]`).ReplaceAllString(text, "**$1**")
text = regexp.MustCompile(`(?i)\[(?:i|em)\](.*?)\[/\s*(?:i|em)\]`).ReplaceAllString(text, "*$1*")
text = regexp.MustCompile(`(?i)\[u\](.*?)\[/\s*u\]`).ReplaceAllString(text, "__$1__")
text = regexp.MustCompile(`(?i)\[(?:s|strike)\](.*?)\[/\s*(?:s|strike)\]`).ReplaceAllString(text, "~~$1~~")
text = regexp.MustCompile(`(?i)\[code\](.*?)\[/code\]`).ReplaceAllString(text, "`$1`")
text = regexp.MustCompile(`(?i)\[(?:php|plain|code=\w+)\](.*?)\[/(?:php|plain|code)\]`).ReplaceAllString(text, "```$1```")
text = urlSimplePattern.ReplaceAllString(text, "$1")
text = boldPattern.ReplaceAllString(text, "**$1**")
text = italicPattern.ReplaceAllString(text, "*$1*")
text = underlinePattern.ReplaceAllString(text, "__$1__")
text = strikePattern.ReplaceAllString(text, "~~$1~~")
text = codePattern.ReplaceAllString(text, "`$1`")
text = codeBlockPattern.ReplaceAllString(text, "```$1```")
quotePattern := regexp.MustCompile(`(?i)\[quote\](.*?)\[/quote\]`)
text = quotePattern.ReplaceAllStringFunc(text, func(match string) string {
parts := quotePattern.FindStringSubmatch(match)
if len(parts) < 2 {
@@ -51,11 +70,11 @@ func BBCodeToMarkdown(text string) string {
return strings.Join(lines, "\n")
})
text = regexp.MustCompile(`(?i)\[spoiler\](.*?)\[/spoiler\]`).ReplaceAllString(text, "||$1||")
text = regexp.MustCompile(`(?i)\[(?:color|size)=.*?\](.*?)\[/\s*(?:color|size)\]`).ReplaceAllString(text, "$1")
text = regexp.MustCompile(`(?m)^\[\*\]\s*`).ReplaceAllString(text, "• ")
text = regexp.MustCompile(`(?i)\[/?list\]`).ReplaceAllString(text, "")
text = regexp.MustCompile(`\[/?[A-Za-z0-9\-=_]+\]`).ReplaceAllString(text, "")
text = spoilerPattern.ReplaceAllString(text, "||$1||")
text = colorSizePattern.ReplaceAllString(text, "$1")
text = listItemPattern.ReplaceAllString(text, "• ")
text = listTagPattern.ReplaceAllString(text, "")
text = genericTagPattern.ReplaceAllString(text, "")
return strings.TrimSpace(text)
}