diff --git a/main.go b/main.go index 9078c96..b148682 100644 --- a/main.go +++ b/main.go @@ -1,54 +1,47 @@ package main import ( - "io" + "encoding/json" "net/http" + "net/url" "os" - "regexp" "strings" "time" tgbotapi "git.zio.sh/astra/telegram-bot-api" "github.com/astravexton/logger" - "github.com/dghubble/sling" "github.com/google/uuid" ) -const ddgURL = "https://duckduckgo.com" +const openverseURL = "https://api.openverse.org/v1/images/" -// ImgResult ... -type ImgResult struct { - Ads interface{} `json:"ads"` - Next string `json:"next"` - Query string `json:"query"` - QueryEncoded string `json:"queryEncoded"` - ResponseType string `json:"response_type"` - Results []Results `json:"results"` - Vqd struct { - Testing string `json:"testing"` - } `json:"vqd"` +var httpClient = &http.Client{Timeout: 5 * time.Second} + +type openverseResponse struct { + ResultCount int `json:"result_count"` + Results []openverseImage `json:"results"` +} + +type openverseImage struct { + URL string `json:"url"` + Thumbnail string `json:"thumbnail"` + Title string `json:"title"` + Width int `json:"width"` + Height int `json:"height"` + ForeignLandingURL string `json:"foreign_landing_url"` + Source string `json:"source"` + Filetype string `json:"filetype"` } type Results struct { - Height int `json:"height"` - Image string `json:"image"` - Source string `json:"source"` - Thumbnail string `json:"thumbnail"` - Title string `json:"title"` - URL string `json:"url"` - Width int `json:"width"` -} - -type Params struct { - Query string `url:"q,omitempty"` - L string `url:"l,omitempty"` - O string `url:"o,omitempty"` - Q string `url:"q,omitempty"` - VQD string `url:"vqd,omitempty"` - F string `url:"f,omitempty"` - P string `url:"p,omitempty"` - S string `url:"s,omitempty"` - U string `url:"u,omitempty"` + Height int + Image string + Source string + Thumbnail string + Title string + URL string + Width int + Filetype string } func main() { @@ -80,7 +73,7 @@ func main() { break } var result interface{} - if strings.HasSuffix(image.Image, ".gif") { + if image.Filetype == "gif" || strings.HasSuffix(strings.ToLower(image.Image), ".gif") { result = &tgbotapi.InlineQueryResultGIF{ Type: "gif", ID: uuid.New().String(), @@ -100,7 +93,7 @@ func main() { inlineConf := tgbotapi.InlineConfig{ InlineQueryID: update.InlineQuery.ID, IsPersonal: false, - CacheTime: 1, + CacheTime: 300, Results: results, } bot.Request(inlineConf) @@ -109,61 +102,69 @@ func main() { } } + func getImageList(query string) []Results { - safeSearch := "-1" // -1 = safe off, 1 = safe on - if len(query) >= 8 { - if strings.HasPrefix(query, "safe:on") { - safeSearch = "1" - query = query[8:] - } + mature := "true" + if strings.HasPrefix(query, "safe:on") { + mature = "false" + query = strings.TrimSpace(strings.TrimPrefix(query, "safe:on")) + } + if query == "" { + return nil } - params := &Params{Query: query} - s := sling.New().Get(ddgURL) - s.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36") - s.QueryStruct(params) - req, err := s.Request() + // Anonymous requests are capped at 20; a registered token allows more. + token := os.Getenv("OPENVERSE_TOKEN") + pageSize := "20" + if token != "" { + pageSize = "50" + } + + params := url.Values{} + params.Set("q", query) + params.Set("page_size", pageSize) + params.Set("mature", mature) + + req, err := http.NewRequest(http.MethodGet, openverseURL+"?"+params.Encode(), nil) if err != nil { - logger.Critical(err.Error()) + logger.Warning("building request: %s", err) + return nil + } + req.Header.Set("User-Agent", "telegram-imagesearch-bot/1.0") + if token != "" { + req.Header.Set("Authorization", "Bearer "+token) } - client := &http.Client{} - client.Timeout = time.Second * 3 - - res, err := client.Do(req) + res, err := httpClient.Do(req) if err != nil { - logger.Critical(err.Error()) + logger.Warning("openverse request failed: %s", err) + return nil } - defer res.Body.Close() - body, _ := io.ReadAll(res.Body) - bodyContents := string(body) - - r := regexp.MustCompile(`vqd=([\d-]+)`) - token := strings.ReplaceAll(r.FindString(bodyContents), "vqd=", "") - - params = &Params{ - L: "us-en", - O: "json", - Q: query, - VQD: token, - F: ",,,,,", - P: safeSearch, - S: "0", - U: "bing", - } - s = s.Get(ddgURL).Path("/i.js").QueryStruct(params) - s.Set("Authority", "duckduckgo.com") - s.Set("Accept", "application/json, text/javascript, */*; q=0.01") - s.Set("Cookie", "bc=1; at=-1; au=-1; av=1; ax=-1; ak=-1; ao=-1; ap=-1; 1=-1; 5=1; aq=-1; ay=b; ah=uk-en; l=wt-wt; p=-2") - s.Set("Referer", "https://duckduckgo.com/") - - imageList := &ImgResult{} - _, err = s.ReceiveSuccess(imageList) - if err != nil { - logger.Critical(err.Error()) + if res.StatusCode != http.StatusOK { + logger.Warning("openverse returned %s for %q", res.Status, query) + return nil } - return imageList.Results + var data openverseResponse + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + logger.Warning("decoding openverse response: %s", err) + return nil + } + + results := make([]Results, 0, len(data.Results)) + for _, img := range data.Results { + results = append(results, Results{ + Height: img.Height, + Image: img.URL, + Source: img.Source, + Thumbnail: img.Thumbnail, + Title: img.Title, + URL: img.ForeignLandingURL, + Width: img.Width, + Filetype: img.Filetype, + }) + } + return results }