From 5947c1d643dfc077c19d3b4c01e599578e1dfe62 Mon Sep 17 00:00:00 2001 From: NeonXP Date: Wed, 4 Jan 2023 18:52:25 +0300 Subject: init --- cmd/bash/main.go | 98 ++++++++++++++++++++++++++++++++++ cmd/bot/main.go | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ cmd/bot/texts.go | 12 +++++ 3 files changed, 266 insertions(+) create mode 100644 cmd/bash/main.go create mode 100644 cmd/bot/main.go create mode 100644 cmd/bot/texts.go (limited to 'cmd') diff --git a/cmd/bash/main.go b/cmd/bash/main.go new file mode 100644 index 0000000..8ecf2e7 --- /dev/null +++ b/cmd/bash/main.go @@ -0,0 +1,98 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + "strconv" + "strings" + "sync" + + "github.com/antchfx/htmlquery" +) + +func main() { + all := []quoteElem{} + from := 3472 + wg := sync.WaitGroup{} + for i := from; i >= 1; i-- { + wg.Add(1) + go func(i int) { + defer wg.Done() + quotes, err := parsePage(i) + if err != nil { + log.Println(err) + return + } + all = append(all, quotes...) + }(i) + } + wg.Wait() + b, err := json.Marshal(all) + if err != nil { + panic(err) + } + if err := os.WriteFile("db/quotes.json", b, os.ModePerm); err != nil { + panic(err) + } + log.Println("ok") +} + +func parsePage(num int) ([]quoteElem, error) { + doc, err := htmlquery.LoadURL(fmt.Sprintf("https://xn--80abh7bk0c.xn--p1ai/index/%d", num)) + if err != nil { + return nil, err + } + quotes := []quoteElem{} + quotesList, err := htmlquery.QueryAll(doc, "/html/body/div[1]/main/section/article") + if err != nil { + return nil, err + } + for _, quote := range quotesList { + header, err := htmlquery.Query(quote, "/div/header/a") + if err != nil { + return nil, err + } + if header == nil { + break + } + num, _ := strconv.Atoi(header.FirstChild.Data[1:]) + date, err := htmlquery.Query(quote, "/div/header/div") + dates := "" + if err != nil { + return nil, err + } + if date != nil { + dates = date.FirstChild.Data + dates = strings.Trim(strings.ReplaceAll(dates, "\\n", ""), " ") + } + body := htmlquery.FindOne(quote, "/div/div").FirstChild + text := []string{} + for { + if body.DataAtom == 0 { + text = append(text, body.Data) + } + body = body.NextSibling + if body == nil { + break + } + } + quotes = append(quotes, quoteElem{ + Body: strings.Trim(strings.Join(text, "\n"), " \n\t"), + Num: num, + Date: dates, + }) + quote = quote.NextSibling + if quote == nil { + break + } + } + return quotes, nil +} + +type quoteElem struct { + Num int `json:"num"` + Body string `json:"body"` + Date string `json:"date"` +} diff --git a/cmd/bot/main.go b/cmd/bot/main.go new file mode 100644 index 0000000..99e1424 --- /dev/null +++ b/cmd/bot/main.go @@ -0,0 +1,156 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + "strings" + "time" + + "go.etcd.io/bbolt" + tele "gopkg.in/telebot.v3" + "gopkg.in/telebot.v3/middleware" + + "go.neonxp.dev/bot/pkg/bash" + "go.neonxp.dev/bot/pkg/geo" + "go.neonxp.dev/bot/pkg/weather" +) + +var ( + rplGeoSearch = &tele.ReplyMarkup{} + btnShops = rplGeoSearch.Data("Магазины и супермаркеты", "shops") + btnFarma = rplGeoSearch.Data("Аптеки", "farma") +) + +func main() { + pref := tele.Settings{ + Token: os.Getenv("TOKEN"), + Poller: &tele.LongPoller{Timeout: 10 * time.Second}, + } + rplGeoSearch.Inline(rplGeoSearch.Row(btnShops, btnFarma)) + db, err := bbolt.Open("db/my.db", 0o600, nil) + if err != nil { + log.Fatal(err) + } + defer db.Close() + + b, err := tele.NewBot(pref) + if err != nil { + log.Fatal(err) + return + } + b.Use(middleware.AutoRespond()) + b.Use(func(next tele.HandlerFunc) tele.HandlerFunc { + return tele.HandlerFunc(func(ctx tele.Context) error { + if ctx.Message() != nil { + log.Printf("F:%s L:%s N:%s ID:%d: %s", ctx.Sender().FirstName, ctx.Sender().LastName, ctx.Sender().Username, ctx.Sender().ID, ctx.Message().Text) + } + return next(ctx) + }) + }) + + b.Handle("/help", func(ctx tele.Context) error { + return ctx.Send(helpText, tele.ModeMarkdown) + }) + + b.Handle("/weather", func(ctx tele.Context) error { + city := ctx.Message().Payload + if city == "" { + city = "Казань" + } + weather, err := weather.Get(city) + if err != nil { + return err + } + return ctx.Send(weather) + }) + b.Handle("/weatherfull", func(ctx tele.Context) error { + city := ctx.Message().Payload + if city == "" { + city = "Казань" + } + return ctx.Send(&tele.Photo{ + File: tele.FromURL(fmt.Sprintf("https://wttr.in/%s.png?2p&lang=ru", city)), + }) + }) + b.Handle("/bash", func(ctx tele.Context) error { + q, err := bash.Get() + if err != nil { + return err + } + return ctx.Send(fmt.Sprintf("[#%d](https://xn--80abh7bk0c.xn--p1ai/quote/%d) • _%s_\n\n%s", q.Num, q.Num, q.Date, q.Body), tele.ModeMarkdown) + }) + b.Handle("/info", func(ctx tele.Context) error { + cmd := exec.Command("neofetch", "--stdout") + stdout, err := cmd.Output() + if err != nil { + return err + } + + return ctx.Send(string(stdout)) + }) + b.Handle(tele.OnLocation, func(ctx tele.Context) error { + return ctx.Send("Выберите категорию:", &tele.SendOptions{ + ReplyTo: ctx.Message(), + ReplyMarkup: rplGeoSearch, + }) + }) + b.Handle(&btnFarma, func(ctx tele.Context) error { + return geoSearch(ctx, "farma") + }) + b.Handle(&btnShops, func(ctx tele.Context) error { + return geoSearch(ctx, "shops") + }) + + b.Handle(tele.OnQuery, func(ctx tele.Context) error { + city := ctx.Data() + if len(city) < 3 { + return ctx.Answer(&tele.QueryResponse{}) + } + weather, err := weather.Get(city) + if err != nil { + return err + } + return ctx.Answer(&tele.QueryResponse{ + Results: tele.Results{ + &tele.PhotoResult{ + ResultBase: tele.ResultBase{ + ID: city, + }, + Title: weather, + URL: fmt.Sprintf("https://wttr.in/%s.png?2p&lang=ru", city), + ThumbURL: "https://neonxp.dev/img/weather.png", + }, + }, + CacheTime: 1800, + }) + }) + + b.Handle(tele.OnInlineResult, func(ctx tele.Context) error { + city := ctx.InlineResult().ResultID + return ctx.Send(&tele.Photo{ + File: tele.FromURL(fmt.Sprintf("https://wttr.in/%s.png?2p&lang=ru", city)), + }) + }) + + b.Start() +} + +func geoSearch(ctx tele.Context, search string) error { + msg := ctx.Message() + if msg.ReplyTo == nil || msg.ReplyTo.Location == nil { + return fmt.Errorf("no location: %v", msg) + } + points, err := geo.Search(float64(msg.ReplyTo.Location.Lat), float64(msg.ReplyTo.Location.Lng), search) + if err != nil { + return err + } + results := make([]string, 0, len(points)) + for i, p := range points { + results = append(results, fmt.Sprintf( + `%d. %s`, i+1, p.Lat, p.Lon, p.Name, + )) + } + return ctx.Send("Результаты в радиусе километра:\n\n"+strings.Join(results, "\n"), tele.ModeHTML) +} diff --git a/cmd/bot/texts.go b/cmd/bot/texts.go new file mode 100644 index 0000000..1656cdc --- /dev/null +++ b/cmd/bot/texts.go @@ -0,0 +1,12 @@ +package main + +const ( + helpText = "Универсальный бот - мультитул.\n" + + "\n*Команды:*\n\n" + + "- /help - помощь\n" + + "- /bash - цитата со старого доброго башорга\n" + + "- /weather - текущая погода. По-умолчанию, в Казани. Для другого города следует указать город: `/weather Москва`\n" + + "- /weatherfull - погода на два дня. По-умолчанию, в Казани. Для другого города следует указать город: `/weatherfull Москва`\n" + + "\n*Гео справочник:*\n\n" + + "Пришлите геопозицию в радиусе километра от которой искать интересующие заведения.\nЗатем выберите категорию интересующих заведений." +) -- cgit v1.2.3