2021-12-08 21:38:22 +00:00
|
|
|
/*
|
|
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2021-12-08 17:24:05 +00:00
|
|
|
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 = `<p class="is-size-7 pt-2">
|
|
|
|
<span class="iconify" data-icon="simple-icons:duckduckgo" data-inline="true"></span>
|
|
|
|
Results from
|
|
|
|
<a href="https://duckduckgo.com/%s">DuckDuckGo »</a>
|
|
|
|
</p>`
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2021-12-08 21:18:14 +00:00
|
|
|
// NewDDGCard is a NewCardFunc that creates a new DDGCard
|
2021-12-08 17:24:05 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-12-08 21:18:14 +00:00
|
|
|
// Decode response into response struct
|
2021-12-08 17:24:05 +00:00
|
|
|
err = json.NewDecoder(res.Body).Decode(&ddg.resp)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-12-08 21:18:14 +00:00
|
|
|
// Returned checks if abstract is empty.
|
|
|
|
// If it is, query returned no result.
|
2021-12-08 17:24:05 +00:00
|
|
|
func (ddg *DDGCard) Returned() bool {
|
|
|
|
// Value was returned if abstract is not empty
|
|
|
|
return ddg.resp.Abstract != ""
|
|
|
|
}
|
|
|
|
|
2021-12-08 21:18:14 +00:00
|
|
|
// Matches always returns true as there are no keys
|
2021-12-08 17:24:05 +00:00
|
|
|
func (ddg *DDGCard) Matches() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-12-08 21:18:14 +00:00
|
|
|
// StripKey returns the query as there are no keys
|
2021-12-08 17:24:05 +00:00
|
|
|
func (ddg *DDGCard) StripKey() string {
|
|
|
|
// No key to strip, so return query
|
|
|
|
return ddg.query
|
|
|
|
}
|
|
|
|
|
2021-12-08 21:18:14 +00:00
|
|
|
// Title returns the instant answer heading
|
2021-12-08 17:24:05 +00:00
|
|
|
func (ddg *DDGCard) Title() string {
|
|
|
|
return ddg.resp.Heading
|
|
|
|
}
|
|
|
|
|
2021-12-08 21:18:14 +00:00
|
|
|
// Content returns the instant answer abstract with
|
|
|
|
// DuckDuckGo attibution
|
2021-12-08 17:24:05 +00:00
|
|
|
func (ddg *DDGCard) Content() template.HTML {
|
|
|
|
// Return abstract with attribution
|
|
|
|
return template.HTML(ddg.resp.Abstract + fmt.Sprintf(ddgAttribution, ddg.query))
|
|
|
|
}
|
|
|
|
|
2021-12-08 21:18:14 +00:00
|
|
|
// Footer returns a footer containing URL and name of source
|
|
|
|
// gotten from instant answer API
|
2021-12-08 17:24:05 +00:00
|
|
|
func (ddg *DDGCard) Footer() template.HTML {
|
|
|
|
// Return footer with abstract url and source
|
|
|
|
return template.HTML(fmt.Sprintf(
|
|
|
|
`<div class="card-footer"><a class="card-footer-item" href="%s">%s</a></div>`,
|
|
|
|
ddg.resp.AbstractURL,
|
|
|
|
ddg.resp.AbstractSource,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2021-12-08 21:18:14 +00:00
|
|
|
// Head returns an empty string as no extra tags are reuired
|
|
|
|
// for DDGCard
|
2021-12-08 17:24:05 +00:00
|
|
|
func (ddg *DDGCard) Head() template.HTML {
|
|
|
|
return ""
|
|
|
|
}
|