Fix golint errors and gofmt
This commit is contained in:
parent
c8bec472be
commit
254155d442
@ -44,6 +44,8 @@ func NewCalcCard(query, _ string) Card {
|
|||||||
return &CalcCard{query: query, useFunc: true}
|
return &CalcCard{query: query, useFunc: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Matches determines whether a query matches the requirements
|
||||||
|
// fot CalcCard and determines which function to run with what arguments
|
||||||
func (cc *CalcCard) Matches() bool {
|
func (cc *CalcCard) Matches() bool {
|
||||||
// If query has solve prefix
|
// If query has solve prefix
|
||||||
if strings.HasPrefix(cc.query, "solve") {
|
if strings.HasPrefix(cc.query, "solve") {
|
||||||
@ -106,6 +108,7 @@ func (cc *CalcCard) Matches() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StripKey removes all key words related to CalcCard
|
||||||
func (cc *CalcCard) StripKey() string {
|
func (cc *CalcCard) StripKey() string {
|
||||||
// Compile regex for words to be removed
|
// Compile regex for words to be removed
|
||||||
trimRgx := regexp.MustCompile(`^(.*?)solve|integrate|integral(?: of)?|diff|differentiate|derivative(?: of)?|derive|calculate(.*)$`)
|
trimRgx := regexp.MustCompile(`^(.*?)solve|integrate|integral(?: of)?|diff|differentiate|derivative(?: of)?|derive|calculate(.*)$`)
|
||||||
@ -113,6 +116,7 @@ func (cc *CalcCard) StripKey() string {
|
|||||||
return trimRgx.ReplaceAllString(cc.query, "${1}${2}")
|
return trimRgx.ReplaceAllString(cc.query, "${1}${2}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Content returns the solveRenderScript with the given input
|
||||||
func (cc *CalcCard) Content() template.HTML {
|
func (cc *CalcCard) Content() template.HTML {
|
||||||
var input string
|
var input string
|
||||||
// If function is being used
|
// If function is being used
|
||||||
@ -130,22 +134,29 @@ func (cc *CalcCard) Content() template.HTML {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Footer returns empty string because CalcCard has no footer
|
||||||
func (cc *CalcCard) Footer() template.HTML {
|
func (cc *CalcCard) Footer() template.HTML {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returned always returns true since CalcCard is a frontend
|
||||||
|
// card and it is thus impossible to determine whether
|
||||||
|
// the query gets an answer
|
||||||
func (cc *CalcCard) Returned() bool {
|
func (cc *CalcCard) Returned() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunQuery returns nil as CalcCard is a frontend card
|
||||||
func (cc *CalcCard) RunQuery() error {
|
func (cc *CalcCard) RunQuery() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Title for CalcCard is "Calculation"
|
||||||
func (cc *CalcCard) Title() string {
|
func (cc *CalcCard) Title() string {
|
||||||
return "Calculation"
|
return "Calculation"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Head returns the extra head tags required for CalcCard
|
||||||
func (cc *CalcCard) Head() template.HTML {
|
func (cc *CalcCard) Head() template.HTML {
|
||||||
return calcExtraHead
|
return calcExtraHead
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
package cards
|
package cards
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"sort"
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrCardRegistered = errors.New("card with that name has already been registered")
|
|
||||||
|
|
||||||
// cardRegistration represents a card that has been registered
|
// cardRegistration represents a card that has been registered
|
||||||
type cardRegistration struct {
|
type cardRegistration struct {
|
||||||
name string
|
name string
|
||||||
|
@ -39,7 +39,7 @@ type DDGInstAns struct {
|
|||||||
AnswerType string
|
AnswerType string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDDGCard isa NewCardFunc that creates a new DDGCard
|
// NewDDGCard is a NewCardFunc that creates a new DDGCard
|
||||||
func NewDDGCard(query, userAgent string) Card {
|
func NewDDGCard(query, userAgent string) Card {
|
||||||
return &DDGCard{
|
return &DDGCard{
|
||||||
query: url.QueryEscape(query),
|
query: url.QueryEscape(query),
|
||||||
@ -68,7 +68,7 @@ func (ddg *DDGCard) RunQuery() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode response into repsonse struct
|
// Decode response into response struct
|
||||||
err = json.NewDecoder(res.Body).Decode(&ddg.resp)
|
err = json.NewDecoder(res.Body).Decode(&ddg.resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -77,30 +77,38 @@ func (ddg *DDGCard) RunQuery() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returned checks if abstract is empty.
|
||||||
|
// If it is, query returned no result.
|
||||||
func (ddg *DDGCard) Returned() bool {
|
func (ddg *DDGCard) Returned() bool {
|
||||||
// Value was returned if abstract is not empty
|
// Value was returned if abstract is not empty
|
||||||
return ddg.resp.Abstract != ""
|
return ddg.resp.Abstract != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Matches always returns true as there are no keys
|
||||||
func (ddg *DDGCard) Matches() bool {
|
func (ddg *DDGCard) Matches() bool {
|
||||||
// Everything matches since there are no keys
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StripKey returns the query as there are no keys
|
||||||
func (ddg *DDGCard) StripKey() string {
|
func (ddg *DDGCard) StripKey() string {
|
||||||
// No key to strip, so return query
|
// No key to strip, so return query
|
||||||
return ddg.query
|
return ddg.query
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Title returns the instant answer heading
|
||||||
func (ddg *DDGCard) Title() string {
|
func (ddg *DDGCard) Title() string {
|
||||||
return ddg.resp.Heading
|
return ddg.resp.Heading
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Content returns the instant answer abstract with
|
||||||
|
// DuckDuckGo attibution
|
||||||
func (ddg *DDGCard) Content() template.HTML {
|
func (ddg *DDGCard) Content() template.HTML {
|
||||||
// Return abstract with attribution
|
// Return abstract with attribution
|
||||||
return template.HTML(ddg.resp.Abstract + fmt.Sprintf(ddgAttribution, ddg.query))
|
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 {
|
func (ddg *DDGCard) Footer() template.HTML {
|
||||||
// Return footer with abstract url and source
|
// Return footer with abstract url and source
|
||||||
return template.HTML(fmt.Sprintf(
|
return template.HTML(fmt.Sprintf(
|
||||||
@ -110,6 +118,8 @@ func (ddg *DDGCard) Footer() template.HTML {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Head returns an empty string as no extra tags are reuired
|
||||||
|
// for DDGCard
|
||||||
func (ddg *DDGCard) Head() template.HTML {
|
func (ddg *DDGCard) Head() template.HTML {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -166,16 +166,21 @@ func (mc *MetaweatherCard) RunQuery() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returned checks whether no location was found or response
|
||||||
|
// said not found.
|
||||||
func (mc *MetaweatherCard) Returned() bool {
|
func (mc *MetaweatherCard) Returned() bool {
|
||||||
return len(mc.location) > 0 && mc.resp.Detail != "Not found."
|
return len(mc.location) > 0 && mc.resp.Detail != "Not found."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Matches determines whether the query matches the keys for
|
||||||
|
// MetaweatherCard
|
||||||
func (mc *MetaweatherCard) Matches() bool {
|
func (mc *MetaweatherCard) Matches() bool {
|
||||||
return strings.HasPrefix(mc.query, "weather in") ||
|
return strings.HasPrefix(mc.query, "weather in") ||
|
||||||
strings.HasPrefix(mc.query, "weather") ||
|
strings.HasPrefix(mc.query, "weather") ||
|
||||||
strings.HasSuffix(mc.query, "weather")
|
strings.HasSuffix(mc.query, "weather")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StripKey removes keys related to MetaweatherCard
|
||||||
func (mc *MetaweatherCard) StripKey() string {
|
func (mc *MetaweatherCard) StripKey() string {
|
||||||
query := strings.TrimPrefix(mc.query, "weather in")
|
query := strings.TrimPrefix(mc.query, "weather in")
|
||||||
query = strings.TrimPrefix(query, "weather")
|
query = strings.TrimPrefix(query, "weather")
|
||||||
@ -183,6 +188,7 @@ func (mc *MetaweatherCard) StripKey() string {
|
|||||||
return strings.TrimSpace(query)
|
return strings.TrimSpace(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Content returns metaweatherContent with all information added
|
||||||
func (mc *MetaweatherCard) Content() template.HTML {
|
func (mc *MetaweatherCard) Content() template.HTML {
|
||||||
return template.HTML(fmt.Sprintf(
|
return template.HTML(fmt.Sprintf(
|
||||||
metaweatherContent,
|
metaweatherContent,
|
||||||
@ -213,15 +219,18 @@ func (mc *MetaweatherCard) Content() template.HTML {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Title of MetaweatherCard is "Weather"
|
||||||
func (mc *MetaweatherCard) Title() string {
|
func (mc *MetaweatherCard) Title() string {
|
||||||
return "Weather"
|
return "Weather"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Footer returns a footer with a link to metaweather
|
||||||
func (mc *MetaweatherCard) Footer() template.HTML {
|
func (mc *MetaweatherCard) Footer() template.HTML {
|
||||||
// Return footer with link to metaweather
|
|
||||||
return `<div class="card-footer"><a class="card-footer-item" href="https://www.metaweather.com/">Metaweather</a></div>`
|
return `<div class="card-footer"><a class="card-footer-item" href="https://www.metaweather.com/">Metaweather</a></div>`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Head returns an empty string as no extra head tags
|
||||||
|
// are required for MetaweatherCard
|
||||||
func (mc *MetaweatherCard) Head() template.HTML {
|
func (mc *MetaweatherCard) Head() template.HTML {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -43,12 +43,14 @@ func NewPlotCard(query, _ string) Card {
|
|||||||
return &PlotCard{query: query}
|
return &PlotCard{query: query}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Matches checks if the query matches the rules for PlotCard
|
||||||
func (pc *PlotCard) Matches() bool {
|
func (pc *PlotCard) Matches() bool {
|
||||||
return strings.HasPrefix(pc.query, "plot") ||
|
return strings.HasPrefix(pc.query, "plot") ||
|
||||||
strings.HasPrefix(pc.query, "graph") ||
|
strings.HasPrefix(pc.query, "graph") ||
|
||||||
strings.HasPrefix(pc.query, "draw")
|
strings.HasPrefix(pc.query, "draw")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StripKey removes all keys related to PlotCard
|
||||||
func (pc *PlotCard) StripKey() string {
|
func (pc *PlotCard) StripKey() string {
|
||||||
query := strings.TrimPrefix(pc.query, "plot")
|
query := strings.TrimPrefix(pc.query, "plot")
|
||||||
query = strings.TrimPrefix(query, "graph")
|
query = strings.TrimPrefix(query, "graph")
|
||||||
@ -56,6 +58,7 @@ func (pc *PlotCard) StripKey() string {
|
|||||||
return strings.TrimSpace(query)
|
return strings.TrimSpace(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Content returns plot script with given input
|
||||||
func (pc *PlotCard) Content() template.HTML {
|
func (pc *PlotCard) Content() template.HTML {
|
||||||
return template.HTML(fmt.Sprintf(
|
return template.HTML(fmt.Sprintf(
|
||||||
plotScript,
|
plotScript,
|
||||||
@ -63,27 +66,28 @@ func (pc *PlotCard) Content() template.HTML {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since this card is frontend, this cannot be checked.
|
// Returned will alwats return true because
|
||||||
// Therefore, it will always return true.
|
// this card is frontend, and this cannot be checked.
|
||||||
func (pc *PlotCard) Returned() bool {
|
func (pc *PlotCard) Returned() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Title generates a title formatted as "Plot (<eqation>)"
|
||||||
func (pc *PlotCard) Title() string {
|
func (pc *PlotCard) Title() string {
|
||||||
return "Plot (" + pc.StripKey() + ")"
|
return "Plot (" + pc.StripKey() + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Head returns extra head tags for PlotCard
|
||||||
func (pc *PlotCard) Head() template.HTML {
|
func (pc *PlotCard) Head() template.HTML {
|
||||||
return plotExtraHead
|
return plotExtraHead
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Footer returns an empty string as PlotCard has no footer
|
||||||
func (pc *PlotCard) Footer() template.HTML {
|
func (pc *PlotCard) Footer() template.HTML {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunQuery returns nil as PlotCard is a frontend card
|
||||||
func (pc *PlotCard) RunQuery() error {
|
func (pc *PlotCard) RunQuery() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
2
main.go
2
main.go
@ -66,6 +66,7 @@ type TmplContext struct {
|
|||||||
Card cards.Card
|
Card cards.Card
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrTmplContext represents context passed to an error template
|
||||||
type ErrTmplContext struct {
|
type ErrTmplContext struct {
|
||||||
BaseContext
|
BaseContext
|
||||||
Error string
|
Error string
|
||||||
@ -74,6 +75,7 @@ type ErrTmplContext struct {
|
|||||||
Status int
|
Status int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BaseContext is the common context between all templates
|
||||||
type BaseContext struct {
|
type BaseContext struct {
|
||||||
LoadTime time.Duration
|
LoadTime time.Duration
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
var bingURL = urlMustParse("https://www.bing.com/search?count=10")
|
var bingURL = urlMustParse("https://www.bing.com/search?count=10")
|
||||||
|
|
||||||
|
// Bing represents the Bing search engine
|
||||||
type Bing struct {
|
type Bing struct {
|
||||||
keyword string
|
keyword string
|
||||||
userAgent string
|
userAgent string
|
||||||
@ -18,29 +19,37 @@ type Bing struct {
|
|||||||
baseSel *goquery.Selection
|
baseSel *goquery.Selection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetKeyword sets the keyword for searching
|
||||||
func (b *Bing) SetKeyword(keyword string) {
|
func (b *Bing) SetKeyword(keyword string) {
|
||||||
b.keyword = keyword
|
b.keyword = keyword
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetPage sets the page number for searching
|
||||||
func (b *Bing) SetPage(page int) {
|
func (b *Bing) SetPage(page int) {
|
||||||
b.first = page * 10
|
b.first = page * 10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetUserAgent sets the user agent to use for the request
|
||||||
func (b *Bing) SetUserAgent(ua string) {
|
func (b *Bing) SetUserAgent(ua string) {
|
||||||
b.userAgent = ua
|
b.userAgent = ua
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init runs requests for Bing search engine
|
||||||
func (b *Bing) Init() error {
|
func (b *Bing) Init() error {
|
||||||
|
// Copy URL so it can be changed
|
||||||
initURL := copyURL(bingURL)
|
initURL := copyURL(bingURL)
|
||||||
query := initURL.Query()
|
query := initURL.Query()
|
||||||
|
// Set query
|
||||||
query.Set("q", b.keyword)
|
query.Set("q", b.keyword)
|
||||||
if b.first > 0 {
|
if b.first > 0 {
|
||||||
query.Set("first", strconv.Itoa(b.first))
|
query.Set("first", strconv.Itoa(b.first))
|
||||||
} else {
|
} else {
|
||||||
query.Set("first", "1")
|
query.Set("first", "1")
|
||||||
}
|
}
|
||||||
|
// Update URL query parameters
|
||||||
initURL.RawQuery = query.Encode()
|
initURL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
// Create new request for modified URL
|
||||||
req, err := http.NewRequest(
|
req, err := http.NewRequest(
|
||||||
http.MethodGet,
|
http.MethodGet,
|
||||||
initURL.String(),
|
initURL.String(),
|
||||||
@ -49,17 +58,21 @@ func (b *Bing) Init() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// If no user agent, use default
|
||||||
if b.userAgent == "" {
|
if b.userAgent == "" {
|
||||||
b.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"
|
b.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"
|
||||||
}
|
}
|
||||||
|
// Set request user agent
|
||||||
req.Header.Set("User-Agent", b.userAgent)
|
req.Header.Set("User-Agent", b.userAgent)
|
||||||
|
|
||||||
|
// Perform request
|
||||||
res, err := http.DefaultClient.Do(req)
|
res, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
// Create new goquery document
|
||||||
doc, err := goquery.NewDocumentFromReader(res.Body)
|
doc, err := goquery.NewDocumentFromReader(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -70,6 +83,7 @@ func (b *Bing) Init() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Each runs eachCb with the index of each search result
|
||||||
func (b *Bing) Each(eachCb func(int) error) error {
|
func (b *Bing) Each(eachCb func(int) error) error {
|
||||||
for i := 0; i < b.baseSel.Length(); i++ {
|
for i := 0; i < b.baseSel.Length(); i++ {
|
||||||
err := eachCb(i)
|
err := eachCb(i)
|
||||||
@ -80,18 +94,22 @@ func (b *Bing) Each(eachCb func(int) error) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Title returns the title of the search result corresponding to i
|
||||||
func (b *Bing) Title(i int) (string, error) {
|
func (b *Bing) Title(i int) (string, error) {
|
||||||
return get(b.baseSel, i).ChildrenFiltered("h2").Children().First().Text(), nil
|
return get(b.baseSel, i).ChildrenFiltered("h2").Children().First().Text(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Link returns the link to the search result corresponding to i
|
||||||
func (b *Bing) Link(i int) (string, error) {
|
func (b *Bing) Link(i int) (string, error) {
|
||||||
return get(b.baseSel, i).ChildrenFiltered("h2").Children().First().AttrOr("href", ""), nil
|
return get(b.baseSel, i).ChildrenFiltered("h2").Children().First().AttrOr("href", ""), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Desc returns the description of the search result corresponding to i
|
||||||
func (b *Bing) Desc(i int) (string, error) {
|
func (b *Bing) Desc(i int) (string, error) {
|
||||||
return get(b.baseSel, i).ChildrenFiltered(".b_caption").Children().Last().Text(), nil
|
return get(b.baseSel, i).ChildrenFiltered(".b_caption").Children().Last().Text(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns "bing"
|
||||||
func (b *Bing) Name() string {
|
func (b *Bing) Name() string {
|
||||||
return "bing"
|
return "bing"
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ var ddgURL = urlMustParse("https://html.duckduckgo.com/html")
|
|||||||
|
|
||||||
const uddgPrefix = "//duckduckgo.com/l/?uddg="
|
const uddgPrefix = "//duckduckgo.com/l/?uddg="
|
||||||
|
|
||||||
|
// DDG represents the DuckDuckGo search engine
|
||||||
type DDG struct {
|
type DDG struct {
|
||||||
keyword string
|
keyword string
|
||||||
userAgent string
|
userAgent string
|
||||||
@ -21,28 +22,37 @@ type DDG struct {
|
|||||||
baseSel *goquery.Selection
|
baseSel *goquery.Selection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetKeyword sets the keyword for searching
|
||||||
func (d *DDG) SetKeyword(keyword string) {
|
func (d *DDG) SetKeyword(keyword string) {
|
||||||
d.keyword = keyword
|
d.keyword = keyword
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetPage sets the page number for searching
|
||||||
func (d *DDG) SetPage(page int) {
|
func (d *DDG) SetPage(page int) {
|
||||||
d.page = page * 30
|
d.page = page * 30
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetUserAgent sets the user agent for the request
|
||||||
func (d *DDG) SetUserAgent(ua string) {
|
func (d *DDG) SetUserAgent(ua string) {
|
||||||
d.userAgent = "Opera/9.80 (Windows NT 5.1; U; zh-tw) Presto/2.8.131 Version/11.10" //ua
|
d.userAgent = ua
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init runs requests for the DuckDuckGo search engine
|
||||||
func (d *DDG) Init() error {
|
func (d *DDG) Init() error {
|
||||||
|
// Copy URL so that it can be changed
|
||||||
initURL := copyURL(ddgURL)
|
initURL := copyURL(ddgURL)
|
||||||
|
// Get query parameters
|
||||||
query := initURL.Query()
|
query := initURL.Query()
|
||||||
|
// Set query
|
||||||
query.Set("q", d.keyword)
|
query.Set("q", d.keyword)
|
||||||
if d.page > 0 {
|
if d.page > 0 {
|
||||||
query.Set("s", strconv.Itoa(d.page))
|
query.Set("s", strconv.Itoa(d.page))
|
||||||
query.Set("dc", strconv.Itoa(d.page+1))
|
query.Set("dc", strconv.Itoa(d.page+1))
|
||||||
}
|
}
|
||||||
|
// Update URL query
|
||||||
initURL.RawQuery = query.Encode()
|
initURL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
// Create new request for modified URL
|
||||||
req, err := http.NewRequest(
|
req, err := http.NewRequest(
|
||||||
http.MethodGet,
|
http.MethodGet,
|
||||||
initURL.String(),
|
initURL.String(),
|
||||||
@ -51,17 +61,21 @@ func (d *DDG) Init() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// If user agent empty, use default
|
||||||
if d.userAgent == "" {
|
if d.userAgent == "" {
|
||||||
d.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"
|
d.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"
|
||||||
}
|
}
|
||||||
|
// Set user agent of request
|
||||||
req.Header.Set("User-Agent", d.userAgent)
|
req.Header.Set("User-Agent", d.userAgent)
|
||||||
|
|
||||||
|
// Perform request
|
||||||
res, err := http.DefaultClient.Do(req)
|
res, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
// Create goquery document from reader
|
||||||
doc, err := goquery.NewDocumentFromReader(res.Body)
|
doc, err := goquery.NewDocumentFromReader(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -72,6 +86,7 @@ func (d *DDG) Init() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Each runs eachCb with the index of each search result
|
||||||
func (d *DDG) Each(eachCb func(int) error) error {
|
func (d *DDG) Each(eachCb func(int) error) error {
|
||||||
for i := 0; i < d.baseSel.Length(); i++ {
|
for i := 0; i < d.baseSel.Length(); i++ {
|
||||||
err := eachCb(i)
|
err := eachCb(i)
|
||||||
@ -82,10 +97,12 @@ func (d *DDG) Each(eachCb func(int) error) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Title returns the title of the search result corresponding to i
|
||||||
func (d *DDG) Title(i int) (string, error) {
|
func (d *DDG) Title(i int) (string, error) {
|
||||||
return strings.TrimSpace(get(d.baseSel, i).Children().First().ChildrenFiltered("h2").Text()), nil
|
return strings.TrimSpace(get(d.baseSel, i).Children().First().ChildrenFiltered("h2").Text()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Link returns the link to the search result corresponding to i
|
||||||
func (d *DDG) Link(i int) (string, error) {
|
func (d *DDG) Link(i int) (string, error) {
|
||||||
link := get(d.baseSel, i).Children().First().ChildrenFiltered("a").AttrOr("href", "")
|
link := get(d.baseSel, i).Children().First().ChildrenFiltered("a").AttrOr("href", "")
|
||||||
if strings.HasPrefix(link, uddgPrefix) {
|
if strings.HasPrefix(link, uddgPrefix) {
|
||||||
@ -94,10 +111,12 @@ func (d *DDG) Link(i int) (string, error) {
|
|||||||
return link, nil
|
return link, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Desc returns the description of the search result corresponding to i
|
||||||
func (d *DDG) Desc(i int) (string, error) {
|
func (d *DDG) Desc(i int) (string, error) {
|
||||||
return get(d.baseSel, i).Children().First().ChildrenFiltered("a").Text(), nil
|
return get(d.baseSel, i).Children().First().ChildrenFiltered("a").Text(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns "ddg"
|
||||||
func (d *DDG) Name() string {
|
func (d *DDG) Name() string {
|
||||||
return "ddg"
|
return "ddg"
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
var googleURL = urlMustParse("https://www.google.com/search")
|
var googleURL = urlMustParse("https://www.google.com/search")
|
||||||
|
|
||||||
|
// Google represents the Google search engine
|
||||||
type Google struct {
|
type Google struct {
|
||||||
keyword string
|
keyword string
|
||||||
userAgent string
|
userAgent string
|
||||||
@ -19,24 +20,35 @@ type Google struct {
|
|||||||
baseSel *goquery.Selection
|
baseSel *goquery.Selection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetKeyword sets the keyword for searching
|
||||||
func (g *Google) SetKeyword(keyword string) {
|
func (g *Google) SetKeyword(keyword string) {
|
||||||
g.keyword = keyword
|
g.keyword = keyword
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetPage sets the page number for searching
|
||||||
func (g *Google) SetPage(page int) {
|
func (g *Google) SetPage(page int) {
|
||||||
g.page = page * 10
|
g.page = page * 10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetUserAgent sets the user agent for the request
|
||||||
func (g *Google) SetUserAgent(ua string) {
|
func (g *Google) SetUserAgent(ua string) {
|
||||||
g.userAgent = ua
|
g.userAgent = ua
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init runs requests for the Google search engine
|
||||||
func (g *Google) Init() error {
|
func (g *Google) Init() error {
|
||||||
|
// Copy URL so that it can be changed
|
||||||
initURL := copyURL(googleURL)
|
initURL := copyURL(googleURL)
|
||||||
|
// Get query parameters
|
||||||
query := initURL.Query()
|
query := initURL.Query()
|
||||||
|
// Set query
|
||||||
query.Set("q", g.keyword)
|
query.Set("q", g.keyword)
|
||||||
|
// Set starting result (page number)
|
||||||
query.Set("start", strconv.Itoa(g.page))
|
query.Set("start", strconv.Itoa(g.page))
|
||||||
|
// Update URL query
|
||||||
initURL.RawQuery = query.Encode()
|
initURL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
// Create new request for modified URL
|
||||||
req, err := http.NewRequest(
|
req, err := http.NewRequest(
|
||||||
http.MethodGet,
|
http.MethodGet,
|
||||||
initURL.String(),
|
initURL.String(),
|
||||||
@ -45,17 +57,21 @@ func (g *Google) Init() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// If user agent empty, use default
|
||||||
if g.userAgent == "" {
|
if g.userAgent == "" {
|
||||||
g.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"
|
g.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"
|
||||||
}
|
}
|
||||||
|
// Set user agent of request
|
||||||
req.Header.Set("User-Agent", g.userAgent)
|
req.Header.Set("User-Agent", g.userAgent)
|
||||||
|
|
||||||
|
// Perform request
|
||||||
res, err := http.DefaultClient.Do(req)
|
res, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
// Create goquery document from reader
|
||||||
doc, err := goquery.NewDocumentFromReader(res.Body)
|
doc, err := goquery.NewDocumentFromReader(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -66,6 +82,7 @@ func (g *Google) Init() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Each runs eachCb with the index of each search result
|
||||||
func (g *Google) Each(eachCb func(int) error) error {
|
func (g *Google) Each(eachCb func(int) error) error {
|
||||||
for i := 0; i < g.baseSel.Length(); i++ {
|
for i := 0; i < g.baseSel.Length(); i++ {
|
||||||
err := eachCb(i)
|
err := eachCb(i)
|
||||||
@ -76,31 +93,38 @@ func (g *Google) Each(eachCb func(int) error) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Title returns the title of the search result corresponding to i
|
||||||
func (g *Google) Title(i int) (string, error) {
|
func (g *Google) Title(i int) (string, error) {
|
||||||
return get(g.baseSel, i).Text(), nil
|
return get(g.baseSel, i).Text(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Link returns the link to the search result corresponding to i
|
||||||
func (g *Google) Link(i int) (string, error) {
|
func (g *Google) Link(i int) (string, error) {
|
||||||
return get(g.baseSel, i).Parent().AttrOr("href", ""), nil
|
return get(g.baseSel, i).Parent().AttrOr("href", ""), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Desc returns the description of the search result corresponding to i
|
||||||
func (g *Google) Desc(i int) (string, error) {
|
func (g *Google) Desc(i int) (string, error) {
|
||||||
return get(g.baseSel, i).Parent().Parent().Next().Text(), nil
|
return get(g.baseSel, i).Parent().Parent().Next().Text(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns "google"
|
||||||
func (g *Google) Name() string {
|
func (g *Google) Name() string {
|
||||||
return "google"
|
return "google"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get gets an element and given index from given selection
|
||||||
func get(sel *goquery.Selection, i int) *goquery.Selection {
|
func get(sel *goquery.Selection, i int) *goquery.Selection {
|
||||||
return sel.Slice(i, i+1)
|
return sel.Slice(i, i+1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse url ignoring error
|
||||||
func urlMustParse(urlStr string) *url.URL {
|
func urlMustParse(urlStr string) *url.URL {
|
||||||
out, _ := url.Parse(urlStr)
|
out, _ := url.Parse(urlStr)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copyURL makes a copy of the url and returns it
|
||||||
func copyURL(orig *url.URL) *url.URL {
|
func copyURL(orig *url.URL) *url.URL {
|
||||||
newURL := new(url.URL)
|
newURL := new(url.URL)
|
||||||
*newURL = *orig
|
*newURL = *orig
|
||||||
|
@ -13,6 +13,7 @@ func init() {
|
|||||||
http.DefaultClient.Timeout = 5 * time.Second
|
http.DefaultClient.Timeout = 5 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Result represents a search result
|
||||||
type Result struct {
|
type Result struct {
|
||||||
Title string
|
Title string
|
||||||
Link string
|
Link string
|
||||||
@ -21,6 +22,7 @@ type Result struct {
|
|||||||
Rank int
|
Rank int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Engine represents a search engine for web results (not images, shopping, erc.)
|
||||||
type Engine interface {
|
type Engine interface {
|
||||||
// Set search keyword for engine
|
// Set search keyword for engine
|
||||||
SetKeyword(string)
|
SetKeyword(string)
|
||||||
@ -51,74 +53,95 @@ type Engine interface {
|
|||||||
Name() string
|
Name() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Options represents search options
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Keyword string
|
Keyword string
|
||||||
UserAgent string
|
UserAgent string
|
||||||
Page int
|
Page int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search searches the given engines concurrently and returns the results
|
||||||
func Search(opts Options, engines ...Engine) ([]*Result, error) {
|
func Search(opts Options, engines ...Engine) ([]*Result, error) {
|
||||||
var outMtx sync.Mutex
|
var outMtx sync.Mutex
|
||||||
var out []*Result
|
var out []*Result
|
||||||
|
|
||||||
|
// Create new error group
|
||||||
wg := errgroup.Group{}
|
wg := errgroup.Group{}
|
||||||
|
// For every engine
|
||||||
for index, engine := range engines {
|
for index, engine := range engines {
|
||||||
|
// Copy index and engine (for goroutine)
|
||||||
curIndex, curEngine := index, engine
|
curIndex, curEngine := index, engine
|
||||||
wg.Go(func() error {
|
wg.Go(func() error {
|
||||||
|
// Set options
|
||||||
curEngine.SetKeyword(opts.Keyword)
|
curEngine.SetKeyword(opts.Keyword)
|
||||||
curEngine.SetUserAgent(opts.UserAgent)
|
curEngine.SetUserAgent(opts.UserAgent)
|
||||||
curEngine.SetPage(opts.Page)
|
curEngine.SetPage(opts.Page)
|
||||||
|
|
||||||
|
// Attempt to init engine
|
||||||
if err := curEngine.Init(); err != nil {
|
if err := curEngine.Init(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For each result
|
||||||
err := curEngine.Each(func(i int) error {
|
err := curEngine.Each(func(i int) error {
|
||||||
|
// Get result link
|
||||||
link, err := curEngine.Link(i)
|
link, err := curEngine.Link(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate result rank
|
||||||
rank := (curIndex * 100) + i
|
rank := (curIndex * 100) + i
|
||||||
|
|
||||||
|
// Check if result exists
|
||||||
index, exists := linkExists(out, link)
|
index, exists := linkExists(out, link)
|
||||||
|
// If result already exists
|
||||||
if exists {
|
if exists {
|
||||||
|
// Add engine to the existing result
|
||||||
out[index].Engines = append(out[index].Engines, curEngine.Name())
|
out[index].Engines = append(out[index].Engines, curEngine.Name())
|
||||||
|
// If the rank is higher than the old one, update it
|
||||||
if rank < out[index].Rank {
|
if rank < out[index].Rank {
|
||||||
out[index].Rank = rank
|
out[index].Rank = rank
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get result title
|
||||||
title, err := curEngine.Title(i)
|
title, err := curEngine.Title(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get result description
|
||||||
desc, err := curEngine.Desc(i)
|
desc, err := curEngine.Desc(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If title, link, or description empty, ignore
|
||||||
if title == "" || link == "" || desc == "" {
|
if title == "" || link == "" || desc == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If length of description, truncate
|
||||||
if len(desc) > 500 {
|
if len(desc) > 500 {
|
||||||
desc = desc[:500] + "..."
|
desc = desc[:500] + "..."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create result struct
|
||||||
result := &Result{
|
result := &Result{
|
||||||
Title: title,
|
Title: title,
|
||||||
Link: link,
|
Link: link,
|
||||||
Desc: desc,
|
Desc: desc,
|
||||||
Rank: rank,
|
Rank: rank,
|
||||||
|
Engines: []string{curEngine.Name()},
|
||||||
}
|
}
|
||||||
result.Engines = append(result.Engines, curEngine.Name())
|
|
||||||
|
|
||||||
|
// Lock out mutex
|
||||||
outMtx.Lock()
|
outMtx.Lock()
|
||||||
|
// Add result to slice
|
||||||
out = append(out, result)
|
out = append(out, result)
|
||||||
|
// Unlock out mutex
|
||||||
outMtx.Unlock()
|
outMtx.Unlock()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -127,6 +150,7 @@ func Search(opts Options, engines ...Engine) ([]*Result, error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort slice by rank
|
||||||
sort.Slice(out, func(i, j int) bool {
|
sort.Slice(out, func(i, j int) bool {
|
||||||
return out[i].Rank < out[j].Rank
|
return out[i].Rank < out[j].Rank
|
||||||
})
|
})
|
||||||
@ -134,6 +158,7 @@ func Search(opts Options, engines ...Engine) ([]*Result, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for error group
|
||||||
if err := wg.Wait(); err != nil {
|
if err := wg.Wait(); err != nil {
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
@ -141,9 +166,13 @@ func Search(opts Options, engines ...Engine) ([]*Result, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// linkExists checks if a link exists in the results
|
||||||
func linkExists(results []*Result, link string) (int, bool) {
|
func linkExists(results []*Result, link string) (int, bool) {
|
||||||
|
// For every result
|
||||||
for index, result := range results {
|
for index, result := range results {
|
||||||
|
// If link is the same as provided
|
||||||
if result.Link == link {
|
if result.Link == link {
|
||||||
|
// Return index with true
|
||||||
return index, true
|
return index, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user