Skip to content

Commit

Permalink
refact: bot (indes#209)
Browse files Browse the repository at this point in the history
  • Loading branch information
indes committed Sep 30, 2022
1 parent 8b78c5a commit ea8e62d
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 263 deletions.
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ require (
github.com/grokify/html-strip-tags-go v0.0.0-20200923094847-079d207a09f1
github.com/indes/telegraph-go v1.0.1
github.com/jinzhu/gorm v1.9.16
github.com/magiconair/properties v1.8.6
github.com/mattn/go-sqlite3 v1.14.15 // indirect
github.com/spf13/cast v1.5.0
github.com/spf13/viper v1.13.0
Expand Down
210 changes: 154 additions & 56 deletions internal/bot/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,24 @@ import (

"github.com/indes/flowerss-bot/internal/bot/handler"
"github.com/indes/flowerss-bot/internal/bot/middleware"
"github.com/indes/flowerss-bot/internal/bot/preview"
"github.com/indes/flowerss-bot/internal/config"
"github.com/indes/flowerss-bot/internal/core"
"github.com/indes/flowerss-bot/internal/log"
"github.com/indes/flowerss-bot/internal/model"
"github.com/indes/flowerss-bot/pkg/client"

"go.uber.org/zap"
tb "gopkg.in/telebot.v3"
)

var (
// B bot
B *tb.Bot

Core *core.Core
)

func init() {
if config.RunMode == config.TestMode {
return
}
zap.S().Infow(
"init telegram bot",
"token", config.BotToken,
"endpoint", config.TelegramEndpoint,
)
type Bot struct {
core *core.Core
tb *tb.Bot // telebot.Bot instance
}

func NewBot(core *core.Core) *Bot {
log.Infof("init telegram bot, token %s, endpoint %s", config.BotToken, config.TelegramEndpoint)
clientOpts := []client.HttpClientOption{
client.WithTimeout(10 * time.Second),
}
Expand All @@ -52,64 +45,57 @@ func init() {
settings.Verbose = true
}

var err error
B, err = tb.NewBot(settings)
if err != nil {
zap.S().Fatal(err)
return
b := &Bot{
core: core,
}
B.Use(middleware.UserFilter(), middleware.PreLoadMentionChat(), middleware.IsChatAdmin())
}

// Start bot
func Start(appCore *core.Core) {
if config.RunMode == config.TestMode {
return
var err error
b.tb, err = tb.NewBot(settings)
if err != nil {
log.Error(err)
return nil
}

Core = appCore
zap.S().Infof("bot start %s", config.AppVersionInfo())
setCommands()
B.Start()
b.tb.Use(middleware.UserFilter(), middleware.PreLoadMentionChat(), middleware.IsChatAdmin())
return b
}

func setCommands() {
func (b *Bot) registerCommands(appCore *core.Core) error {
commandHandlers := []handler.CommandHandler{
handler.NewStart(),
handler.NewPing(B),
handler.NewAddSubscription(Core),
handler.NewRemoveSubscription(B, Core),
handler.NewListSubscription(Core),
handler.NewPing(b.tb),
handler.NewAddSubscription(appCore),
handler.NewRemoveSubscription(b.tb, appCore),
handler.NewListSubscription(appCore),
handler.NewRemoveAllSubscription(),
handler.NewOnDocument(B, Core),
handler.NewSet(B, Core),
handler.NewSetFeedTag(Core),
handler.NewSetUpdateInterval(Core),
handler.NewExport(Core),
handler.NewOnDocument(b.tb, appCore),
handler.NewSet(b.tb, appCore),
handler.NewSetFeedTag(appCore),
handler.NewSetUpdateInterval(appCore),
handler.NewExport(appCore),
handler.NewImport(),
handler.NewPauseAll(Core),
handler.NewActiveAll(Core),
handler.NewPauseAll(appCore),
handler.NewActiveAll(appCore),
handler.NewHelp(),
handler.NewVersion(),
}

for _, h := range commandHandlers {
B.Handle(h.Command(), h.Handle, h.Middlewares()...)
b.tb.Handle(h.Command(), h.Handle, h.Middlewares()...)
}

ButtonHandlers := []handler.ButtonHandler{
handler.NewRemoveAllSubscriptionButton(Core),
handler.NewRemoveAllSubscriptionButton(appCore),
handler.NewCancelRemoveAllSubscriptionButton(),
handler.NewSetFeedItemButton(B, Core),
handler.NewRemoveSubscriptionItemButton(Core),
handler.NewNotificationSwitchButton(B, Core),
handler.NewSetSubscriptionTagButton(B),
handler.NewTelegraphSwitchButton(B, Core),
handler.NewSubscriptionSwitchButton(B, Core),
handler.NewSetFeedItemButton(b.tb, appCore),
handler.NewRemoveSubscriptionItemButton(appCore),
handler.NewNotificationSwitchButton(b.tb, appCore),
handler.NewSetSubscriptionTagButton(b.tb),
handler.NewTelegraphSwitchButton(b.tb, appCore),
handler.NewSubscriptionSwitchButton(b.tb, appCore),
}

for _, h := range ButtonHandlers {
B.Handle(h, h.Handle, h.Middlewares()...)
b.tb.Handle(h, h.Handle, h.Middlewares()...)
}

var commands []tb.Command
Expand All @@ -119,8 +105,120 @@ func setCommands() {
}
commands = append(commands, tb.Command{Text: h.Command(), Description: h.Description()})
}
zap.S().Debugf("set bot command %+v", commands)
if err := B.SetCommands(commands); err != nil {
zap.S().Errorw("set bot commands failed", "error", err.Error())
log.Debugf("set bot command %+v", commands)
if err := b.tb.SetCommands(commands); err != nil {
return err
}
return nil
}

func (b *Bot) Run() error {
if config.RunMode == config.TestMode {
return nil
}

if err := b.registerCommands(b.core); err != nil {
return err
}
zap.S().Infof("bot start %s", config.AppVersionInfo())
b.tb.Start()
return nil
}

func (b *Bot) SourceUpdate(
source *model.Source, newContents []*model.Content, subscribes []*model.Subscribe,
) {
b.BroadcastNews(source, subscribes, newContents)
}

func (b *Bot) SourceUpdateError(source *model.Source) {
b.BroadcastSourceError(source)
}

// BroadcastNews send new contents message to subscriber
func (b *Bot) BroadcastNews(source *model.Source, subs []*model.Subscribe, contents []*model.Content) {
zap.S().Infow(
"broadcast news",
"fetcher id", source.ID,
"fetcher title", source.Title,
"subscriber count", len(subs),
"new contents", len(contents),
)

for _, content := range contents {
previewText := preview.TrimDescription(content.Description, config.PreviewText)

for _, sub := range subs {
tpldata := &config.TplData{
SourceTitle: source.Title,
ContentTitle: content.Title,
RawLink: content.RawLink,
PreviewText: previewText,
TelegraphURL: content.TelegraphURL,
Tags: sub.Tag,
EnableTelegraph: sub.EnableTelegraph == 1 && content.TelegraphURL != "",
}

u := &tb.User{
ID: sub.UserID,
}
o := &tb.SendOptions{
DisableWebPagePreview: config.DisableWebPagePreview,
ParseMode: config.MessageMode,
DisableNotification: sub.EnableNotification != 1,
}
msg, err := tpldata.Render(config.MessageMode)
if err != nil {
zap.S().Errorw(
"broadcast news error, tpldata.Render err",
"error", err.Error(),
)
return
}
if _, err := b.tb.Send(u, msg, o); err != nil {

if strings.Contains(err.Error(), "Forbidden") {
zap.S().Errorw(
"broadcast news error, bot stopped by user",
"error", err.Error(),
"user id", sub.UserID,
"source id", sub.SourceID,
"title", source.Title,
"link", source.Link,
)
sub.Unsub()
}

/*
Telegram return error if markdown message has incomplete format.
Print the msg to warn the user
api error: Bad Request: can't parse entities: Can't find end of the entity starting at byte offset 894
*/
if strings.Contains(err.Error(), "parse entities") {
zap.S().Errorw(
"broadcast news error, markdown error",
"markdown msg", msg,
"error", err.Error(),
)
}
}
}
}
}

// BroadcastSourceError send fetcher update error message to subscribers
func (b *Bot) BroadcastSourceError(source *model.Source) {
subs := model.GetSubscriberBySource(source)
var u tb.User
for _, sub := range subs {
message := fmt.Sprintf(
"[%s](%s) 已经累计连续%d次更新失败,暂停更新", source.Title, source.Link, config.ErrorThreshold,
)
u.ID = sub.UserID
_, _ = b.tb.Send(
&u, message, &tb.SendOptions{
ParseMode: tb.ModeMarkdown,
},
)
}
}
17 changes: 11 additions & 6 deletions internal/bot/util.go → internal/bot/preview/util.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package bot
package preview

import (
"html"
Expand All @@ -8,7 +8,7 @@ import (
strip "github.com/grokify/html-strip-tags-go"
)

func trimDescription(desc string, limit int) string {
func TrimDescription(desc string, limit int) string {
if limit == 0 {
return ""
}
Expand All @@ -17,10 +17,15 @@ func trimDescription(desc string, limit int) string {
regexp.MustCompile("\n+").ReplaceAllLiteralString(
strings.ReplaceAll(
regexp.MustCompile(`<br(| /)>`).ReplaceAllString(
html.UnescapeString(desc), "<br>"),
"<br>", "\n"),
"\n")),
"\n")
html.UnescapeString(desc), "<br>",
),
"<br>", "\n",
),
"\n",
),
),
"\n",
)

contentDescRune := []rune(desc)
if len(contentDescRune) > limit {
Expand Down
Loading

0 comments on commit ea8e62d

Please sign in to comment.