RSS feed, optimized docs handler

This commit is contained in:
Nefrace 2023-06-15 17:03:57 +03:00
parent 928149cb8e
commit 900673a59d
10 changed files with 217 additions and 14 deletions

View File

@ -8,7 +8,9 @@ var commandMe = neco.NewCommand("me", "Пишу ваш текст о вас в
var commandHelp = neco.NewCommand("help", "Показываю данный текст", false)
var commandSay = neco.NewCommand("say", "Пишу ваш текст от своего имени.", true)
var commandWarn = neco.NewCommand("warn", "Делаю предупреждение пользователю", true)
var commandFeed = neco.NewCommand("subscribe", "Регистрирую данный чат в качестве получателя рассылки", true)
var commandFeedUnsub = neco.NewCommand("unsubscribe", "Удаляю данный чат из рассылки", true)
var defaultCommands = []*neco.Command{commandHelp, commandMe}
var adminCommands = []*neco.Command{commandSay, commandWarn}
var adminCommands = []*neco.Command{commandSay, commandWarn, commandFeed}
var allCommands = append(defaultCommands, adminCommands...)

12
go.mod
View File

@ -8,3 +8,15 @@ require (
github.com/google/uuid v1.3.0
github.com/joho/godotenv v1.5.1
)
require (
github.com/PuerkitoBio/goquery v1.8.0 // indirect
github.com/andybalholm/cascadia v1.3.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mmcdole/gofeed v1.2.1 // indirect
github.com/mmcdole/goxpp v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
golang.org/x/net v0.4.0 // indirect
golang.org/x/text v0.5.0 // indirect
)

31
go.sum
View File

@ -2,9 +2,40 @@ git.nefrace.ru/nefrace/nechotron v0.0.0-20230119201747-5842815c958c h1:3vYJhrChr
git.nefrace.ru/nefrace/nechotron v0.0.0-20230119201747-5842815c958c/go.mod h1:PiYTWTy1SMXKdsxNSrQOqUQRffw4DXI32PjjxVMJuOA=
github.com/NicoNex/echotron/v3 v3.22.0 h1:2ymJcjKqtZ/rfD5CveR2VKqQob7JmRgJmoLOJ3sim/4=
github.com/NicoNex/echotron/v3 v3.22.0/go.mod h1:LpP5IyHw0y+DZUZMBgXEDAF9O8feXrQu7w7nlJzzoZI=
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/mmcdole/gofeed v1.2.1 h1:tPbFN+mfOLcM1kDF1x2c/N68ChbdBatkppdzf/vDe1s=
github.com/mmcdole/gofeed v1.2.1/go.mod h1:2wVInNpgmC85q16QTTuwbuKxtKkHLCDDtf0dCmnrNr4=
github.com/mmcdole/goxpp v1.1.0 h1:WwslZNF7KNAXTFuzRtn/OKZxFLJAAyOA9w82mDz2ZGI=
github.com/mmcdole/goxpp v1.1.0/go.mod h1:v+25+lT2ViuQ7mVxcncQ8ch1URund48oH+jhjiwEgS8=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -9,10 +9,11 @@ import (
"strings"
"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/ru/stable/search.html?q=%s"
var docURL = "https://docs.godotengine.org/en/%s/search.html?q=%s"
type DocResponse struct {
Count uint `json:"count"`
@ -62,13 +63,21 @@ func getDocs(topic string, version string) (string, error) {
return text, nil
}
var doc_variants []string = []string{"latest", "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)))
}
kb := nechotron.NewInlineKeyboard().
Row(
nechotron.InButtonCallback("Дай для 3.5", fmt.Sprintf("docs3:%s", topic)),
nechotron.InButtonCallback("А на русском можно?", "rudocs")).
Row(nechotron.InButtonURL("Поищу сам", fmt.Sprintf(docURL, url.QueryEscape(topic)))).
Row(versionButtons...).
Row(nechotron.InButtonURL("Поищу сам", fmt.Sprintf(docURL, currentVersion, url.QueryEscape(topic)))).
Row(nechotron.InButtonCallback("Спасибо, не надо", "delete"))
opts := nechotron.NewOptions().
MarkdownV2().
@ -84,19 +93,28 @@ func handleDocRequest(u *nechotron.Update) error {
return err
}
func handleDocRequest3(u *nechotron.Update) error {
topic := strings.TrimPrefix(u.Callback(), "docs3:")
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)))
}
kb := nechotron.NewInlineKeyboard().
Row(
nechotron.InButtonCallback("А на русском можно?", "rudocs")).
Row(nechotron.InButtonURL("Поищу сам", fmt.Sprintf(docURL, url.QueryEscape(topic)))).
Row(versionButtons...).
Row(nechotron.InButtonURL("Поищу сам", fmt.Sprintf(docURL, version, url.QueryEscape(topic)))).
Row(nechotron.InButtonCallback("Спасибо, не надо", "delete"))
opts := nechotron.NewOptions().
MarkdownV2().
ReplyMarkup(kb.Markup()).
MessageTextOptions()
text, docerr := getDocs(topic, "3.5")
text, docerr := getDocs(topic, version)
if docerr != nil {
log.Println("Can't get docs: ", docerr)
}

View File

@ -36,7 +36,7 @@ func handleKarma(u *nechotron.Update) error {
store := tongo.NewStore[KarmaShot](db)
fromKarma, _ := store.Count(u.Ctx, tongo.E("to", from.ID))
totalFromKarma := from.KarmaOffset + fromKarma
if totalFromKarma < 0 {
if totalFromKarma <= 0 {
res, err := u.AnswerText(
fmt.Sprintf("У тебя слишком маленькая карма *\\(%d\\), чтобы менять её другим\\.", totalFromKarma),
&echotron.MessageOptions{ParseMode: echotron.MarkdownV2, ReplyToMessageID: u.MessageID()})

View File

@ -4,6 +4,7 @@ import (
"fmt"
"log"
"math/rand"
"time"
"git.nefrace.ru/nefrace/nechotron"
"git.nefrace.ru/nefrace/tongo"
@ -31,6 +32,9 @@ func handleHelp(u *nechotron.Update, text string) error {
func handleSay(u *nechotron.Update, text string) error {
u.DeleteMessage()
if text == "" {
return nil
}
_, err := u.AnswerMarkdown(fmt.Sprintf("*_%s_*", nechotron.EscapeMd2(text)))
return err
}
@ -70,3 +74,43 @@ func handleUsersImport(u *nechotron.Update) error {
log.Println(string(file))
return nil
}
func handleRegisterFeed(u *nechotron.Update, _ string) error {
feedChats := tongo.NewStore[FeedChat](db)
chat, err := feedChats.GetOne(u.Ctx, tongo.E("chatid", u.ChatID()))
if err != nil {
chat = &FeedChat{
Item: tongo.NewID(),
ChatID: u.ChatID(),
Title: u.Message.Chat.Title,
ThreadID: u.Message.ThreadID,
}
feedChats.InsertOne(u.Ctx, chat)
res, _ := u.AnswerPlain("Чат зарегистрирован для рассылки")
go func() {
time.Sleep(10 * time.Second)
u.Bot.DeleteMessage(u.ChatID(), res.Result.ID)
}()
}
return nil
}
func handleDeleteFeed(u *nechotron.Update, _ string) error {
feedChats := tongo.NewStore[FeedChat](db)
chat, err := feedChats.GetOne(u.Ctx, tongo.E("chatid", u.ChatID()))
if err != nil {
res, _ := u.AnswerPlain("Чат не подписан на рассылку")
go func() {
time.Sleep(10 * time.Second)
u.Bot.DeleteMessage(u.ChatID(), res.Result.ID)
}()
return nil
}
feedChats.DeleteByID(u.Ctx, chat.Id)
res, _ := u.AnswerPlain("Чат удалён из рассылки")
go func() {
time.Sleep(10 * time.Second)
u.Bot.DeleteMessage(u.ChatID(), res.Result.ID)
}()
return nil
}

View File

@ -34,6 +34,7 @@ func main() {
api := echotron.NewAPI(token)
nechotron.SetMyCommands(api, "", echotron.BotCommandScope{Type: echotron.Any}, defaultCommands...)
nechotron.SetMyCommands(api, "", echotron.BotCommandScope{Type: echotron.BCSTAllChatAdministrators}, allCommands...)
go RSSTask(&api)
log.Fatal(neco.DispatchPoll())
}

76
rss.go Normal file
View File

@ -0,0 +1,76 @@
package main
import (
"context"
"fmt"
"log"
"time"
"git.nefrace.ru/nefrace/nechotron"
"git.nefrace.ru/nefrace/tongo"
"github.com/NicoNex/echotron/v3"
"github.com/mmcdole/gofeed"
)
var text_template = `
[*%s*](%s)
_by %s_
%s
`
func RSSTask(api *echotron.API) {
for {
ParseRSS(api)
time.Sleep(5 * time.Minute)
}
}
func ParseRSS(api *echotron.API) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
feedChats := tongo.NewStore[FeedChat](db)
feeds := tongo.NewStore[Feeds](db)
lastFeed, err := feeds.GetOne(ctx)
if err != nil || lastFeed == nil {
lastFeed = &Feeds{
Item: tongo.NewID(),
}
feeds.ReplaceItem(ctx, *lastFeed, true)
}
fp := gofeed.NewParser()
feed, err := fp.ParseURL("https://godotengine.org/rss.xml")
if err != nil {
log.Println("Can't get feed: ", err)
return
}
item := feed.Items[0]
if lastFeed.Published.Compare(*item.PublishedParsed) == -1 {
chats, _ := feedChats.GetMany(ctx)
if len(chats) == 0 {
return
}
text := fmt.Sprintf(text_template,
nechotron.EscapeMd2(item.Title),
item.Link,
nechotron.EscapeMd2(item.Authors[0].Name),
nechotron.EscapeMd2(item.Description),
)
sent := false
for _, chat := range chats {
_, err := api.SendPhoto(echotron.NewInputFileURL(item.Custom["image"]), chat.ChatID, nechotron.NewOptions().MarkdownV2().Caption(text).PhotoOptions())
if err != nil {
log.Println("Can't send message to", chat.Title, ": ", err)
} else {
sent = true
}
time.Sleep(1 * time.Second)
}
if !sent {
return
}
lastFeed.Published = *item.PublishedParsed
lastFeed.Title = item.Title
feeds.ReplaceItem(ctx, *lastFeed, true)
}
}

View File

@ -9,6 +9,8 @@ var MainState = neco.State{
adminDispatcher := neco.NewDispatcher().
HandleCallback(neco.CallbackExact("delete"), handleDeleteCallback).
HandleCommand(commandSay, handleSay).
HandleCommand(commandFeed, handleRegisterFeed).
HandleCommand(commandFeedUnsub, handleDeleteFeed).
HandleFilter(isFile, handleUsersImport)
adminOnly := neco.NewDispatcher().
HandleFilter(neco.IsUserAdmin, adminDispatcher.Run)
@ -24,7 +26,7 @@ var MainState = neco.State{
docs := neco.NewDispatcher().
HandleFilter(docRequest, handleDocRequest).
HandleCallback(neco.CallbackPrefix("docs3"), handleDocRequest3)
HandleCallback(neco.CallbackPrefix("docs"), handleDocCallback)
triggers := neco.NewDispatcher().
HandleFilter(offtopTrigger, handleOfftop)

View File

@ -97,3 +97,20 @@ type Config struct {
}
func (Config) Coll() string { return "config" }
type Feeds struct {
tongo.Item `bson:",inline"`
Title string
Published time.Time
}
func (Feeds) Coll() string { return "feeds" }
type FeedChat struct {
tongo.Item `bson:",inline"`
Title string
ChatID int64
ThreadID int
}
func (FeedChat) Coll() string { return "feed_chats" }