scope/internal/cards/ddg.go

144 lines
3.6 KiB
Go

/*
* 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/>.
*/
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>&nbsp;
Results from
<a href="https://duckduckgo.com/%s">DuckDuckGo &raquo;</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
}
// 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(
`<div class="card-footer"><a class="card-footer-item" href="%s">%s</a></div>`,
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 ""
}