169 lines
4.4 KiB
Go
Executable File
169 lines
4.4 KiB
Go
Executable File
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
)
|
|
|
|
// SearchResult represents a search result with frontend-compatible fields
|
|
type SearchResult struct {
|
|
Title string `json:"title"`
|
|
Artist string `json:"artist"`
|
|
URL string `json:"url"` // For frontend compatibility
|
|
Link string `json:"link"` // Alternative field
|
|
LyricURL string `json:"lyric_url"` // Lyrics URL
|
|
CoverURL string `json:"cover_url"`
|
|
Duration int `json:"duration"` // Duration in seconds
|
|
}
|
|
|
|
// HandleSearch handles music search requests
|
|
func HandleSearch(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
|
query := r.URL.Query().Get("query")
|
|
fmt.Printf("[API] Search request: query=%s\n", query)
|
|
|
|
if query == "" {
|
|
json.NewEncoder(w).Encode([]SearchResult{})
|
|
return
|
|
}
|
|
|
|
// Search from multiple sources
|
|
items := searchFromAllSources(query)
|
|
|
|
// Convert to frontend-compatible format
|
|
results := make([]SearchResult, len(items))
|
|
for i, item := range items {
|
|
// Use AudioFullURL as primary, fallback to AudioURL
|
|
audioURL := item.AudioFullURL
|
|
if audioURL == "" {
|
|
audioURL = item.AudioURL
|
|
}
|
|
|
|
results[i] = SearchResult{
|
|
Title: item.Title,
|
|
Artist: item.Artist,
|
|
URL: audioURL,
|
|
Link: audioURL,
|
|
LyricURL: item.LyricURL,
|
|
CoverURL: item.CoverURL,
|
|
Duration: item.Duration,
|
|
}
|
|
|
|
fmt.Printf("[API] Result %d: title=%s, artist=%s, url=%s, lyric=%s\n", i+1, item.Title, item.Artist, audioURL, item.LyricURL)
|
|
}
|
|
|
|
fmt.Printf("[API] Search completed: %d results found\n", len(results))
|
|
json.NewEncoder(w).Encode(results)
|
|
}
|
|
|
|
// searchFromAllSources searches music from all available sources
|
|
func searchFromAllSources(query string) []MusicItem {
|
|
var results []MusicItem
|
|
|
|
// Source 1: Search from sources.json (local config)
|
|
sources := readSources()
|
|
for _, source := range sources {
|
|
if containsIgnoreCase(source.Title, query) || containsIgnoreCase(source.Artist, query) {
|
|
item := MusicItem{
|
|
Title: source.Title,
|
|
Artist: source.Artist,
|
|
AudioURL: source.AudioURL,
|
|
AudioFullURL: source.AudioFullURL,
|
|
M3U8URL: source.M3U8URL,
|
|
LyricURL: source.LyricURL,
|
|
CoverURL: source.CoverURL,
|
|
Duration: source.Duration,
|
|
FromCache: false,
|
|
}
|
|
results = append(results, item)
|
|
|
|
// Limit results to prevent too many items
|
|
if len(results) >= 20 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Source 2: Search from local files
|
|
localItems := searchLocalMusic(query)
|
|
results = append(results, localItems...)
|
|
|
|
// If no local results, try API search
|
|
if len(results) == 0 {
|
|
apiResults := searchFromAPI(query)
|
|
results = append(results, apiResults...)
|
|
}
|
|
|
|
// Limit total results
|
|
if len(results) > 20 {
|
|
results = results[:20]
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
// searchLocalMusic searches music from local files
|
|
func searchLocalMusic(query string) []MusicItem {
|
|
// This function would search from local music files
|
|
// For now, return empty array
|
|
// TODO: Implement local file search
|
|
return []MusicItem{}
|
|
}
|
|
|
|
// searchFromAPI searches music from external APIs
|
|
func searchFromAPI(query string) []MusicItem {
|
|
var results []MusicItem
|
|
|
|
// Try different sources in priority order: 酷我 > 网易云 > 咪咕 > 百度
|
|
// 酷我的搜索结果更准确,原唱版本更容易排在前面
|
|
sources := []string{"kuwo", "netease", "migu", "baidu"}
|
|
|
|
for _, source := range sources {
|
|
item := YuafengAPIResponseHandler(source, query, "")
|
|
if item.Title != "" {
|
|
results = append(results, item)
|
|
break // Return first successful result
|
|
}
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
// containsIgnoreCase checks if a string contains substring (case insensitive)
|
|
func containsIgnoreCase(s, substr string) bool {
|
|
s = toLower(s)
|
|
substr = toLower(substr)
|
|
return contains(s, substr)
|
|
}
|
|
|
|
func toLower(s string) string {
|
|
var result []rune
|
|
for _, r := range s {
|
|
if r >= 'A' && r <= 'Z' {
|
|
r += 32
|
|
}
|
|
result = append(result, r)
|
|
}
|
|
return string(result)
|
|
}
|
|
|
|
func contains(s, substr string) bool {
|
|
return len(substr) == 0 || indexString(s, substr) >= 0
|
|
}
|
|
|
|
func indexString(s, substr string) int {
|
|
n := len(substr)
|
|
if n == 0 {
|
|
return 0
|
|
}
|
|
for i := 0; i+n <= len(s); i++ {
|
|
if s[i:i+n] == substr {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|