refactor(modernize): apply safe modernize fixes
This commit is contained in:
parent
2c8416e658
commit
222d1a3086
23 changed files with 62 additions and 88 deletions
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -249,11 +250,9 @@ func (cb *ContextBuilder) sourceFilesChangedLocked() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check tracked source files (bootstrap + memory).
|
// Check tracked source files (bootstrap + memory).
|
||||||
for _, p := range cb.sourcePaths() {
|
if slices.ContainsFunc(cb.sourcePaths(), cb.fileChangedSince) {
|
||||||
if cb.fileChangedSince(p) {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// --- Skills directory (handled separately from sourcePaths) ---
|
// --- Skills directory (handled separately from sourcePaths) ---
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -404,11 +404,11 @@ func TestConcurrentBuildSystemPromptWithCache(t *testing.T) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
errs := make(chan string, goroutines*iterations)
|
errs := make(chan string, goroutines*iterations)
|
||||||
|
|
||||||
for g := 0; g < goroutines; g++ {
|
for g := range goroutines {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(id int) {
|
go func(id int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
for i := 0; i < iterations; i++ {
|
for i := range iterations {
|
||||||
result := cb.BuildSystemPromptWithCache()
|
result := cb.BuildSystemPromptWithCache()
|
||||||
if result == "" {
|
if result == "" {
|
||||||
errs <- "empty prompt returned"
|
errs <- "empty prompt returned"
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -175,13 +176,7 @@ func TestToolRegistry_ToolRegistration(t *testing.T) {
|
||||||
toolsList := toolsInfo["names"].([]string)
|
toolsList := toolsInfo["names"].([]string)
|
||||||
|
|
||||||
// Check that our custom tool name is in the list
|
// Check that our custom tool name is in the list
|
||||||
found := false
|
found := slices.Contains(toolsList, "mock_custom")
|
||||||
for _, name := range toolsList {
|
|
||||||
if name == "mock_custom" {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
if !found {
|
||||||
t.Error("Expected custom tool to be registered")
|
t.Error("Expected custom tool to be registered")
|
||||||
}
|
}
|
||||||
|
|
@ -250,13 +245,7 @@ func TestToolRegistry_GetDefinitions(t *testing.T) {
|
||||||
toolsList := toolsInfo["names"].([]string)
|
toolsList := toolsInfo["names"].([]string)
|
||||||
|
|
||||||
// Check that our custom tool name is in the list
|
// Check that our custom tool name is in the list
|
||||||
found := false
|
found := slices.Contains(toolsList, "mock_custom")
|
||||||
for _, name := range toolsList {
|
|
||||||
if name == "mock_custom" {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
if !found {
|
||||||
t.Error("Expected custom tool to be registered")
|
t.Error("Expected custom tool to be registered")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ func (ms *MemoryStore) GetRecentDailyNotes(days int) string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
first := true
|
first := true
|
||||||
|
|
||||||
for i := 0; i < days; i++ {
|
for i := range days {
|
||||||
date := time.Now().AddDate(0, 0, -i)
|
date := time.Now().AddDate(0, 0, -i)
|
||||||
dateStr := date.Format("20060102") // YYYYMMDD
|
dateStr := date.Format("20060102") // YYYYMMDD
|
||||||
monthDir := dateStr[:6] // YYYYMM
|
monthDir := dateStr[:6] // YYYYMM
|
||||||
|
|
|
||||||
|
|
@ -318,10 +318,7 @@ func (c *OneBotChannel) sendAPIRequest(action string, params any, timeout time.D
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *OneBotChannel) reconnectLoop() {
|
func (c *OneBotChannel) reconnectLoop() {
|
||||||
interval := time.Duration(c.config.ReconnectInterval) * time.Second
|
interval := max(time.Duration(c.config.ReconnectInterval)*time.Second, 5*time.Second)
|
||||||
if interval < 5*time.Second {
|
|
||||||
interval = 5 * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
|
@ -975,8 +972,8 @@ func (c *OneBotChannel) checkGroupTrigger(
|
||||||
if prefix == "" {
|
if prefix == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(content, prefix) {
|
if after, ok := strings.CutPrefix(content, prefix); ok {
|
||||||
return true, strings.TrimSpace(strings.TrimPrefix(content, prefix))
|
return true, strings.TrimSpace(after)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -596,7 +596,7 @@ func pkcs7UnpadWeCom(data []byte) ([]byte, error) {
|
||||||
return nil, fmt.Errorf("padding size larger than data")
|
return nil, fmt.Errorf("padding size larger than data")
|
||||||
}
|
}
|
||||||
// Verify all padding bytes
|
// Verify all padding bytes
|
||||||
for i := 0; i < padding; i++ {
|
for i := range padding {
|
||||||
if data[len(data)-1-i] != byte(padding) {
|
if data[len(data)-1-i] != byte(padding) {
|
||||||
return nil, fmt.Errorf("invalid padding byte at position %d", i)
|
return nil, fmt.Errorf("invalid padding byte at position %d", i)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ func encryptTestMessageApp(message, aesKey string) (string, error) {
|
||||||
|
|
||||||
// Prepare message: random(16) + msg_len(4) + msg + corp_id
|
// Prepare message: random(16) + msg_len(4) + msg + corp_id
|
||||||
random := make([]byte, 0, 16)
|
random := make([]byte, 0, 16)
|
||||||
for i := 0; i < 16; i++ {
|
for i := range 16 {
|
||||||
random = append(random, byte(i+1))
|
random = append(random, byte(i+1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ func encryptTestMessage(message, aesKey string) (string, error) {
|
||||||
|
|
||||||
// Prepare message: random(16) + msg_len(4) + msg + receiveid
|
// Prepare message: random(16) + msg_len(4) + msg + receiveid
|
||||||
random := make([]byte, 0, 16)
|
random := make([]byte, 0, 16)
|
||||||
for i := 0; i < 16; i++ {
|
for i := range 16 {
|
||||||
random = append(random, byte(i))
|
random = append(random, byte(i))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ func TestGetModelConfig_RoundRobin(t *testing.T) {
|
||||||
|
|
||||||
// Test round-robin distribution
|
// Test round-robin distribution
|
||||||
results := make(map[string]int)
|
results := make(map[string]int)
|
||||||
for i := 0; i < 30; i++ {
|
for range 30 {
|
||||||
result, err := cfg.GetModelConfig("lb-model")
|
result, err := cfg.GetModelConfig("lb-model")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("GetModelConfig() error = %v", err)
|
t.Fatalf("GetModelConfig() error = %v", err)
|
||||||
|
|
@ -94,17 +94,15 @@ func TestGetModelConfig_Concurrent(t *testing.T) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
errors := make(chan error, goroutines*iterations)
|
errors := make(chan error, goroutines*iterations)
|
||||||
|
|
||||||
for i := 0; i < goroutines; i++ {
|
for range goroutines {
|
||||||
wg.Add(1)
|
wg.Go(func() {
|
||||||
go func() {
|
for range iterations {
|
||||||
defer wg.Done()
|
|
||||||
for j := 0; j < iterations; j++ {
|
|
||||||
_, err := cfg.GetModelConfig("concurrent-model")
|
_, err := cfg.GetModelConfig("concurrent-model")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors <- err
|
errors <- err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -122,9 +123,7 @@ func (s *Server) readyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
ready := s.ready
|
ready := s.ready
|
||||||
checks := make(map[string]Check)
|
checks := make(map[string]Check)
|
||||||
for k, v := range s.checks {
|
maps.Copy(checks, s.checks)
|
||||||
checks[k] = v
|
|
||||||
}
|
|
||||||
s.mu.RUnlock()
|
s.mu.RUnlock()
|
||||||
|
|
||||||
if !ready {
|
if !ready {
|
||||||
|
|
|
||||||
|
|
@ -212,14 +212,14 @@ func translateTools(tools []ToolDefinition) []anthropic.ToolUnionParam {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseResponse(resp *anthropic.Message) *LLMResponse {
|
func parseResponse(resp *anthropic.Message) *LLMResponse {
|
||||||
var content string
|
var content strings.Builder
|
||||||
var toolCalls []ToolCall
|
var toolCalls []ToolCall
|
||||||
|
|
||||||
for _, block := range resp.Content {
|
for _, block := range resp.Content {
|
||||||
switch block.Type {
|
switch block.Type {
|
||||||
case "text":
|
case "text":
|
||||||
tb := block.AsText()
|
tb := block.AsText()
|
||||||
content += tb.Text
|
content.WriteString(tb.Text)
|
||||||
case "tool_use":
|
case "tool_use":
|
||||||
tu := block.AsToolUse()
|
tu := block.AsToolUse()
|
||||||
var args map[string]any
|
var args map[string]any
|
||||||
|
|
@ -246,7 +246,7 @@ func parseResponse(resp *anthropic.Message) *LLMResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
return &LLMResponse{
|
return &LLMResponse{
|
||||||
Content: content,
|
Content: content.String(),
|
||||||
ToolCalls: toolCalls,
|
ToolCalls: toolCalls,
|
||||||
FinishReason: finishReason,
|
FinishReason: finishReason,
|
||||||
Usage: &UsageInfo{
|
Usage: &UsageInfo{
|
||||||
|
|
@ -264,8 +264,8 @@ func normalizeBaseURL(apiBase string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
base = strings.TrimRight(base, "/")
|
base = strings.TrimRight(base, "/")
|
||||||
if strings.HasSuffix(base, "/v1") {
|
if before, ok := strings.CutSuffix(base, "/v1"); ok {
|
||||||
base = strings.TrimSuffix(base, "/v1")
|
base = before
|
||||||
}
|
}
|
||||||
if base == "" {
|
if base == "" {
|
||||||
return defaultBaseURL
|
return defaultBaseURL
|
||||||
|
|
|
||||||
|
|
@ -163,8 +163,8 @@ func resolveCodexModel(model string) (string, string) {
|
||||||
return codexDefaultModel, "empty model"
|
return codexDefaultModel, "empty model"
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(m, "openai/") {
|
if after, ok := strings.CutPrefix(m, "openai/"); ok {
|
||||||
m = strings.TrimPrefix(m, "openai/")
|
m = after
|
||||||
} else if strings.Contains(m, "/") {
|
} else if strings.Contains(m, "/") {
|
||||||
return codexDefaultModel, "non-openai model namespace"
|
return codexDefaultModel, "non-openai model namespace"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@ func TestCooldown_FailureWindowReset(t *testing.T) {
|
||||||
ct, current := newTestTracker(now)
|
ct, current := newTestTracker(now)
|
||||||
|
|
||||||
// 4 errors → 1h cooldown
|
// 4 errors → 1h cooldown
|
||||||
for i := 0; i < 4; i++ {
|
for range 4 {
|
||||||
ct.MarkFailure("openai", FailoverRateLimit)
|
ct.MarkFailure("openai", FailoverRateLimit)
|
||||||
*current = current.Add(2 * time.Second) // small advance between errors
|
*current = current.Add(2 * time.Second) // small advance between errors
|
||||||
}
|
}
|
||||||
|
|
@ -230,7 +230,7 @@ func TestCooldown_ConcurrentAccess(t *testing.T) {
|
||||||
ct := NewCooldownTracker()
|
ct := NewCooldownTracker()
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
for i := 0; i < 100; i++ {
|
for range 100 {
|
||||||
wg.Add(3)
|
wg.Add(3)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
|
||||||
|
|
@ -307,8 +307,8 @@ func stripSystemParts(messages []Message) []openaiMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeModel(model, apiBase string) string {
|
func normalizeModel(model, apiBase string) string {
|
||||||
idx := strings.Index(model, "/")
|
before, after, ok := strings.Cut(model, "/")
|
||||||
if idx == -1 {
|
if !ok {
|
||||||
return model
|
return model
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -316,10 +316,10 @@ func normalizeModel(model, apiBase string) string {
|
||||||
return model
|
return model
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix := strings.ToLower(model[:idx])
|
prefix := strings.ToLower(before)
|
||||||
switch prefix {
|
switch prefix {
|
||||||
case "moonshot", "nvidia", "groq", "ollama", "deepseek", "google", "openrouter", "zhipu", "mistral":
|
case "moonshot", "nvidia", "groq", "ollama", "deepseek", "google", "openrouter", "zhipu", "mistral":
|
||||||
return model[idx+1:]
|
return after
|
||||||
default:
|
default:
|
||||||
return model
|
return model
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package routing
|
package routing
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestNormalizeAgentID_Empty(t *testing.T) {
|
func TestNormalizeAgentID_Empty(t *testing.T) {
|
||||||
|
|
@ -57,11 +59,11 @@ func TestNormalizeAgentID_AllInvalid(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNormalizeAgentID_TruncatesAt64(t *testing.T) {
|
func TestNormalizeAgentID_TruncatesAt64(t *testing.T) {
|
||||||
long := ""
|
var long strings.Builder
|
||||||
for i := 0; i < 100; i++ {
|
for range 100 {
|
||||||
long += "a"
|
long.WriteString("a")
|
||||||
}
|
}
|
||||||
got := NormalizeAgentID(long)
|
got := NormalizeAgentID(long.String())
|
||||||
if len(got) > MaxAgentIDLength {
|
if len(got) > MaxAgentIDLength {
|
||||||
t.Errorf("length = %d, want <= %d", len(got), MaxAgentIDLength)
|
t.Errorf("length = %d, want <= %d", len(got), MaxAgentIDLength)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -240,7 +240,7 @@ func (sl *SkillsLoader) parseSimpleYAML(content string) map[string]string {
|
||||||
normalized := strings.ReplaceAll(content, "\r\n", "\n")
|
normalized := strings.ReplaceAll(content, "\r\n", "\n")
|
||||||
normalized = strings.ReplaceAll(normalized, "\r", "\n")
|
normalized = strings.ReplaceAll(normalized, "\r", "\n")
|
||||||
|
|
||||||
for _, line := range strings.Split(normalized, "\n") {
|
for line := range strings.SplitSeq(normalized, "\n") {
|
||||||
line = strings.TrimSpace(line)
|
line = strings.TrimSpace(line)
|
||||||
if line == "" || strings.HasPrefix(line, "#") {
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package skills
|
package skills
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -183,7 +183,7 @@ func buildTrigrams(s string) []uint32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort and Deduplication
|
// Sort and Deduplication
|
||||||
sort.Slice(trigrams, func(i, j int) bool { return trigrams[i] < trigrams[j] })
|
slices.Sort(trigrams)
|
||||||
n := 1
|
n := 1
|
||||||
for i := 1; i < len(trigrams); i++ {
|
for i := 1; i < len(trigrams); i++ {
|
||||||
if trigrams[i] != trigrams[i-1] {
|
if trigrams[i] != trigrams[i-1] {
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ func TestSearchCacheConcurrency(t *testing.T) {
|
||||||
|
|
||||||
// Concurrent writes
|
// Concurrent writes
|
||||||
go func() {
|
go func() {
|
||||||
for i := 0; i < 100; i++ {
|
for i := range 100 {
|
||||||
cache.Put("query-write-"+string(rune('a'+i%26)), []SearchResult{{Slug: "x"}})
|
cache.Put("query-write-"+string(rune('a'+i%26)), []SearchResult{{Slug: "x"}})
|
||||||
}
|
}
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
|
|
@ -161,7 +161,7 @@ func TestSearchCacheConcurrency(t *testing.T) {
|
||||||
|
|
||||||
// Concurrent reads
|
// Concurrent reads
|
||||||
go func() {
|
go func() {
|
||||||
for i := 0; i < 100; i++ {
|
for range 100 {
|
||||||
cache.Get("query-write-a")
|
cache.Get("query-write-a")
|
||||||
}
|
}
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ func TestConcurrentAccess(t *testing.T) {
|
||||||
|
|
||||||
// Test concurrent writes
|
// Test concurrent writes
|
||||||
done := make(chan bool, 10)
|
done := make(chan bool, 10)
|
||||||
for i := 0; i < 10; i++ {
|
for i := range 10 {
|
||||||
go func(idx int) {
|
go func(idx int) {
|
||||||
channel := fmt.Sprintf("channel-%d", idx)
|
channel := fmt.Sprintf("channel-%d", idx)
|
||||||
sm.SetLastChannel(channel)
|
sm.SetLastChannel(channel)
|
||||||
|
|
@ -144,7 +144,7 @@ func TestConcurrentAccess(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for all goroutines to complete
|
// Wait for all goroutines to complete
|
||||||
for i := 0; i < 10; i++ {
|
for range 10 {
|
||||||
<-done
|
<-done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package tools
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -218,7 +219,8 @@ func (t *CronTool) listJobs() *ToolResult {
|
||||||
return SilentResult("No scheduled jobs")
|
return SilentResult("No scheduled jobs")
|
||||||
}
|
}
|
||||||
|
|
||||||
result := "Scheduled jobs:\n"
|
var result strings.Builder
|
||||||
|
result.WriteString("Scheduled jobs:\n")
|
||||||
for _, j := range jobs {
|
for _, j := range jobs {
|
||||||
var scheduleInfo string
|
var scheduleInfo string
|
||||||
if j.Schedule.Kind == "every" && j.Schedule.EveryMS != nil {
|
if j.Schedule.Kind == "every" && j.Schedule.EveryMS != nil {
|
||||||
|
|
@ -230,10 +232,10 @@ func (t *CronTool) listJobs() *ToolResult {
|
||||||
} else {
|
} else {
|
||||||
scheduleInfo = "unknown"
|
scheduleInfo = "unknown"
|
||||||
}
|
}
|
||||||
result += fmt.Sprintf("- %s (id: %s, %s)\n", j.Name, j.ID, scheduleInfo)
|
result.WriteString(fmt.Sprintf("- %s (id: %s, %s)\n", j.Name, j.ID, scheduleInfo))
|
||||||
}
|
}
|
||||||
|
|
||||||
return SilentResult(result)
|
return SilentResult(result.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *CronTool) removeJob(args map[string]any) *ToolResult {
|
func (t *CronTool) removeJob(args map[string]any) *ToolResult {
|
||||||
|
|
|
||||||
|
|
@ -329,7 +329,7 @@ func TestToolRegistry_ConcurrentAccess(t *testing.T) {
|
||||||
r := NewToolRegistry()
|
r := NewToolRegistry()
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
for i := 0; i < 50; i++ {
|
for i := range 50 {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(n int) {
|
go func(n int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
|
||||||
|
|
@ -285,7 +285,7 @@ func (p *DuckDuckGoSearchProvider) extractResults(html string, count int, query
|
||||||
|
|
||||||
maxItems := min(len(matches), count)
|
maxItems := min(len(matches), count)
|
||||||
|
|
||||||
for i := 0; i < maxItems; i++ {
|
for i := range maxItems {
|
||||||
urlStr := matches[i][1]
|
urlStr := matches[i][1]
|
||||||
title := stripTags(matches[i][2])
|
title := stripTags(matches[i][2])
|
||||||
title = strings.TrimSpace(title)
|
title = strings.TrimSpace(title)
|
||||||
|
|
@ -293,9 +293,9 @@ func (p *DuckDuckGoSearchProvider) extractResults(html string, count int, query
|
||||||
// URL decoding if needed
|
// URL decoding if needed
|
||||||
if strings.Contains(urlStr, "uddg=") {
|
if strings.Contains(urlStr, "uddg=") {
|
||||||
if u, err := url.QueryUnescape(urlStr); err == nil {
|
if u, err := url.QueryUnescape(urlStr); err == nil {
|
||||||
idx := strings.Index(u, "uddg=")
|
_, after, ok := strings.Cut(u, "uddg=")
|
||||||
if idx != -1 {
|
if ok {
|
||||||
urlStr = u[idx+5:]
|
urlStr = after
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,7 @@ func SplitMessage(content string, maxLen int) []string {
|
||||||
var messages []string
|
var messages []string
|
||||||
|
|
||||||
// Dynamic buffer: 10% of maxLen, but at least 50 chars if possible
|
// Dynamic buffer: 10% of maxLen, but at least 50 chars if possible
|
||||||
codeBlockBuffer := maxLen / 10
|
codeBlockBuffer := max(maxLen/10, 50)
|
||||||
if codeBlockBuffer < 50 {
|
|
||||||
codeBlockBuffer = 50
|
|
||||||
}
|
|
||||||
if codeBlockBuffer > maxLen/2 {
|
if codeBlockBuffer > maxLen/2 {
|
||||||
codeBlockBuffer = maxLen / 2
|
codeBlockBuffer = maxLen / 2
|
||||||
}
|
}
|
||||||
|
|
@ -28,10 +25,7 @@ func SplitMessage(content string, maxLen int) []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Effective split point: maxLen minus buffer, to leave room for code blocks
|
// Effective split point: maxLen minus buffer, to leave room for code blocks
|
||||||
effectiveLimit := maxLen - codeBlockBuffer
|
effectiveLimit := max(maxLen-codeBlockBuffer, maxLen/2)
|
||||||
if effectiveLimit < maxLen/2 {
|
|
||||||
effectiveLimit = maxLen / 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find natural split point within the effective limit
|
// Find natural split point within the effective limit
|
||||||
msgEnd := findLastNewline(content[:effectiveLimit], 200)
|
msgEnd := findLastNewline(content[:effectiveLimit], 200)
|
||||||
|
|
@ -151,10 +145,7 @@ func findNextClosingCodeBlock(text string, startIdx int) int {
|
||||||
// findLastNewline finds the last newline character within the last N characters
|
// findLastNewline finds the last newline character within the last N characters
|
||||||
// Returns the position of the newline or -1 if not found
|
// Returns the position of the newline or -1 if not found
|
||||||
func findLastNewline(s string, searchWindow int) int {
|
func findLastNewline(s string, searchWindow int) int {
|
||||||
searchStart := len(s) - searchWindow
|
searchStart := max(len(s)-searchWindow, 0)
|
||||||
if searchStart < 0 {
|
|
||||||
searchStart = 0
|
|
||||||
}
|
|
||||||
for i := len(s) - 1; i >= searchStart; i-- {
|
for i := len(s) - 1; i >= searchStart; i-- {
|
||||||
if s[i] == '\n' {
|
if s[i] == '\n' {
|
||||||
return i
|
return i
|
||||||
|
|
@ -166,10 +157,7 @@ func findLastNewline(s string, searchWindow int) int {
|
||||||
// findLastSpace finds the last space character within the last N characters
|
// findLastSpace finds the last space character within the last N characters
|
||||||
// Returns the position of the space or -1 if not found
|
// Returns the position of the space or -1 if not found
|
||||||
func findLastSpace(s string, searchWindow int) int {
|
func findLastSpace(s string, searchWindow int) int {
|
||||||
searchStart := len(s) - searchWindow
|
searchStart := max(len(s)-searchWindow, 0)
|
||||||
if searchStart < 0 {
|
|
||||||
searchStart = 0
|
|
||||||
}
|
|
||||||
for i := len(s) - 1; i >= searchStart; i-- {
|
for i := len(s) - 1; i >= searchStart; i-- {
|
||||||
if s[i] == ' ' || s[i] == '\t' {
|
if s[i] == ' ' || s[i] == '\t' {
|
||||||
return i
|
return i
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue