package main import ( "encoding/json" "fmt" "log" "net/http" "net/url" "strings" "time" "git.nefrace.ru/nefrace/nechotron" "github.com/NicoNex/echotron/v3" ) var docApiURL = "https://docs.godotengine.org/_/api/v2/search/?q=%s&project=godot&version=%s&language=en" var docURL = "https://docs.godotengine.org/en/%s/search.html?q=%s" var lastDocSearch = time.Now() 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, version string) (string, string, error) { since := time.Since(lastDocSearch) if since < 20*time.Second { return "Извините, запросы происходят слишком часто\\.", "", nil } lastDocSearch = time.Now() topic_escaped := nechotron.EscapeMd2(topic) not_found := fmt.Sprintf("Извините, по запросу *%s* ничего не найдено\\.", topic_escaped) exactUrl := fmt.Sprintf(docApiURL, url.QueryEscape(topic), version) looselyUrl := fmt.Sprintf(docApiURL, url.QueryEscape(topic+"*"), version) workedTopic := topic response, err := getAndDecode[DocResponse](exactUrl) if err != nil || len(response.Results) == 0 { workedTopic = topic + "*" 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 := nechotron.EscapeMd2(r.Title) link, _ := url.JoinPath(r.Domain, r.Path) textResults += fmt.Sprintf("%d\\. [%s](%s)\n", i+1, text, link) } text := fmt.Sprintf("Вот что я нашла по запросу *%s* \\(для версии `%s`\\): \n\n%s", topic_escaped, version, textResults) return text, workedTopic, nil } var doc_variants []string = []string{"4.1", "3.5"} func handleDocRequest(u *nechotron.Update) error { topic := u.Ctx.Value(nechotron.FilteredValue("docTopic")).(string) currentVersion := doc_variants[0] versionButtons := []echotron.InlineKeyboardButton{} for _, variant := range doc_variants { if variant == currentVersion { continue } versionButtons = append(versionButtons, nechotron.InButtonCallback("Для "+variant, fmt.Sprintf("docs:%s:%s", variant, topic))) } text, workedTopic, docerr := getDocs(topic, doc_variants[0]) if docerr != nil { log.Println("Can't get docs: ", docerr) } kb := nechotron.NewInlineKeyboard(). Row(versionButtons...). Row(nechotron.InButtonURL("Поискать самостоятельно", fmt.Sprintf(docURL, currentVersion, url.QueryEscape(workedTopic)))). Row(nechotron.InButtonCallback("Спасибо, не надо", "delete")) opts := nechotron.NewOptions(). MarkdownV2(). ReplyTo(u.MessageID()). ReplyMarkup(kb.Markup()). MessageOptions() _, err := u.AnswerText(text, opts) return err } func handleDocCallback(u *nechotron.Update) error { text := strings.TrimPrefix(u.Callback(), "docs:") items := strings.Split(text, ":") version := items[0] topic := items[1] versionButtons := []echotron.InlineKeyboardButton{} for _, variant := range doc_variants { if variant == version { continue } versionButtons = append(versionButtons, nechotron.InButtonCallback("Для "+variant, fmt.Sprintf("docs:%s:%s", variant, topic))) } text, workedTopic, docerr := getDocs(topic, version) if docerr != nil { log.Println("Can't get docs: ", docerr) } kb := nechotron.NewInlineKeyboard(). Row(versionButtons...). Row(nechotron.InButtonURL("Поискать самостоятельно", fmt.Sprintf(docURL, version, url.QueryEscape(workedTopic)))). Row(nechotron.InButtonCallback("Спасибо, не надо", "delete")) opts := nechotron.NewOptions(). MarkdownV2(). ReplyMarkup(kb.Markup()). MessageTextOptions() _, err := u.EditText(text, opts) if err != nil { log.Println(err) log.Println(text) } return err }