diff --git a/main.go b/main.go
index 0fdcf8b..46e2d4d 100644
--- a/main.go
+++ b/main.go
@@ -111,6 +111,8 @@ func handleSearch(w http.ResponseWriter, r *http.Request) {
handleTextSearch(w, query, safe, lang)
case "image":
handleImageSearch(w, query, safe, lang, page)
+ case "video":
+ videoSearchEndpointHandler(w, r)
default:
http.ServeFile(w, r, "static/search.html")
}
diff --git a/run.sh b/run.sh
new file mode 100755
index 0000000..469dfac
--- /dev/null
+++ b/run.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+go run main.go text.go images.go imageproxy.go video.go
\ No newline at end of file
diff --git a/templates/videos.html b/templates/videos.html
new file mode 100644
index 0000000..47a4c94
--- /dev/null
+++ b/templates/videos.html
@@ -0,0 +1,29 @@
+
+
+
+
+
Video Results
+ {{ if .Results }}
+ {{ range .Results }}
+
+
{{ .Title }}
+
+
+
+
{{ .Views }} views | {{ .Duration }}
+
By {{ .Creator }}
+
+
+ {{ end }}
+ {{ else }}
+
No results found. Try adjusting your search term.
+ {{ end }}
+
+
+
diff --git a/video.go b/video.go
new file mode 100644
index 0000000..9719ddd
--- /dev/null
+++ b/video.go
@@ -0,0 +1,143 @@
+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("%dM views", views/1_000_000)
+ case views >= 10_000:
+ return fmt.Sprintf("%.1fK views", float64(views)/1_000)
+ default:
+ return fmt.Sprintf("%d views", views)
+ }
+}
+
+// formatDuration formats video duration as done in the Python code
+func formatDuration(seconds int) string {
+ 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
+ }
+ results = append(results, VideoResult{
+ Href: fmt.Sprintf("https://%s%s", PIPED_INSTANCE, 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()),
+ })
+}