Search images from Pinterest — submit a search to view results.
`)
// search block
io.WriteString(w, ``)
// bookmarks area (server-rendered)
if bookmarkingEnabled {
items := readBookmarksFromReq(r)
io.WriteString(w, `
Saved searches
`)
for _, q := range items {
escaped := html.EscapeString(q)
// Each bookmark pill: link to /search?q=... and a small form to remove
io.WriteString(w, ``+escaped+``)
// remove form
io.WriteString(w, ``)
}
io.WriteString(w, `
`)
}
io.WriteString(w, ``)
}
// Search streaming handler (same streaming approach as before), includes server-side Save form when bookmarkingEnabled
func searchHandler(w http.ResponseWriter, r *http.Request) {
q := strings.TrimSpace(r.URL.Query().Get("q"))
if len(q) < 1 || len(q) > 64 {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
bookmark := r.URL.Query().Get("bookmark")
csrftoken := r.URL.Query().Get("csrftoken")
dataObj := map[string]any{"options": map[string]any{"query": q}}
if bookmark != "" {
dataObj["options"].(map[string]any)["bookmarks"] = []string{bookmark}
}
jb, err := json.Marshal(dataObj)
if err != nil {
http.Error(w, "internal", http.StatusInternalServerError)
return
}
dataParam := url.QueryEscape(string(jb))
var req *http.Request
if bookmark == "" {
u := pinterestSearchURL + "?data=" + dataParam
req, err = http.NewRequestWithContext(r.Context(), "GET", u, nil)
} else {
body := "data=" + dataParam
req, err = http.NewRequestWithContext(r.Context(), "POST", pinterestSearchURL, strings.NewReader(body))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
if err != nil {
http.Error(w, "failed to build request", http.StatusInternalServerError)
return
}
req.Header.Set("x-pinterest-pws-handler", "www/search/[scope].js")
if csrftoken != "" {
req.Header.Set("x-csrftoken", csrftoken)
req.Header.Set("Cookie", "csrftoken="+csrftoken)
}
resp, err := httpClient.Do(req)
if err != nil {
http.Error(w, "failed to fetch", http.StatusBadGateway)
return
}
defer resp.Body.Close()
var newCsrf string
for _, c := range resp.Cookies() {
if strings.EqualFold(c.Name, "csrftoken") {
newCsrf = c.Value
break
}
}
// start streaming HTML
w.Header().Set("Content-Type", "text/html; charset=utf-8")
io.WriteString(w, ``+html.EscapeString(q)+` - Pinata`)
// header with inline search and server-side Save form (if enabled)
io.WriteString(w, `
`)
// inline search form
io.WriteString(w, ``)
// Save form: POSTs to /bookmark with q and next back to the current results page
if bookmarkingEnabled {
// next param to return to this search page (use URL-encoded /search?q=...)
next := "/search?q=" + url.QueryEscape(q)
io.WriteString(w, ``)
}
io.WriteString(w, `
`)
io.WriteString(w, `
Results for "`+html.EscapeString(q)+`"
`)
io.WriteString(w, `
`)
dec := json.NewDecoder(resp.Body)
var nextBookmark string
for {
tk, err := dec.Token()
if err != nil {
if err == io.EOF {
break
}
log.Printf("json token error: %v", err)
break
}
key, ok := tk.(string)
if !ok {
continue
}
switch key {
case "results":
t, err := dec.Token()
if err != nil {
log.Printf("unexpected json after results: %v", err)
continue
}
if delim, ok := t.(json.Delim); !ok || delim != '[' {
continue
}
var rObj struct {
Images struct {
Orig struct {
URL string `json:"url"`
} `json:"orig"`
} `json:"images"`
}
for dec.More() {
if err := dec.Decode(&rObj); err != nil {
log.Printf("error decoding result item: %v", err)
break
}
u := strings.TrimSpace(rObj.Images.Orig.URL)
if u == "" {
continue
}
esc := url.QueryEscape(u)
b64 := base64.StdEncoding.EncodeToString([]byte(u))
var cardBuilder strings.Builder
cardBuilder.WriteString(`
`)
if !disableReverse {
cardBuilder.WriteString(`🔍`)
}
cardBuilder.WriteString(`
`)
io.WriteString(w, cardBuilder.String())
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
}
_, _ = dec.Token() // consume closing
case "bookmark":
t, err := dec.Token()
if err == nil {
if s, ok := t.(string); ok {
nextBookmark = s
}
}
default:
continue
}
}
// close container
io.WriteString(w, `
`)
// show pagination link if present
if nextBookmark != "" {
qenc := url.QueryEscape(q)
benc := url.QueryEscape(nextBookmark)
cenc := ""
if newCsrf != "" {
cenc = "&csrftoken=" + url.QueryEscape(newCsrf)
} else if csrftoken != "" {
cenc = "&csrftoken=" + url.QueryEscape(csrftoken)
}
next := "/search?q=" + qenc + "&bookmark=" + benc + cenc
io.WriteString(w, `