package main import ( "encoding/json" "fmt" "log" "net/http" "net/url" "strings" "time" "github.com/go-telegram/bot" ) var docApiURL = "https://docs.godotengine.org/_/api/v3/search/?q=project:godot %s" var docURL = "https://docs.godotengine.org/en/stable/search.html?q=%s" var docApiURL_ru = "https://docs.godotengine.org/_/api/v3/search/?q=project:godot-ru %s" var docURL_ru = "https://docs.godotengine.org/ru/4.x/search.html?q=%s" var lastDocSearch = time.Now().Add(-1 * time.Hour) type DocResponse struct { Count uint `json:"count"` Next string `json:"next"` Previous string `json:"previous"` Results []DocResult `json:"results"` } type DocResult struct { Title string `json:"title"` Domain string `json:"domain"` Path string `json:"path"` Highlights DocHighlights `json:"highlights"` } type DocHighlights struct { Title []string `json:"title"` } func getAndDecode[T any](u string) (*T, error) { req, err := url.ParseRequestURI(u) if err != nil { log.Println("Can't parse url: ", err) return nil, err } result, err := http.Get(req.String()) if err != nil { log.Println("Can't get http: ", err) return nil, err } defer result.Body.Close() var response T err = json.NewDecoder(result.Body).Decode(&response) if err != nil { return nil, err } return &response, nil } func getDocs(topic string, russian bool) (string, error) { apiURL := docApiURL pageURL := docURL if russian { apiURL = docApiURL_ru pageURL = docURL_ru } since := time.Since(lastDocSearch) GO_SEARCH_YOURSELF := fmt.Sprintf("\n\n[Попробуйте поискать самостоятельно\\!](%s)", fmt.Sprintf(pageURL, url.QueryEscape(topic))) if since < 5*time.Second { return "Извините, запросы происходят слишком часто" + GO_SEARCH_YOURSELF, nil } lastDocSearch = time.Now() topic_escaped := bot.EscapeMarkdown(topic) not_found := fmt.Sprintf("Извините, по запросу *%s* ничего не найдено\\."+GO_SEARCH_YOURSELF, topic_escaped) exactUrl := fmt.Sprintf(apiURL, url.QueryEscape(topic)) looselyUrl := fmt.Sprintf(apiURL, url.QueryEscape(topic+"*")) response, err := getAndDecode[DocResponse](exactUrl) if err != nil || len(response.Results) == 0 { response, err = getAndDecode[DocResponse](looselyUrl) if err != nil || len(response.Results) == 0 { return not_found, nil } } textResults := "" for i, r := range response.Results { if i > 9 { break } text := bot.EscapeMarkdown(r.Title) link, _ := url.JoinPath(r.Domain, r.Path) textResults += fmt.Sprintf("%d\\. [%s](%s)\n", i+1, text, link) } textResults += fmt.Sprintf("\n\n Или вы можете попробовать [поискать самостоятельно](%s)\\!", fmt.Sprintf(pageURL, topic)) text := fmt.Sprintf("Вот что я нашла по запросу *%s*: \n\n%s", topic_escaped, textResults) return text, nil } func HandleDocRequest(g Godette) { topic := strings.TrimPrefix(g.update.Message.Text, "/docs ") text, docerr := getDocs(topic, false) if docerr != nil { log.Println("Can't get docs: ", docerr) } _, err := g.ReplyWithMarkdown(text) if err != nil { log.Println(err) } } func HandleDocRequestRu(g Godette) { topic := strings.TrimPrefix(g.update.Message.Text, "/docs_ru ") text, docerr := getDocs(topic, true) if docerr != nil { log.Println("Can't get docs: ", docerr) } _, err := g.ReplyWithMarkdown(text) if err != nil { log.Println(err) } }