From 860b09bcf886d4578f149caab4e81be54c55adc2 Mon Sep 17 00:00:00 2001 From: sundowndev Date: Sat, 5 Sep 2020 20:56:14 +0200 Subject: [PATCH] feat: implement duckduckgo search --- dorkgen.go | 9 +- dorkgen_test.go | 7 ++ duckduckgo/duckduckgo.go | 171 ++++++++++++++++++++++++++ duckduckgo/duckduckgo_test.go | 218 ++++++++++++++++++++++++++++++++++ 4 files changed, 404 insertions(+), 1 deletion(-) create mode 100644 duckduckgo/duckduckgo.go create mode 100644 duckduckgo/duckduckgo_test.go diff --git a/dorkgen.go b/dorkgen.go index da9295b..f022f85 100644 --- a/dorkgen.go +++ b/dorkgen.go @@ -1,7 +1,14 @@ package dorkgen -import "github.com/sundowndev/dorkgen/googlesearch" +import ( + "github.com/sundowndev/dorkgen/duckduckgo" + "github.com/sundowndev/dorkgen/googlesearch" +) func NewGoogleSearch() *googlesearch.GoogleSearch { return googlesearch.New() } + +func NewDuckDuckGo() *duckduckgo.DuckDuckGo { + return duckduckgo.New() +} diff --git a/dorkgen_test.go b/dorkgen_test.go index 30c336c..0499df3 100644 --- a/dorkgen_test.go +++ b/dorkgen_test.go @@ -2,6 +2,7 @@ package dorkgen import ( assertion "github.com/stretchr/testify/assert" + "github.com/sundowndev/dorkgen/duckduckgo" "github.com/sundowndev/dorkgen/googlesearch" "testing" ) @@ -14,4 +15,10 @@ func TestInit(t *testing.T) { assert.IsType(&googlesearch.GoogleSearch{}, dork, "they should be equal") }) + + t.Run("should create a DuckDuckGo instance", func(t *testing.T) { + dork := NewDuckDuckGo() + + assert.IsType(&duckduckgo.DuckDuckGo{}, dork, "they should be equal") + }) } diff --git a/duckduckgo/duckduckgo.go b/duckduckgo/duckduckgo.go new file mode 100644 index 0000000..a6b0404 --- /dev/null +++ b/duckduckgo/duckduckgo.go @@ -0,0 +1,171 @@ +package duckduckgo + +import ( + "net/url" + "strings" +) + +const ( + searchURL = "https://www.google.com/search" + siteTag = "site:" + urlTag = "inurl:" + filetypeTag = "filetype:" + extTag = "ext:" + excludeTag = "-" + intitleTag = "intitle:" + intextTag = "intext:" + operatorOr = "|" + operatorAnd = "+" + allInURLTag = "allinurl:" + locationTag = "region:" + feedTag = "feed:" + hasfeedTag = "hasfeed:" + languageTag = "language:" + allintitleTag = "allintitle:" +) + +// DuckDuckGo is the Google search implementation for Dorkgen +type DuckDuckGo struct { + tags []string +} + +// New creates a new instance of DuckDuckGo +func New() *DuckDuckGo { + return &DuckDuckGo{} +} + +func (d *DuckDuckGo) concat(tag string, value string, quotes bool) string { + if quotes { + return tag + "\"" + value + "\"" + } + + return tag + value +} + +// String converts all tags to a single request +func (d *DuckDuckGo) String() string { + return strings.Join(d.tags, " ") +} + +// QueryValues returns search request as URL values +func (d *DuckDuckGo) QueryValues() url.Values { + tags := strings.Join(d.tags, " ") + + params := url.Values{} + params.Add("q", tags) + + return params +} + +// URL converts tags to an encoded Google Search URL +func (d *DuckDuckGo) URL() string { + baseURL, _ := url.Parse(searchURL) + + baseURL.RawQuery = d.QueryValues().Encode() + + return baseURL.String() +} + +// Site specifically searches that particular site and lists all the results for that site. +func (d *DuckDuckGo) Site(site string) *DuckDuckGo { + d.tags = append(d.tags, d.concat(siteTag, site, false)) + return d +} + +// Or puts an OR operator in the request +func (d *DuckDuckGo) Or() *DuckDuckGo { + d.tags = append(d.tags, operatorOr) + return d +} + +// And puts an AND operator in the request +func (d *DuckDuckGo) And() *DuckDuckGo { + d.tags = append(d.tags, operatorAnd) + return d +} + +// Intext searches for the occurrences of keywords all at once or one at a time. +func (d *DuckDuckGo) Intext(text string) *DuckDuckGo { + d.tags = append(d.tags, d.concat(intextTag, text, true)) + return d +} + +// Inurl searches for a URL matching one of the keywords. +func (d *DuckDuckGo) Inurl(url string) *DuckDuckGo { + d.tags = append(d.tags, d.concat(urlTag, url, true)) + return d +} + +// Filetype searches for a particular filetype mentioned in the query. +func (d *DuckDuckGo) Filetype(filetype string) *DuckDuckGo { + d.tags = append(d.tags, d.concat(filetypeTag, filetype, true)) + return d +} + +// Ext searches for a particular file extension mentioned in the query. +func (d *DuckDuckGo) Ext(ext string) *DuckDuckGo { + d.tags = append(d.tags, d.concat(extTag, ext, false)) + return d +} + +// Exclude excludes some results. +func (d *DuckDuckGo) Exclude(tags *DuckDuckGo) *DuckDuckGo { + d.tags = append(d.tags, d.concat(excludeTag, tags.String(), false)) + return d +} + +// Group isolate tags between parentheses +func (d *DuckDuckGo) Group(tags *DuckDuckGo) *DuckDuckGo { + d.tags = append(d.tags, "("+tags.String()+")") + return d +} + +// Intitle searches for occurrences of keywords in title all or one. +func (d *DuckDuckGo) Intitle(value string) *DuckDuckGo { + d.tags = append(d.tags, d.concat(intitleTag, value, true)) + return d +} + +// Plain allows you to add additional values as string without any kind of formatting. +func (d *DuckDuckGo) Plain(value string) *DuckDuckGo { + d.tags = append(d.tags, value) + return d +} + +// AllInURL finds pages that include a specific keyword as part of their indexed URLs. +func (d *DuckDuckGo) AllInURL(value string) *DuckDuckGo { + d.tags = append(d.tags, d.concat(allInURLTag, value, true)) + return d +} + +// Location searches for specific region. +// An iso location code is a short code for a country for example, Egypt is eg and USA is us. +// https://en.wikipedia.org/wiki/ISO_3166-1 +func (d *DuckDuckGo) Location(isoCode string) *DuckDuckGo { + d.tags = append(d.tags, d.concat(locationTag, isoCode, false)) + return d +} + +// Feed finds RSS feed related to search term (i.e. rss). +func (d *DuckDuckGo) Feed(feed string) *DuckDuckGo { + d.tags = append(d.tags, d.concat(feedTag, feed, false)) + return d +} + +// HasFeed finds webpages that contain both the term or terms for which you are querying and one or more RSS or Atom feeds. +func (d *DuckDuckGo) HasFeed(url string) *DuckDuckGo { + d.tags = append(d.tags, d.concat(hasfeedTag, url, true)) + return d +} + +// Language returns websites that match the search term in a specified language. +func (d *DuckDuckGo) Language(lang string) *DuckDuckGo { + d.tags = append(d.tags, d.concat(languageTag, lang, false)) + return d +} + +// AllInTitle finds pages that include a specific keyword as part of the indexed title tag. +func (d *DuckDuckGo) AllInTitle(value string) *DuckDuckGo { + d.tags = append(d.tags, d.concat(allintitleTag, value, true)) + return d +} diff --git a/duckduckgo/duckduckgo_test.go b/duckduckgo/duckduckgo_test.go new file mode 100644 index 0000000..c888bfb --- /dev/null +++ b/duckduckgo/duckduckgo_test.go @@ -0,0 +1,218 @@ +package duckduckgo_test + +import ( + "fmt" + "github.com/sundowndev/dorkgen/duckduckgo" + "net/url" + "testing" + + assertion "github.com/stretchr/testify/assert" +) + +var dork *duckduckgo.DuckDuckGo + +func TestInit(t *testing.T) { + assert := assertion.New(t) + + t.Run("should convert to URL correctly", func(t *testing.T) { + dork = duckduckgo.New() + + result := dork. + Site("example.com"). + URL() + + assert.Equal(result, "https://www.google.com/search?q=site%3Aexample.com", "they should be equal") + }) + + t.Run("should convert to string correctly", func(t *testing.T) { + dork = duckduckgo.New() + + result := fmt.Sprint(dork.Site("example.com")) + + assert.Equal(result, "site:example.com", "they should be equal") + }) + + t.Run("should handle site tag correctly", func(t *testing.T) { + dork = duckduckgo.New() + + result := dork. + Site("example.com"). + String() + + assert.Equal(result, "site:example.com", "they should be equal") + }) + + t.Run("should handle intext tag correctly", func(t *testing.T) { + dork = duckduckgo.New() + + result := dork. + Intext("text"). + String() + + assert.Equal(result, "intext:\"text\"", "they should be equal") + }) + + t.Run("should handle inurl tag correctly", func(t *testing.T) { + dork = duckduckgo.New() + + result := dork. + Inurl("index.php"). + String() + + assert.Equal(result, "inurl:\"index.php\"", "they should be equal") + }) + + t.Run("should handle filetype tag correctly", func(t *testing.T) { + dork = duckduckgo.New() + + result := dork. + Filetype("pdf"). + String() + + assert.Equal(result, "filetype:\"pdf\"", "they should be equal") + }) + + //t.Run("should handle AllInURL tag correctly", func(t *testing.T) { + // dork = duckduckgo.New() + // + // result := dork. + // Cache("www.google.com"). + // String() + // + // assert.Equal(result, "cache:\"www.google.com\"", "they should be equal") + //}) + + //t.Run("should handle Location tag correctly", func(t *testing.T) { + // dork = duckduckgo.New() + // + // result := dork. + // Related("www.google.com"). + // String() + // + // assert.Equal(result, "related:\"www.google.com\"", "they should be equal") + //}) + + //t.Run("should handle Feed tag correctly", func(t *testing.T) { + // dork = duckduckgo.New() + // + // result := dork. + // Related("www.google.com"). + // String() + // + // assert.Equal(result, "related:\"www.google.com\"", "they should be equal") + //}) + + //t.Run("should handle HasFeed tag correctly", func(t *testing.T) { + // dork = duckduckgo.New() + // + // result := dork. + // Related("www.google.com"). + // String() + // + // assert.Equal(result, "related:\"www.google.com\"", "they should be equal") + //}) + + //t.Run("should handle Language tag correctly", func(t *testing.T) { + // dork = duckduckgo.New() + // + // result := dork. + // Related("www.google.com"). + // String() + // + // assert.Equal(result, "related:\"www.google.com\"", "they should be equal") + //}) + + //t.Run("should handle AllInTitle tag correctly", func(t *testing.T) { + // dork = duckduckgo.New() + // + // result := dork. + // Related("www.google.com"). + // String() + // + // assert.Equal(result, "related:\"www.google.com\"", "they should be equal") + //}) + + t.Run("should handle ext tag correctly", func(t *testing.T) { + dork = duckduckgo.New() + + result := dork. + Ext("(doc | pdf | xls | txt | xml)"). + String() + + assert.Equal(result, "ext:(doc | pdf | xls | txt | xml)", "they should be equal") + }) + + t.Run("should handle exclude tag correctly", func(t *testing.T) { + dork = duckduckgo.New() + + result := dork. + Exclude(duckduckgo.New().Plain("html")). + Exclude(duckduckgo.New().Plain("htm")). + Exclude(duckduckgo.New().Plain("php")). + Exclude(duckduckgo.New().Plain("md5sums")). + String() + + assert.Equal(result, "-html -htm -php -md5sums", "they should be equal") + }) + + t.Run("should handle 'OR' tag correctly", func(t *testing.T) { + dork = duckduckgo.New() + + result := dork. + Site("facebook.com"). + Or(). + Site("twitter.com"). + String() + + assert.Equal(result, "site:facebook.com | site:twitter.com", "they should be equal") + }) + + t.Run("should handle 'AND' tag correctly", func(t *testing.T) { + dork = duckduckgo.New() + + result := dork. + Intitle("facebook"). + And(). + Intitle("twitter"). + String() + + assert.Equal(result, "intitle:\"facebook\" + intitle:\"twitter\"", "they should be equal") + }) + + t.Run("should handle group tag correctly", func(t *testing.T) { + dork = duckduckgo.New() + + result := dork. + Site("linkedin.com"). + Group(duckduckgo.New().Intext("1").Or().Intext("2")). + String() + + assert.Equal(result, "site:linkedin.com (intext:\"1\" | intext:\"2\")", "they should be equal") + }) + + t.Run("should handle group tag correctly", func(t *testing.T) { + dork = duckduckgo.New() + + result := dork. + Site("linkedin.com"). + Group(duckduckgo.New().Intext("1").Or().Intext("2")). + Intitle("jordan"). + String() + + assert.Equal(result, "site:linkedin.com (intext:\"1\" | intext:\"2\") intitle:\"jordan\"", "they should be equal") + }) + + t.Run("should return URL values", func(t *testing.T) { + dork = duckduckgo.New() + + result := dork. + Site("linkedin.com"). + Group(duckduckgo.New().Intext("1").Or().Intext("2")). + Intitle("jordan"). + QueryValues() + + assert.Equal(url.Values{ + "q": []string{"site:linkedin.com (intext:\"1\" | intext:\"2\") intitle:\"jordan\""}, + }, result, "they should be equal") + }) +}