package app import ( "context" "fmt" "log/slog" "os" "strings" "time" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" "github.com/microcosm-cc/bluemonday" "github.com/mmcdole/gofeed" ) type App struct { config *Config telegram *tgbotapi.BotAPI } func New(cfg *Config) (*App, error) { bot, err := tgbotapi.NewBotAPI(cfg.Telegram.BotToken) if err != nil { return nil, err } return &App{ config: cfg, telegram: bot, }, nil } func (a *App) Run(ctx context.Context) error { ticker := time.NewTicker(a.config.RSS.CheckInterval) if err := a.iteration(); err != nil { slog.ErrorContext(ctx, "failed iteration", slog.Any("error", err)) } for { select { case <-ctx.Done(): return nil case <-ticker.C: if err := a.iteration(); err != nil { slog.ErrorContext(ctx, "failed iteration", slog.Any("error", err)) } } } } func (a *App) iteration() error { seq, err := a.readSeqFile() if os.IsNotExist(err) { seq = "" err = nil } if err != nil { return err } items, err := a.findNewItems(seq) if err != nil || len(items) == 0 { return err } for i := len(items) - 1; i >= 0; i-- { if err := a.processItem(items[i]); err != nil { return err } if err := a.writeSeqFile(items[i].GUID); err != nil { return err } } return nil } func (a *App) processItem(item *gofeed.Item) error { p := bluemonday.Policy{} p.AllowStandardURLs() p.AllowElements("b", "i", "code", "u", "strike", "pre", "br", "a") p.AllowNoAttrs().Globally() p.AllowAttrs("href").OnElements("a") s := strings.ReplaceAll(item.Description, "", "") s = strings.ReplaceAll(s, "", "") s = strings.ReplaceAll(s, "", "") s = strings.ReplaceAll(s, "", "") s = strings.ReplaceAll(s, "

", "\n") s = strings.ReplaceAll(s, "
", "\n") s = p.Sanitize(s) s = strings.Trim(s, "\n\t ") str := fmt.Sprintf("%s\n%s", item.Title, s) for _, group := range a.config.Telegram.TargetGroups { switch { case item.Image != nil: msg := tgbotapi.NewPhoto( group, tgbotapi.FileURL(item.Image.URL), ) msg.ParseMode = tgbotapi.ModeHTML msg.Caption = str if _, err := a.telegram.Send(msg); err != nil { return err } default: msg := tgbotapi.NewMessage(group, str) msg.ParseMode = tgbotapi.ModeHTML if _, err := a.telegram.Send(msg); err != nil { return err } } msg := tgbotapi.NewMessage( group, fmt.Sprintf("Оригинал: %s", item.Link), ) if _, err := a.telegram.Send(msg); err != nil { return err } } return nil } func (a *App) readSeqFile() (string, error) { seqVal, err := os.ReadFile(a.config.RSS.SeqFile) if err != nil { return "", err } return string(seqVal), nil } func (a *App) writeSeqFile(seqVal string) error { return os.WriteFile(a.config.RSS.SeqFile, []byte(seqVal), 0o644) } func (a *App) findNewItems(from string) ([]*gofeed.Item, error) { fp := gofeed.NewParser() feed, err := fp.ParseURL(a.config.RSS.URL) if err != nil { return nil, err } out := make([]*gofeed.Item, 0, len(feed.Items)) for _, item := range feed.Items { if item.GUID == from { break } out = append(out, item) } return out, nil }