Fix cookie refresh and reconnection bugs
This commit is contained in:
@@ -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
17
main.go
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user