Fix cookie refresh and reconnection bugs
This commit is contained in:
@@ -70,6 +70,8 @@ func (s *CookieRefreshService) Start() {
|
|||||||
s.wg.Add(1)
|
s.wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer s.wg.Done()
|
defer s.wg.Done()
|
||||||
|
|
||||||
|
// Initial fetch
|
||||||
log.Println("⏳ Fetching initial cookie...")
|
log.Println("⏳ Fetching initial cookie...")
|
||||||
c, err := s.FetchFreshCookie()
|
c, err := s.FetchFreshCookie()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -81,6 +83,30 @@ func (s *CookieRefreshService) Start() {
|
|||||||
s.currentCookie = c
|
s.currentCookie = c
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
s.readyOnce.Do(func() { close(s.readyCh) })
|
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()
|
defer s.mu.RUnlock()
|
||||||
return s.currentCookie
|
return s.currentCookie
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CookieRefreshService) FetchFreshCookie() (string, error) {
|
func (s *CookieRefreshService) FetchFreshCookie() (string, error) {
|
||||||
if s.debug {
|
if s.debug {
|
||||||
log.Println("💡 Stage: Starting FetchFreshCookie")
|
log.Println("💡 Stage: Starting FetchFreshCookie")
|
||||||
|
|||||||
17
main.go
17
main.go
@@ -5,7 +5,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
|
|
||||||
"local/sneedchatbridge/config"
|
"local/sneedchatbridge/config"
|
||||||
"local/sneedchatbridge/cookie"
|
"local/sneedchatbridge/cookie"
|
||||||
@@ -29,7 +28,7 @@ func main() {
|
|||||||
log.Printf("Using Sneedchat room ID: %d", cfg.SneedchatRoomID)
|
log.Printf("Using Sneedchat room ID: %d", cfg.SneedchatRoomID)
|
||||||
log.Printf("Bridge username: %s", cfg.BridgeUsername)
|
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")
|
cookieSvc, err := cookie.NewCookieRefreshService(cfg.BridgeUsername, cfg.BridgePassword, "kiwifarms.st")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to create cookie service: %v", err)
|
log.Fatalf("Failed to create cookie service: %v", err)
|
||||||
@@ -53,20 +52,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
log.Println("🌉 Discord-Sneedchat Bridge started successfully")
|
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
|
// Connect to Sneedchat
|
||||||
go func() {
|
go func() {
|
||||||
if err := sneedClient.Connect(); err != nil {
|
if err := sneedClient.Connect(); err != nil {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ProcessedCacheSize = 250
|
ProcessedCacheSize = 1000 // Increased from 250
|
||||||
ReconnectInterval = 7 * time.Second
|
ReconnectInterval = 7 * time.Second
|
||||||
MappingCacheSize = 1000
|
MappingCacheSize = 1000
|
||||||
MappingCleanupInterval = 5 * time.Minute
|
MappingCleanupInterval = 5 * time.Minute
|
||||||
@@ -218,25 +218,45 @@ func (c *Client) handleDisconnect() {
|
|||||||
c.OnDisconnect()
|
c.OnDisconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(ReconnectInterval)
|
// Reconnection loop with exponential backoff
|
||||||
|
delay := ReconnectInterval
|
||||||
|
maxDelay := 2 * time.Minute
|
||||||
|
attempt := 0
|
||||||
|
|
||||||
|
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 {
|
if err := c.Connect(); err != nil {
|
||||||
log.Printf("Reconnect attempt failed: %v", err)
|
log.Printf("⚠️ Reconnect attempt #%d failed: %v", attempt, err)
|
||||||
return
|
|
||||||
|
// Exponential backoff
|
||||||
|
delay *= 2
|
||||||
|
if delay > maxDelay {
|
||||||
|
delay = maxDelay
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("🟢 Reconnected successfully")
|
log.Println("🟢 Reconnected successfully")
|
||||||
|
|
||||||
// 🚦 Allow websocket handshake to stabilize
|
// Allow websocket to stabilize
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
// 🔁 Re-join chat room and verify connection
|
// Re-join room
|
||||||
c.joinRoom()
|
c.joinRoom()
|
||||||
c.Send("/ping")
|
c.Send("/ping")
|
||||||
log.Printf("🔁 Rejoined Sneedchat room %d after reconnect", c.roomID)
|
log.Printf("📍 Rejoined Sneedchat room %d after reconnect", c.roomID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (c *Client) Disconnect() {
|
func (c *Client) Disconnect() {
|
||||||
close(c.stopCh)
|
close(c.stopCh)
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
@@ -378,9 +398,18 @@ func (c *Client) isProcessed(id int) bool {
|
|||||||
func (c *Client) addToProcessed(id int) {
|
func (c *Client) addToProcessed(id int) {
|
||||||
c.processedMu.Lock()
|
c.processedMu.Lock()
|
||||||
defer c.processedMu.Unlock()
|
defer c.processedMu.Unlock()
|
||||||
|
|
||||||
c.processedMessageIDs = append(c.processedMessageIDs, id)
|
c.processedMessageIDs = append(c.processedMessageIDs, id)
|
||||||
|
|
||||||
|
// Hard cap: keep only the most recent 1000 messages (FIFO)
|
||||||
if len(c.processedMessageIDs) > ProcessedCacheSize {
|
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 == "" {
|
if bridgeUsername == "" || pingID == "" {
|
||||||
return content
|
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))
|
return pat.ReplaceAllString(content, fmt.Sprintf("<@%s>", pingID))
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,27 @@ import (
|
|||||||
"strings"
|
"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 {
|
func BBCodeToMarkdown(text string) string {
|
||||||
if text == "" {
|
if text == "" {
|
||||||
return ""
|
return ""
|
||||||
@@ -12,10 +33,9 @@ func BBCodeToMarkdown(text string) string {
|
|||||||
text = strings.ReplaceAll(text, "\r\n", "\n")
|
text = strings.ReplaceAll(text, "\r\n", "\n")
|
||||||
text = strings.ReplaceAll(text, "\r", "\n")
|
text = strings.ReplaceAll(text, "\r", "\n")
|
||||||
|
|
||||||
text = regexp.MustCompile(`(?i)\[img\](.*?)\[/img\]`).ReplaceAllString(text, "$1")
|
text = imgPattern.ReplaceAllString(text, "$1")
|
||||||
text = regexp.MustCompile(`(?i)\[video\](.*?)\[/video\]`).ReplaceAllString(text, "$1")
|
text = videoPattern.ReplaceAllString(text, "$1")
|
||||||
|
|
||||||
urlPattern := regexp.MustCompile(`(?i)\[url=(.*?)\](.*?)\[/url\]`)
|
|
||||||
text = urlPattern.ReplaceAllStringFunc(text, func(match string) string {
|
text = urlPattern.ReplaceAllStringFunc(text, func(match string) string {
|
||||||
parts := urlPattern.FindStringSubmatch(match)
|
parts := urlPattern.FindStringSubmatch(match)
|
||||||
if len(parts) < 3 {
|
if len(parts) < 3 {
|
||||||
@@ -23,21 +43,20 @@ func BBCodeToMarkdown(text string) string {
|
|||||||
}
|
}
|
||||||
link := strings.TrimSpace(parts[1])
|
link := strings.TrimSpace(parts[1])
|
||||||
txt := strings.TrimSpace(parts[2])
|
txt := strings.TrimSpace(parts[2])
|
||||||
if regexp.MustCompile(`(?i)^https?://`).MatchString(txt) {
|
if httpPattern.MatchString(txt) {
|
||||||
return txt
|
return txt
|
||||||
}
|
}
|
||||||
return "[" + txt + "](" + link + ")"
|
return "[" + txt + "](" + link + ")"
|
||||||
})
|
})
|
||||||
|
|
||||||
text = regexp.MustCompile(`(?i)\[url\](.*?)\[/url\]`).ReplaceAllString(text, "$1")
|
text = urlSimplePattern.ReplaceAllString(text, "$1")
|
||||||
text = regexp.MustCompile(`(?i)\[(?:b|strong)\](.*?)\[/\s*(?:b|strong)\]`).ReplaceAllString(text, "**$1**")
|
text = boldPattern.ReplaceAllString(text, "**$1**")
|
||||||
text = regexp.MustCompile(`(?i)\[(?:i|em)\](.*?)\[/\s*(?:i|em)\]`).ReplaceAllString(text, "*$1*")
|
text = italicPattern.ReplaceAllString(text, "*$1*")
|
||||||
text = regexp.MustCompile(`(?i)\[u\](.*?)\[/\s*u\]`).ReplaceAllString(text, "__$1__")
|
text = underlinePattern.ReplaceAllString(text, "__$1__")
|
||||||
text = regexp.MustCompile(`(?i)\[(?:s|strike)\](.*?)\[/\s*(?:s|strike)\]`).ReplaceAllString(text, "~~$1~~")
|
text = strikePattern.ReplaceAllString(text, "~~$1~~")
|
||||||
text = regexp.MustCompile(`(?i)\[code\](.*?)\[/code\]`).ReplaceAllString(text, "`$1`")
|
text = codePattern.ReplaceAllString(text, "`$1`")
|
||||||
text = regexp.MustCompile(`(?i)\[(?:php|plain|code=\w+)\](.*?)\[/(?:php|plain|code)\]`).ReplaceAllString(text, "```$1```")
|
text = codeBlockPattern.ReplaceAllString(text, "```$1```")
|
||||||
|
|
||||||
quotePattern := regexp.MustCompile(`(?i)\[quote\](.*?)\[/quote\]`)
|
|
||||||
text = quotePattern.ReplaceAllStringFunc(text, func(match string) string {
|
text = quotePattern.ReplaceAllStringFunc(text, func(match string) string {
|
||||||
parts := quotePattern.FindStringSubmatch(match)
|
parts := quotePattern.FindStringSubmatch(match)
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
@@ -51,11 +70,11 @@ func BBCodeToMarkdown(text string) string {
|
|||||||
return strings.Join(lines, "\n")
|
return strings.Join(lines, "\n")
|
||||||
})
|
})
|
||||||
|
|
||||||
text = regexp.MustCompile(`(?i)\[spoiler\](.*?)\[/spoiler\]`).ReplaceAllString(text, "||$1||")
|
text = spoilerPattern.ReplaceAllString(text, "||$1||")
|
||||||
text = regexp.MustCompile(`(?i)\[(?:color|size)=.*?\](.*?)\[/\s*(?:color|size)\]`).ReplaceAllString(text, "$1")
|
text = colorSizePattern.ReplaceAllString(text, "$1")
|
||||||
text = regexp.MustCompile(`(?m)^\[\*\]\s*`).ReplaceAllString(text, "• ")
|
text = listItemPattern.ReplaceAllString(text, "• ")
|
||||||
text = regexp.MustCompile(`(?i)\[/?list\]`).ReplaceAllString(text, "")
|
text = listTagPattern.ReplaceAllString(text, "")
|
||||||
text = regexp.MustCompile(`\[/?[A-Za-z0-9\-=_]+\]`).ReplaceAllString(text, "")
|
text = genericTagPattern.ReplaceAllString(text, "")
|
||||||
|
|
||||||
return strings.TrimSpace(text)
|
return strings.TrimSpace(text)
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user