package main import ( "flag" "fmt" "io" "net/http" "net/url" "os" "strings" "bytes" "github.com/PuerkitoBio/goquery" ) func die(err error) { if (err != nil) { fmt.Printf("%=+v\n", err) os.Exit(1) } } func quit(message string, exitCode int) { fmt.Println(message) os.Exit(exitCode) } func getNodeText(sel *goquery.Selection, cssSel string) string { return strings.Trim(sel.Find(cssSel).Eq(0).Text(), " \t\n\r") } const userAgent = "websearch/0.1 (textmode; Linux x86_64; 1024x768)" const ddgUrl = "https://html.duckduckgo.com/html" // See https://duckduckgo.com/params for more. type DDGQuery struct { q string // search terms b string // ? df string // duration, but not sure of what kf string // favicons, -1 is off kh string // https, 1 is on kl string // region of search kp string // Safe search, 1 is on k1 string // advertisements, -1 is off } func makeQuery(qs []string) string { qsEscaped := []string{} for _, str := range qs { qsEscaped = append(qsEscaped, url.QueryEscape(str)) } query := DDGQuery{ q: strings.Join(qsEscaped, "+"), b: "", df: "", kf: "-1", kl: "us-en", kh: "1", kp: "1", k1: "-1", } queryStr := fmt.Sprintf("q=%s", query.q) queryStr += fmt.Sprintf("&b=%s", url.QueryEscape(query.b)) queryStr += fmt.Sprintf("&df=%s", url.QueryEscape(query.df)) queryStr += fmt.Sprintf("&kf=%s", url.QueryEscape(query.kf)) queryStr += fmt.Sprintf("&kh=%s", url.QueryEscape(query.kh)) queryStr += fmt.Sprintf("&kl=%s", url.QueryEscape(query.kl)) queryStr += fmt.Sprintf("&kp=%s", url.QueryEscape(query.kp)) queryStr += fmt.Sprintf("&k1=%s", url.QueryEscape(query.k1)) return queryStr } func main() { flag.Parse() searchWords := flag.Args() if len(searchWords) <= 0 { quit("Enter a search", 1) } reqBody := makeQuery(searchWords) req, err := http.NewRequest("GET", ddgUrl + "?" + reqBody , nil); die(err) req.Header.Set("User-Agent", userAgent) req.Header.Set("DNT", "1") res, err := http.DefaultClient.Do(req); die(err); defer res.Body.Close() resBytes, err := io.ReadAll(res.Body); die(err) goqueryDoc, err := goquery.NewDocumentFromReader(bytes.NewReader(resBytes)); die(err) results := goqueryDoc.Find(".web-result") if len(results.Nodes) == 0 { quit(fmt.Sprintf("No results for %s\n", reqBody), 0) } report := "" for i := len(results.Nodes) - 1; i >= 0; i-- { r := results.Eq(i) report += fmt.Sprintf( "R%02d: %s\n%s\n%s\n\n", i+1, getNodeText(r, ".result__title"), getNodeText(r, ".result__url"), getNodeText(r, ".result__snippet"), ) } fmt.Print(report) }