Improve translated search flow and modal layout
build-push / docker (push) Successful in 4m19s

This commit is contained in:
AI Assistant
2026-03-13 11:57:58 +09:00
parent 1fc06fb785
commit 6852e07607
4 changed files with 186 additions and 70 deletions
+73 -47
View File
@@ -52,30 +52,30 @@ func (s *SearchService) SearchMedia(queries []string) ([]SearchResult, error) {
categories string
engine string
queryBuilder func(string) string
match func(string) bool
match func(SearchResult) bool
}{
{
name: "Google Video",
categories: "videos",
engine: s.GoogleVideoEngine,
queryBuilder: func(query string) string {
return query
return buildGoogleVideoQuery(query)
},
match: func(string) bool { return true },
match: isUsefulGoogleVideoResult,
},
{
name: "Envato",
categories: "general",
engine: s.WebEngine,
queryBuilder: buildEnvatoQuery,
match: isEnvatoURL,
match: isRenderableEnvatoResult,
},
{
name: "Artgrid",
categories: "general",
engine: s.WebEngine,
queryBuilder: buildArtgridQuery,
match: func(link string) bool { return strings.Contains(strings.ToLower(link), "artgrid.io") },
match: isRenderableArtgridResult,
},
}
@@ -88,10 +88,7 @@ func (s *SearchService) SearchMedia(queries []string) ([]SearchResult, error) {
continue
}
for _, source := range sources {
searchQuery := query
if source.queryBuilder != nil {
searchQuery = source.queryBuilder(query)
}
searchQuery := source.queryBuilder(query)
items, err := s.search(searchQuery, source.categories, source.engine, source.name)
if err != nil {
@@ -112,10 +109,7 @@ func (s *SearchService) SearchMedia(queries []string) ([]SearchResult, error) {
if item.Link == "" || seen[item.Link] {
continue
}
if source.match != nil && !source.match(item.Link) {
continue
}
if !isRenderableLink(item.Link, item.Source) {
if !source.match(item) {
continue
}
seen[item.Link] = true
@@ -138,7 +132,7 @@ func (s *SearchService) search(query, categories, engine, source string) ([]Sear
values.Set("q", query)
values.Set("format", "json")
values.Set("safesearch", "0")
values.Set("language", "ko-KR")
values.Set("language", "en-US")
if categories != "" {
values.Set("categories", categories)
}
@@ -189,6 +183,71 @@ func (s *SearchService) search(query, categories, engine, source string) ([]Sear
return results, nil
}
func buildGoogleVideoQuery(query string) string {
return fmt.Sprintf(`"%s" ("stock footage" OR "b-roll" OR cinematic OR "establishing shot" OR editorial) -tutorial -"how to" -review -reaction -course -podcast -vlog -interview -breakdown -edit -editing`, query)
}
func buildEnvatoQuery(query string) string {
return fmt.Sprintf(`"%s" ("stock footage" OR "stock video" OR "motion graphics" OR cinematic OR "b-roll") (site:elements.envato.com OR site:videohive.net/item) -site:elements.envato.com/stock-video -site:elements.envato.com/video-templates -site:elements.envato.com/stock-video/stock-footage`, query)
}
func buildArtgridQuery(query string) string {
return fmt.Sprintf(`"%s" ("stock footage" OR "b-roll" OR cinematic OR editorial) site:artgrid.io/clip/`, query)
}
func isUsefulGoogleVideoResult(result SearchResult) bool {
text := strings.ToLower(result.Title + " " + result.Snippet)
for _, banned := range []string{
"tutorial", "how to", "review", "reaction", "podcast", "interview", "walkthrough",
"course", "lesson", "edit tutorial", "editing tutorial", "premiere pro", "after effects",
"breakdown", "explained", "vlog",
} {
if strings.Contains(text, banned) {
return false
}
}
for _, desired := range []string{
"b-roll", "stock footage", "cinematic", "footage", "establishing shot", "4k",
} {
if strings.Contains(text, desired) {
return true
}
}
lowerLink := strings.ToLower(result.Link)
return strings.Contains(lowerLink, "youtube.com/watch") || strings.Contains(lowerLink, "youtu.be/")
}
func isRenderableEnvatoResult(result SearchResult) bool {
parsed, err := url.Parse(result.Link)
if err != nil {
return false
}
host := strings.ToLower(parsed.Host)
path := strings.Trim(parsed.Path, "/")
if strings.Contains(host, "videohive.net") {
return strings.HasPrefix(path, "item/")
}
if strings.Contains(host, "elements.envato.com") {
if path == "" || strings.Contains(path, "/") {
return false
}
return regexp.MustCompile(`-[A-Z0-9]{6,}$`).MatchString(path)
}
return false
}
func isRenderableArtgridResult(result SearchResult) bool {
parsed, err := url.Parse(result.Link)
if err != nil {
return false
}
if !strings.Contains(strings.ToLower(parsed.Host), "artgrid.io") {
return false
}
path := strings.Trim(parsed.Path, "/")
return regexp.MustCompile(`^clip/[0-9]+/`).MatchString(path)
}
func firstNonEmpty(values ...string) string {
for _, value := range values {
if strings.TrimSpace(value) != "" {
@@ -225,39 +284,6 @@ func inferDisplayLink(link string, parsed []any) string {
return ""
}
func isEnvatoURL(link string) bool {
lower := strings.ToLower(link)
return strings.Contains(lower, "envato") || strings.Contains(lower, "videohive.net")
}
func isRenderableLink(link, source string) bool {
parsed, err := url.Parse(link)
if err != nil {
return false
}
path := strings.Trim(parsed.Path, "/")
if path == "" {
return false
}
lower := strings.ToLower(link)
switch source {
case "Envato":
return strings.Contains(lower, "/item/") || strings.Contains(lower, "/stock-video/") || strings.Contains(lower, "/video-templates/")
case "Artgrid":
return strings.Contains(lower, "artgrid.io") && len(strings.Split(path, "/")) >= 2
default:
return true
}
}
func buildEnvatoQuery(query string) string {
return fmt.Sprintf(`%s ("stock video" OR footage OR "video template" OR cinematic) (site:elements.envato.com/stock-video OR site:elements.envato.com/video-templates OR site:videohive.net/item)`, query)
}
func buildArtgridQuery(query string) string {
return fmt.Sprintf(`%s ("stock footage" OR "b-roll" OR cinematic OR editorial) (site:artgrid.io)`, query)
}
func deriveThumbnail(link string) string {
if videoID := extractYouTubeID(link); videoID != "" {
return "https://i.ytimg.com/vi/" + videoID + "/hqdefault.jpg"