├── .gitignore ├── LICENSE ├── README.md ├── duckduckgo ├── crawl.go └── crawl_test.go ├── go.mod ├── go.sum ├── main.go ├── typings └── types.go └── utils └── utils.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Antonio Cheong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DuckDuckGo-API 2 | API server and module for DuckDuckGo 3 | 4 | ```go 5 | type Search struct { 6 | Query string `json:"query"` 7 | Region string `json:"region"` 8 | TimeRange string `json:"time_range"` 9 | Limit int `json:"limit"` 10 | } 11 | 12 | type Result struct { 13 | Title string `json:"title"` 14 | Link string `json:"link"` 15 | Snippet string `json:"snippet"` 16 | } 17 | ``` 18 | 19 | Send requests via GET or POST in JSON 20 | -------------------------------------------------------------------------------- /duckduckgo/crawl.go: -------------------------------------------------------------------------------- 1 | package duckduckgo 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "net/http" 7 | "net/url" 8 | "strconv" 9 | 10 | "github.com/acheong08/DuckDuckGo-API/typings" 11 | "github.com/acheong08/DuckDuckGo-API/utils" 12 | "github.com/anaskhan96/soup" 13 | ) 14 | 15 | func get_html(search typings.Search) (string, error) { 16 | var base_url string = "html.duckduckgo.com" 17 | // POST form data 18 | var formdata = map[string]string{ 19 | "q": search.Query, 20 | "df": search.TimeRange, 21 | "kl": search.Region, 22 | } 23 | // URL encode form data 24 | var form string = utils.Url_encode(formdata) 25 | // Create POST request 26 | var request = http.Request{ 27 | Method: "POST", 28 | URL: &url.URL{ 29 | Host: base_url, 30 | Path: "/html/", 31 | Scheme: "https", 32 | }, 33 | Header: map[string][]string{ 34 | "Content-Type": {"application/x-www-form-urlencoded"}, 35 | "Accept": {"text/html"}, 36 | "User-Agent": {"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0"}, 37 | }, 38 | Body: utils.StringToReadCloser(form), 39 | } 40 | // Send POST request 41 | var client = http.Client{} 42 | var response, err = client.Do(&request) 43 | if err != nil { 44 | return "", err 45 | } 46 | // Read response body 47 | bodyBytes, err := io.ReadAll(response.Body) 48 | if err != nil { 49 | return "", err 50 | } 51 | // Check status code 52 | if response.StatusCode != 200 { 53 | return "", errors.New("Status code: " + strconv.Itoa(response.StatusCode) + " Body: " + string(bodyBytes)) 54 | } 55 | // Close response body 56 | err = response.Body.Close() 57 | if err != nil { 58 | return "", err 59 | } 60 | return string(bodyBytes), nil 61 | } 62 | 63 | func parse_html(html string) ([]typings.Result, error) { 64 | // Results is an array of Result structs 65 | var final_results []typings.Result = []typings.Result{} 66 | // Parse 67 | doc := soup.HTMLParse(html) 68 | // Find each result__body 69 | result_bodies := doc.FindAll("div", "class", "result__body") 70 | // Loop through each result__body 71 | for _, item := range result_bodies { 72 | // Get text of result__title 73 | var title string = item.Find("a", "class", "result__a").FullText() 74 | // Get href of result__a 75 | var link string = item.Find("a", "class", "result__a").Attrs()["href"] 76 | // Get text of result__snippet 77 | var snippet string = item.Find("a", "class", "result__snippet").FullText() 78 | // Append to final_results 79 | final_results = append(final_results, typings.Result{ 80 | Title: title, 81 | Link: link, 82 | Snippet: snippet, 83 | }) 84 | } 85 | return final_results, nil 86 | } 87 | 88 | func Get_results(search typings.Search) ([]typings.Result, error) { 89 | html, err := get_html(search) 90 | if err != nil { 91 | return nil, err 92 | } 93 | results, err := parse_html(html) 94 | if err != nil { 95 | return nil, err 96 | } 97 | return results, nil 98 | } 99 | -------------------------------------------------------------------------------- /duckduckgo/crawl_test.go: -------------------------------------------------------------------------------- 1 | package duckduckgo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/acheong08/DuckDuckGo-API/duckduckgo" 7 | "github.com/acheong08/DuckDuckGo-API/typings" 8 | ) 9 | 10 | func TestGet_html(t *testing.T) { 11 | search := typings.Search{ 12 | Query: "test", 13 | Region: "", 14 | TimeRange: "", 15 | } 16 | response, err := duckduckgo.Get_results(search) 17 | if err != nil { 18 | t.Errorf("Error: %s", err) 19 | } 20 | t.Log(response) 21 | } 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/acheong08/DuckDuckGo-API 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/acheong08/endless v0.0.0-20230529075213-74050cf641c8 7 | github.com/gin-gonic/gin v1.9.0 8 | ) 9 | 10 | require ( 11 | github.com/anaskhan96/soup v1.2.5 12 | github.com/bytedance/sonic v1.8.0 // indirect 13 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 14 | github.com/gin-contrib/sse v0.1.0 // indirect 15 | github.com/go-playground/locales v0.14.1 // indirect 16 | github.com/go-playground/universal-translator v0.18.1 // indirect 17 | github.com/go-playground/validator/v10 v10.11.2 // indirect 18 | github.com/goccy/go-json v0.10.0 // indirect 19 | github.com/json-iterator/go v1.1.12 // indirect 20 | github.com/klauspost/cpuid/v2 v2.0.9 // indirect 21 | github.com/leodido/go-urn v1.2.1 // indirect 22 | github.com/mattn/go-isatty v0.0.17 // indirect 23 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect 24 | github.com/modern-go/reflect2 v1.0.2 // indirect 25 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect 26 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 27 | github.com/ugorji/go/codec v1.2.9 // indirect 28 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect 29 | golang.org/x/crypto v0.5.0 // indirect 30 | golang.org/x/net v0.7.0 // indirect 31 | golang.org/x/sys v0.5.0 // indirect 32 | golang.org/x/text v0.7.0 // indirect 33 | google.golang.org/protobuf v1.28.1 // indirect 34 | gopkg.in/yaml.v3 v3.0.1 // indirect 35 | ) 36 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/acheong08/endless v0.0.0-20230529075213-74050cf641c8 h1:mHtMoGlGNUfMRjsWcb5Kvd1mJfJG8Gr1TtIghE8iiN8= 2 | github.com/acheong08/endless v0.0.0-20230529075213-74050cf641c8/go.mod h1:0yO7neMeJLvKk/B/fq5votDY8rByrOPDubpvU+6saKo= 3 | github.com/anaskhan96/soup v1.2.5 h1:V/FHiusdTrPrdF4iA1YkVxsOpdNcgvqT1hG+YtcZ5hM= 4 | github.com/anaskhan96/soup v1.2.5/go.mod h1:6YnEp9A2yywlYdM4EgDz9NEHclocMepEtku7wg6Cq3s= 5 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 6 | github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= 7 | github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 8 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 9 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 10 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 11 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 13 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 15 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 16 | github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= 17 | github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= 18 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 19 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 20 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 21 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 22 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 23 | github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= 24 | github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= 25 | github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= 26 | github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 27 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 28 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 29 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 30 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 31 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 32 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 33 | github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= 34 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 35 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 36 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 37 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 38 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 39 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 40 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 41 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 42 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 43 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 44 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 45 | github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= 46 | github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= 47 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 48 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 49 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 50 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 51 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 52 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 53 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 54 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 55 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 56 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 57 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 58 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 59 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 60 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 61 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 62 | github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= 63 | github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 64 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= 65 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 66 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 67 | golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= 68 | golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= 69 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 70 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 71 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 72 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 73 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 74 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 75 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 76 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 77 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 78 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 79 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 80 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 81 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 82 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 83 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 84 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 85 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 86 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 87 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 88 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 89 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 90 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | 7 | "github.com/acheong08/DuckDuckGo-API/duckduckgo" 8 | "github.com/acheong08/DuckDuckGo-API/typings" 9 | "github.com/acheong08/endless" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | func main() { 14 | HOST := os.Getenv("HOST") 15 | PORT := os.Getenv("PORT") 16 | if PORT == "" { 17 | PORT = "8080" 18 | } 19 | handler := gin.Default() 20 | handler.GET("/ping", func(c *gin.Context) { 21 | c.String(200, "pong") 22 | }) 23 | handler.POST("/search", func(ctx *gin.Context) { 24 | // Map request to Search struct 25 | var search typings.Search 26 | if err := ctx.ShouldBindJSON(&search); err != nil { 27 | ctx.JSON(400, gin.H{"error": err.Error(), "details": "Could not bind JSON"}) 28 | return 29 | } 30 | // Ensure query is set 31 | if search.Query == "" { 32 | ctx.JSON(400, gin.H{"error": "Query is required"}) 33 | return 34 | } 35 | // Get results 36 | results, err := duckduckgo.Get_results(search) 37 | if err != nil { 38 | ctx.JSON(500, gin.H{"error": err.Error()}) 39 | return 40 | } 41 | // Limit 42 | if search.Limit > 0 && search.Limit < len(results) { 43 | results = results[:search.Limit] 44 | } 45 | // Return results 46 | ctx.JSON(200, results) 47 | }) 48 | handler.GET("/search", func(ctx *gin.Context) { 49 | // Map request to Search struct 50 | var search typings.Search 51 | // Get query 52 | search.Query = ctx.Query("query") 53 | // Get region 54 | search.Region = ctx.Query("region") 55 | // Get time range 56 | search.TimeRange = ctx.Query("time_range") 57 | if search.Query == "" { 58 | ctx.JSON(400, gin.H{"error": "Query is required"}) 59 | return 60 | } 61 | // Get limit and check if it's a number 62 | limit := ctx.Query("limit") 63 | if limit != "" { 64 | if _, err := strconv.Atoi(limit); err != nil { 65 | ctx.JSON(400, gin.H{"error": "Limit must be a number"}) 66 | return 67 | } 68 | search.Limit, _ = strconv.Atoi(limit) 69 | } 70 | // Get results 71 | results, err := duckduckgo.Get_results(search) 72 | if err != nil { 73 | ctx.JSON(500, gin.H{"error": err.Error()}) 74 | return 75 | } 76 | // Shorten results to limit if limit is set 77 | if search.Limit > 0 && search.Limit < len(results) { 78 | results = results[:search.Limit] 79 | } 80 | // Return results 81 | ctx.JSON(200, results) 82 | }) 83 | 84 | endless.ListenAndServe(HOST+":"+PORT, handler) 85 | } 86 | -------------------------------------------------------------------------------- /typings/types.go: -------------------------------------------------------------------------------- 1 | package typings 2 | 3 | type Search struct { 4 | Query string `json:"query"` 5 | Region string `json:"region"` 6 | TimeRange string `json:"time_range"` 7 | Limit int `json:"limit"` 8 | } 9 | 10 | type Result struct { 11 | Title string `json:"title"` 12 | Link string `json:"link"` 13 | Snippet string `json:"snippet"` 14 | } 15 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "io" 5 | url "net/url" 6 | ) 7 | 8 | // URL encode form data 9 | func Url_encode(formdata map[string]string) string { 10 | var form string 11 | for key, value := range formdata { 12 | form += key + "=" + url.QueryEscape(value) + "&" 13 | } 14 | return form 15 | } 16 | 17 | func StringToReadCloser(s string) *readCloser { 18 | return &readCloser{s: s} 19 | } 20 | 21 | type readCloser struct { 22 | s string 23 | } 24 | 25 | func (r *readCloser) Read(p []byte) (n int, err error) { 26 | n = copy(p, r.s) 27 | r.s = r.s[n:] 28 | if n == 0 { 29 | err = io.EOF 30 | } 31 | return 32 | } 33 | 34 | func (r *readCloser) Close() error { 35 | return nil 36 | } 37 | --------------------------------------------------------------------------------