feat: add BaseURL config, RSS image enclosure, and separate rate limiters

- Load and validate BASE_URL, store in Config
- RSSGenerator now receives Config; adds <enclosure> for photo messages using BaseURL
- RateLimiter tracks feed and image limiters; provide FeedRateLimit and ImageRateLimit middleware
- Update routes to use the new middlewares and inject Config into RSSGenerator
This commit is contained in:
2026-03-13 22:11:01 +03:30
parent 7aab3f55ed
commit e210acb810
4 changed files with 93 additions and 26 deletions
+45 -10
View File
@@ -2,9 +2,11 @@ package rss
import (
"encoding/xml"
"errors"
"fmt"
"net/url"
"strconv"
"tgss/internal/config"
"time"
"github.com/gotd/td/tg"
@@ -12,11 +14,18 @@ import (
)
type RSSItem struct {
Title string `xml:"title"`
Link string `xml:"link"`
PubDate string `xml:"pubDate,omitempty"`
Description string `xml:"description"`
Guid string `xml:"guid,omitempty"`
Title string `xml:"title"`
Link string `xml:"link"`
Enclosure *RSSEnclosure `xml:"enclosure,omitempty"`
PubDate string `xml:"pubDate,omitempty"`
Description string `xml:"description"`
Guid string `xml:"guid,omitempty"`
}
type RSSEnclosure struct {
URL string `xml:"url,attr"`
Length string `xml:"lenghth,omitempty,attr"`
Type string `xml:"type,attr"`
}
type RSSChannel struct {
@@ -40,10 +49,11 @@ type RSSFeed struct {
type RSSGenerator struct {
logger *zap.Logger
config *config.Config
}
func NewRSSGenerator(logger *zap.Logger) *RSSGenerator {
return &RSSGenerator{logger: logger}
func NewRSSGenerator(logger *zap.Logger, config *config.Config) *RSSGenerator {
return &RSSGenerator{logger: logger, config: config}
}
func (r *RSSGenerator) GenerateFeed(items []tg.MessageClass, channelId string) *RSSFeed {
@@ -85,6 +95,7 @@ func (r *RSSGenerator) GenerateFeed(items []tg.MessageClass, channelId string) *
rssChannel.Items = append(rssChannel.Items, *item)
}
// Logging is done by AI
if errorCount > 0 {
r.logger.Warn("feed generation completed with errors",
zap.String("channel", channelId),
@@ -115,13 +126,37 @@ func (r *RSSGenerator) messageToItem(msg tg.MessageClass, channelId string) (*RS
description = "No content"
}
return &RSSItem{
rssItem := &RSSItem{
Title: "Post by @" + channelId + " on Telegram",
Link: messageURL.String(),
PubDate: time.Unix(int64(message.Date), 0).Format("Mon, 02 Jan 2006 15:04 MST"),
Description: description,
Guid: messageURL.String(),
}, nil
}
if err = r.messageHasPhoto(message); err == nil {
if enclosureURL, err := url.ParseRequestURI(r.config.BaseURL + "/image/" + channelId + "/" + strconv.Itoa(msg.GetID())); err == nil {
rssItem.Enclosure = &RSSEnclosure{
URL: enclosureURL.String(),
Length: "0",
Type: "image/jpeg",
}
}
}
return rssItem, nil
}
// TODO: add a hasPhoto helper to add the rss tag accordingly
func (r *RSSGenerator) messageHasPhoto(message tg.MessageClass) error {
media, ok := message.(*tg.Message).Media.(*tg.MessageMediaPhoto)
if !ok {
return errors.New("the message does not contain a photo media object")
}
_, ok = media.Photo.(*tg.Photo)
if !ok {
return errors.New("the message media has no photo payload")
}
return nil
}