package main import ( "encoding/json" "fmt" "html/template" "log" "net/http" "net/url" "strconv" "time" ) const PIPED_INSTANCE = "pipedapi.kavin.rocks" // VideoResult reflects the structured data for a video result type VideoResult struct { Href string Title string Date string Views string Creator string Publisher string Image string Duration string } // VideoAPIResponse matches the structure of the JSON response from the Piped API type VideoAPIResponse struct { Items []struct { URL string `json:"url"` Title string `json:"title"` UploaderName string `json:"uploaderName"` Views int `json:"views"` Thumbnail string `json:"thumbnail"` Duration int `json:"duration"` UploadedDate string `json:"uploadedDate"` Type string `json:"type"` } `json:"items"` } // Function to format views similarly to the Python code func formatViews(views int) string { switch { case views >= 1_000_000_000: return fmt.Sprintf("%.1fB views", float64(views)/1_000_000_000) case views >= 1_000_000: return fmt.Sprintf("%.1fM views", float64(views)/1_000_000) case views >= 10_000: return fmt.Sprintf("%.1fK views", float64(views)/1_000) case views == 1: return fmt.Sprintf("%d view", views) default: return fmt.Sprintf("%d views", views) } } // formatDuration formats video duration as done in the Python code func formatDuration(seconds int) string { if 0 > seconds { return "Live" } hours := seconds / 3600 minutes := (seconds % 3600) / 60 seconds = seconds % 60 if hours > 0 { return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) } return fmt.Sprintf("%02d:%02d", minutes, seconds) } // makeHTMLRequest fetches search results from the Piped API, similarly to the Python `makeHTMLRequest` func makeHTMLRequest(query string) (*VideoAPIResponse, error) { resp, err := http.Get(fmt.Sprintf("https://%s/search?q=%s&filter=all", PIPED_INSTANCE, url.QueryEscape(query))) if err != nil { return nil, fmt.Errorf("error making request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) } var apiResp VideoAPIResponse if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil { return nil, fmt.Errorf("error decoding response: %w", err) } return &apiResp, nil } func videoSearchEndpointHandler(w http.ResponseWriter, r *http.Request) { query := r.URL.Query().Get("q") safe := r.URL.Query().Get("safe") // Assuming "safe" is a query parameter lang := r.URL.Query().Get("lang") // Assuming "lang" is a query parameter pageStr := r.URL.Query().Get("page") page, err := strconv.Atoi(pageStr) if err != nil { // Handle error, perhaps set page to a default value if it's not critical page = 1 // Default page } handleVideoSearch(w, query, safe, lang, page) } // handleVideoSearch adapted from the Python `videoResults`, handles video search requests func handleVideoSearch(w http.ResponseWriter, query, safe, lang string, page int) { start := time.Now() // Modify `makeHTMLRequest` to also accept `safe`, `lang`, and `page` parameters if necessary apiResp, err := makeHTMLRequest(query) if err != nil { log.Printf("Error fetching video results: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } var results []VideoResult for _, item := range apiResp.Items { if item.Type == "channel" || item.Type == "playlist" { continue } if item.UploadedDate == "" { item.UploadedDate = "Now" } results = append(results, VideoResult{ Href: fmt.Sprintf("https://youtube.com%s", item.URL), Title: item.Title, Date: item.UploadedDate, Views: formatViews(item.Views), Creator: item.UploaderName, Publisher: "Piped", Image: fmt.Sprintf("/img_proxy?url=%s", url.QueryEscape(item.Thumbnail)), Duration: formatDuration(item.Duration), }) } elapsed := time.Since(start) tmpl, err := template.ParseFiles("templates/videos.html") if err != nil { log.Printf("Error parsing template: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } tmpl.Execute(w, map[string]interface{}{ "Results": results, "Query": query, "Fetched": fmt.Sprintf("%.2f seconds", elapsed.Seconds()), }) }