/* * Scope - A simple and minimal metasearch engine * Copyright (C) 2021 Arsen Musayelyan * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package cards import ( "encoding/json" "fmt" "html/template" "net/http" "net/url" "time" ) func init() { Register("ddg", 3, NewDDGCard) } // ddgAttribution is HTML for attribution of DuckDuckGo at their request const ddgAttribution = `

  Results from DuckDuckGo »

` // DDGCard represents a DuckDuckGo Instant Answer API card type DDGCard struct { query string userAgent string resp DDGInstAns } // DDGInstAns represents a DuckDuckGo Instant Answer API response type DDGInstAns struct { Abstract string AbstractText string AbstractSource string AbstractURL string Image string Heading string Answer string AnswerType string } // NewDDGCard is a NewCardFunc that creates a new DDGCard func NewDDGCard(query, userAgent string) Card { return &DDGCard{ query: url.QueryEscape(query), userAgent: userAgent, } } // RunQuery requests the query from the instant answer API func (ddg *DDGCard) RunQuery() error { http.DefaultClient.Timeout = 5 * time.Second // Create new API request req, err := http.NewRequest( http.MethodGet, "https://api.duckduckgo.com/?q="+ddg.query+"&format=json", nil, ) if err != nil { return err } req.Header.Set("User-Agent", ddg.userAgent) // Perform request res, err := http.DefaultClient.Do(req) if err != nil { return err } // Decode response into response struct err = json.NewDecoder(res.Body).Decode(&ddg.resp) if err != nil { return err } return nil } // Returned checks if abstract is empty. // If it is, query returned no result. func (ddg *DDGCard) Returned() bool { // Value was returned if abstract is not empty return ddg.resp.Abstract != "" } // Matches always returns true as there are no keys func (ddg *DDGCard) Matches() bool { return true } // StripKey returns the query as there are no keys func (ddg *DDGCard) StripKey() string { // No key to strip, so return query return ddg.query } // Title returns the instant answer heading func (ddg *DDGCard) Title() string { return ddg.resp.Heading } // Content returns the instant answer abstract with // DuckDuckGo attibution func (ddg *DDGCard) Content() template.HTML { // Return abstract with attribution return template.HTML(ddg.resp.Abstract + fmt.Sprintf(ddgAttribution, ddg.query)) } // Footer returns a footer containing URL and name of source // gotten from instant answer API func (ddg *DDGCard) Footer() template.HTML { // Return footer with abstract url and source return template.HTML(fmt.Sprintf( ``, ddg.resp.AbstractURL, ddg.resp.AbstractSource, )) } // Head returns an empty string as no extra tags are reuired // for DDGCard func (ddg *DDGCard) Head() template.HTML { return "" }