mirror of
https://github.com/karust/openserp.git
synced 2026-08-24 17:00:06 +08:00
180 lines
4.5 KiB
Go
180 lines
4.5 KiB
Go
package duckduckgo
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/karust/openserp/core"
|
|
)
|
|
|
|
const baseURL = "https://duckduckgo.com"
|
|
|
|
// ddgKLByLocale maps lowercase BCP47 codes ("en", "en-gb", "zh-tw") to
|
|
// DuckDuckGo's "kl" parameter, which uses an inverted region-language form
|
|
// (e.g. "uk-en"). Lookup is locale-first, then language-only as a fallback.
|
|
var ddgKLByLocale = map[string]string{
|
|
"en": "us-en",
|
|
"en-us": "us-en",
|
|
"en-gb": "uk-en",
|
|
"en-au": "au-en",
|
|
"en-ca": "ca-en",
|
|
"de": "de-de",
|
|
"de-at": "at-de",
|
|
"de-ch": "ch-de",
|
|
"fr": "fr-fr",
|
|
"fr-ca": "ca-fr",
|
|
"fr-be": "be-fr",
|
|
"fr-ch": "ch-fr",
|
|
"es": "es-es",
|
|
"es-mx": "mx-es",
|
|
"es-ar": "ar-es",
|
|
"it": "it-it",
|
|
"nl": "nl-nl",
|
|
"nl-be": "be-nl",
|
|
"pt": "pt-pt",
|
|
"pt-br": "br-pt",
|
|
"ru": "ru-ru",
|
|
"pl": "pl-pl",
|
|
"cs": "cz-cs",
|
|
"sk": "sk-sk",
|
|
"hu": "hu-hu",
|
|
"ro": "ro-ro",
|
|
"da": "dk-da",
|
|
"sv": "se-sv",
|
|
"no": "no-no",
|
|
"fi": "fi-fi",
|
|
"tr": "tr-tr",
|
|
"el": "gr-el",
|
|
"he": "il-he",
|
|
"ar": "xa-ar",
|
|
"zh": "cn-zh",
|
|
"zh-cn": "cn-zh",
|
|
"zh-tw": "tw-zh",
|
|
"ja": "jp-ja",
|
|
"ko": "kr-ko",
|
|
}
|
|
|
|
// duckDuckGoKL resolves a DuckDuckGo "kl" value for the supplied language code.
|
|
// Returns "" when the input has no known mapping so callers can omit the
|
|
// parameter rather than send an unrecognized region.
|
|
func duckDuckGoKL(langCode, region string) string {
|
|
locale := core.ParseLocale(langCode)
|
|
if locale.Language == "" {
|
|
return ""
|
|
}
|
|
|
|
country := core.CountryFromRegion(region)
|
|
if country == "" {
|
|
country = locale.Country
|
|
}
|
|
if country != "" {
|
|
key := locale.Language + "-" + strings.ToLower(country)
|
|
if kl, ok := ddgKLByLocale[key]; ok {
|
|
return kl
|
|
}
|
|
}
|
|
return ddgKLByLocale[locale.Language]
|
|
}
|
|
|
|
// addQueryText appends the "q" param, combining text with site/filetype
|
|
// operators. Returns an error when the resulting query is empty.
|
|
func addQueryText(params url.Values, q core.Query) error {
|
|
if q.Text == "" && q.Site == "" && q.Filetype == "" {
|
|
return errors.New("empty query built")
|
|
}
|
|
text := q.Text
|
|
if q.Site != "" {
|
|
text += " site:" + q.Site
|
|
}
|
|
if q.Filetype != "" {
|
|
text += " filetype:" + q.Filetype
|
|
}
|
|
params.Add("q", text)
|
|
return nil
|
|
}
|
|
|
|
// addDateRange appends the "df" param, converting the YYYYMMDD..YYYYMMDD
|
|
// interval to DuckDuckGo's YYYY-MM-DD..YYYY-MM-DD form. No-op when unset.
|
|
func addDateRange(params url.Values, interval string) error {
|
|
if interval == "" {
|
|
return nil
|
|
}
|
|
intervals := strings.Split(interval, "..")
|
|
if len(intervals) != 2 {
|
|
return errors.New("incorrect date interval provided")
|
|
}
|
|
start, err := time.Parse("20060102", intervals[0])
|
|
if err != nil {
|
|
return errors.New("invalid start date format, expected YYYYMMDD")
|
|
}
|
|
end, err := time.Parse("20060102", intervals[1])
|
|
if err != nil {
|
|
return errors.New("invalid end date format, expected YYYYMMDD")
|
|
}
|
|
params.Add("df", start.Format("2006-01-02")+".."+end.Format("2006-01-02"))
|
|
return nil
|
|
}
|
|
|
|
// buildParams assembles the query, date range, and locale params shared by web
|
|
// and image search.
|
|
func buildParams(q core.Query) (url.Values, error) {
|
|
params := url.Values{}
|
|
if err := addQueryText(params, q); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := addDateRange(params, q.DateInterval); err != nil {
|
|
return nil, err
|
|
}
|
|
if kl := duckDuckGoKL(q.LangCode, q.Region); kl != "" {
|
|
params.Add("kl", kl)
|
|
}
|
|
return params, nil
|
|
}
|
|
|
|
// BuildURL builds a DuckDuckGo web search URL for the provided query and page
|
|
// index. It returns an error when query text or date parameters are invalid.
|
|
func BuildURL(q core.Query, page int) (string, error) {
|
|
base, err := url.Parse(baseURL)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
params, err := buildParams(q)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
params.Add("t", "h") // HTML format
|
|
params.Add("ia", "web") // Web search
|
|
|
|
// Pagination uses 's' (start offset); ~25 results per page.
|
|
if page > 0 {
|
|
params.Add("s", fmt.Sprintf("%d", page*25))
|
|
}
|
|
|
|
base.RawQuery = params.Encode()
|
|
return base.String(), nil
|
|
}
|
|
|
|
// BuildImageURL builds a DuckDuckGo image search URL from Query fields.
|
|
// It returns an error when query text or date parameters are invalid.
|
|
func BuildImageURL(q core.Query) (string, error) {
|
|
base, err := url.Parse(baseURL)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
params, err := buildParams(q)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
params.Add("t", "h") // HTML format
|
|
params.Add("iax", "images") // Image search
|
|
params.Add("ia", "images")
|
|
|
|
base.RawQuery = params.Encode()
|
|
return base.String(), nil
|
|
}
|