Initial AI media hub implementation
Some checks failed
build-push / docker (push) Has been cancelled
Some checks failed
build-push / docker (push) Has been cancelled
This commit is contained in:
116
backend/services/cse.go
Normal file
116
backend/services/cse.go
Normal file
@@ -0,0 +1,116 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user