Files
ai-media-hub/backend/services/cse.go
AI Assistant d7506c041a
Some checks failed
build-push / docker (push) Has been cancelled
Initial AI media hub implementation
2026-03-12 15:01:18 +09:00

117 lines
2.8 KiB
Go

package services
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
)
type SearchResult struct {
Title string `json:"title"`
Link string `json:"link"`
DisplayLink string `json:"displayLink"`
Snippet string `json:"snippet"`
ThumbnailURL string `json:"thumbnailUrl"`
Source string `json:"source"`
}
type SearchService struct {
APIKey string
CX string
Client *http.Client
}
func NewSearchService(apiKey, cx string) *SearchService {
return &SearchService{
APIKey: apiKey,
CX: cx,
Client: &http.Client{Timeout: 20 * time.Second},
}
}
func (s *SearchService) SearchMedia(query string) ([]SearchResult, error) {
if s.APIKey == "" || s.CX == "" {
return nil, fmt.Errorf("google cse credentials are not configured")
}
domains := []string{"youtube.com", "tiktok.com", "envato.com", "artgrid.io"}
siteQuery := strings.Join(domains, " OR site:")
fullQuery := fmt.Sprintf("%s (site:%s)", query, siteQuery)
values := url.Values{}
values.Set("key", s.APIKey)
values.Set("cx", s.CX)
values.Set("q", fullQuery)
values.Set("searchType", "image")
values.Set("num", "10")
values.Set("safe", "off")
results := make([]SearchResult, 0, 30)
seen := map[string]bool{}
for _, start := range []string{"1", "11", "21"} {
values.Set("start", start)
endpoint := "https://www.googleapis.com/customsearch/v1?" + values.Encode()
resp, err := s.Client.Get(endpoint)
if err != nil {
return nil, err
}
if resp.StatusCode >= 300 {
resp.Body.Close()
return nil, fmt.Errorf("google cse returned status %d", resp.StatusCode)
}
var payload struct {
Items []struct {
Title string `json:"title"`
Link string `json:"link"`
DisplayLink string `json:"displayLink"`
Snippet string `json:"snippet"`
Image struct {
ThumbnailLink string `json:"thumbnailLink"`
} `json:"image"`
} `json:"items"`
}
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
resp.Body.Close()
return nil, err
}
resp.Body.Close()
for _, item := range payload.Items {
if item.Link == "" || seen[item.Link] {
continue
}
seen[item.Link] = true
results = append(results, SearchResult{
Title: item.Title,
Link: item.Link,
DisplayLink: item.DisplayLink,
Snippet: item.Snippet,
ThumbnailURL: item.Image.ThumbnailLink,
Source: inferSource(item.DisplayLink),
})
}
}
return results, nil
}
func inferSource(displayLink string) string {
switch {
case strings.Contains(displayLink, "youtube"):
return "YouTube"
case strings.Contains(displayLink, "tiktok"):
return "TikTok"
case strings.Contains(displayLink, "envato"):
return "Envato"
case strings.Contains(displayLink, "artgrid"):
return "Artgrid"
default:
return displayLink
}
}