Add DuckDuckGo support and refactor caching system
Introduced DuckDuckGo as a new search provider alongside Google. Implemented a flexible caching system with in-memory, file-based, and no-op cache options to improve modularity. Updated dependencies and revised the project structure for improved maintainability.
This commit is contained in:
78
pkg/cache/memory.go
vendored
Normal file
78
pkg/cache/memory.go
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
type memoryCache struct {
|
||||
data map[string][]byte
|
||||
}
|
||||
|
||||
var _ Cache = &memoryCache{}
|
||||
|
||||
func NewMemoryCache() (Cache, error) {
|
||||
return &memoryCache{
|
||||
data: make(map[string][]byte),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *memoryCache) Get(key string, writer io.Writer) error {
|
||||
data, ok := m.data[key]
|
||||
if ok {
|
||||
_, err := writer.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
func (m *memoryCache) GetString(key string) (string, error) {
|
||||
data, ok := m.data[key]
|
||||
if ok {
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
return "", ErrNotFound
|
||||
}
|
||||
|
||||
func (m *memoryCache) GetJSON(key string, value interface{}) error {
|
||||
data, ok := m.data[key]
|
||||
if ok {
|
||||
return json.Unmarshal(data, value)
|
||||
}
|
||||
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
func (m *memoryCache) Set(key string, value io.Reader) error {
|
||||
data, err := io.ReadAll(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.data[key] = data
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryCache) SetJSON(key string, value interface{}) error {
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.data[key] = data
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryCache) SetString(key string, value string) error {
|
||||
m.data[key] = []byte(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryCache) Delete(key string) error {
|
||||
delete(m.data, key)
|
||||
return nil
|
||||
}
|
38
pkg/cache/nop.go
vendored
Normal file
38
pkg/cache/nop.go
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type Nop struct {
|
||||
}
|
||||
|
||||
var _ Cache = Nop{}
|
||||
|
||||
func (Nop) Get(_ string, _ io.Writer) error {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
func (Nop) GetString(_ string) (string, error) {
|
||||
return "", ErrNotFound
|
||||
}
|
||||
|
||||
func (Nop) GetJSON(_ string, _ interface{}) error {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
func (Nop) Set(_ string, _ io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Nop) SetJSON(_ string, _ interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Nop) SetString(_ string, _ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Nop) Delete(_ string) error {
|
||||
return nil
|
||||
}
|
83
pkg/search/duckduckgo.go
Normal file
83
pkg/search/duckduckgo.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"answer/pkg/cache"
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gitea.stevedudenhoeffer.com/steve/go-extractor"
|
||||
|
||||
"gitea.stevedudenhoeffer.com/steve/go-extractor/sites/duckduckgo"
|
||||
)
|
||||
|
||||
type duckDuckGo struct {
|
||||
Cache cache.Cache
|
||||
Browser extractor.Browser
|
||||
}
|
||||
|
||||
func NewDuckDuckGo(c cache.Cache) (Search, error) {
|
||||
timeout := 60 * time.Second
|
||||
browser, err := extractor.NewPlayWrightBrowser(extractor.PlayWrightBrowserOptions{Timeout: &timeout})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create browser: %w", err)
|
||||
}
|
||||
|
||||
return duckDuckGo{
|
||||
Cache: c,
|
||||
Browser: browser,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var _ Search = duckDuckGo{}
|
||||
|
||||
func (d duckDuckGo) Search(ctx context.Context, search string) ([]Result, error) {
|
||||
var res []Result
|
||||
|
||||
key := "duckduckgo:" + search
|
||||
|
||||
err := d.Cache.GetJSON(key, &res)
|
||||
|
||||
if err == nil {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
results, err := d.searchDuckDuckGo(ctx, search)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, r := range results {
|
||||
res = append(res, Result{
|
||||
Title: r.Title,
|
||||
URL: r.URL,
|
||||
Description: r.Description,
|
||||
})
|
||||
}
|
||||
|
||||
_ = d.Cache.SetJSON(key, res)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d duckDuckGo) searchDuckDuckGo(ctx context.Context, search string) ([]Result, error) {
|
||||
cfg := duckduckgo.DefaultConfig
|
||||
|
||||
r, err := cfg.Search(ctx, d.Browser, search)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := make([]Result, len(r))
|
||||
|
||||
for i, v := range r {
|
||||
res[i] = Result{
|
||||
URL: v.URL,
|
||||
Title: v.Title,
|
||||
Description: v.Description,
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
Reference in New Issue
Block a user