all repos

rss-tools @ 704cdb2

get rss feed from sources that(i need and) dont provide one
4 files changed, 44 insertions(+), 64 deletions(-)
update feed api again
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-04-22 20:13:38 +0300
Authored at: 2026-04-20 16:04:24 +0300
Change ID: vklopukvkryuuvnmuovpqtqvlwulzxsk
Parent: c37eeed
M app/atom.go
···
        41
        41
         	Updated     time.Time

      
        42
        42
         }

      
        43
        43
         

      
        44
        
        -type FeedOption func(*AtomFeed)

      
        
        44
        +type FeedBuilder struct{ f AtomFeed }

      
        45
        45
         

      
        46
        
        -func WithFeedSubtitle(subtitle string) FeedOption {

      
        47
        
        -	return func(f *AtomFeed) {

      
        48
        
        -		f.Subtitle = subtitle

      
        49
        
        -	}

      
        50
        
        -}

      
        51
        
        -

      
        52
        
        -func WithFeedUpdated(updated time.Time) FeedOption {

      
        53
        
        -	return func(f *AtomFeed) {

      
        54
        
        -		if !updated.IsZero() {

      
        55
        
        -			f.Updated = updated.Format(time.RFC3339)

      
        56
        
        -		}

      
        57
        
        -	}

      
        58
        
        -}

      
        59
        
        -

      
        60
        
        -type FeedBuilder struct {

      
        61
        
        -	feed AtomFeed

      
        62
        
        -}

      
        63
        
        -

      
        64
        
        -func NewFeed(title, id string, opts ...FeedOption) *FeedBuilder {

      
        65
        
        -	builder := &FeedBuilder{feed: AtomFeed{

      
        
        46
        +func NewFeed(title, id string) *FeedBuilder {

      
        
        47
        +	return &FeedBuilder{f: AtomFeed{

      
        66
        48
         		XMLNS:   "http://www.w3.org/2005/Atom",

      
        67
        49
         		Title:   title,

      
        68
        50
         		ID:      id,

      
        69
        51
         		Updated: time.Now().Format(time.RFC3339),

      
        70
        52
         	}}

      
        71
        
        -	for _, opt := range opts {

      
        72
        
        -		opt(&builder.feed)

      
        73
        
        -	}

      
        74
        
        -	return builder

      
        75
        53
         }

      
        76
        54
         

      
        77
        
        -func (f *FeedBuilder) Add(title, id, content string, date time.Time) *FeedBuilder {

      
        78
        
        -	return f.AddEntry(FeedEntry{

      
        79
        
        -		Title:   title,

      
        80
        
        -		ID:      id,

      
        81
        
        -		Content: content,

      
        82
        
        -		Updated: date,

      
        83
        
        -	})

      
        
        55
        +func (f *FeedBuilder) WithSubtitle(subtitle string) *FeedBuilder {

      
        
        56
        +	f.f.Subtitle = subtitle

      
        
        57
        +	return f

      
        84
        58
         }

      
        85
        59
         

      
        86
        
        -func (f *FeedBuilder) AddText(title, content string, updated time.Time) *FeedBuilder {

      
        87
        
        -	return f.AddEntry(FeedEntry{

      
        88
        
        -		Title:   title,

      
        89
        
        -		Content: content,

      
        90
        
        -		Updated: updated,

      
        91
        
        -	})

      
        
        60
        +func (f *FeedBuilder) WithUpdated(updated time.Time) *FeedBuilder {

      
        
        61
        +	if !updated.IsZero() {

      
        
        62
        +		f.f.Updated = updated.Format(time.RFC3339)

      
        
        63
        +	}

      
        
        64
        +	return f

      
        92
        65
         }

      
        93
        66
         

      
        94
        
        -func (f *FeedBuilder) AddEntry(entry FeedEntry) *FeedBuilder {

      
        
        67
        +func (f *FeedBuilder) Add(entry FeedEntry) *FeedBuilder {

      
        95
        68
         	if entry.Updated.IsZero() {

      
        96
        69
         		entry.Updated = time.Now()

      
        97
        70
         	}

      ···
        100
        73
         		entry.ID = fmt.Sprintf("urn:sha1:%x", hash)

      
        101
        74
         	}

      
        102
        75
         

      
        103
        
        -	f.feed.Entries = append(f.feed.Entries, AtomEntry{

      
        
        76
        +	contentType := entry.ContentType

      
        
        77
        +	if contentType == "" {

      
        
        78
        +		contentType = "text"

      
        
        79
        +	}

      
        
        80
        +

      
        
        81
        +	f.f.Entries = append(f.f.Entries, AtomEntry{

      
        104
        82
         		Title:   entry.Title,

      
        105
        83
         		ID:      entry.ID,

      
        106
        84
         		Updated: entry.Updated.Format(time.RFC3339),

      ···
        110
        88
         		},

      
        111
        89
         	})

      
        112
        90
         

      
        113
        
        -	feedUpdated, err := time.Parse(time.RFC3339, f.feed.Updated)

      
        
        91
        +	feedUpdated, err := time.Parse(time.RFC3339, f.f.Updated)

      
        114
        92
         	if err != nil || entry.Updated.After(feedUpdated) {

      
        115
        
        -		f.feed.Updated = entry.Updated.Format(time.RFC3339)

      
        
        93
        +		f.f.Updated = entry.Updated.Format(time.RFC3339)

      
        116
        94
         	}

      
        117
        95
         	return f

      
        118
        96
         }

      
        119
        97
         

      
        120
        98
         func (f *FeedBuilder) SetUpdated(updated time.Time) *FeedBuilder {

      
        121
        99
         	if !updated.IsZero() {

      
        122
        
        -		f.feed.Updated = updated.Format(time.RFC3339)

      
        
        100
        +		f.f.Updated = updated.Format(time.RFC3339)

      
        123
        101
         	}

      
        124
        102
         	return f

      
        125
        103
         }

      ···
        127
        105
         func (f *FeedBuilder) WriteTo(w io.Writer) error {

      
        128
        106
         	enc := xml.NewEncoder(w)

      
        129
        107
         	enc.Indent("", "  ")

      
        130
        
        -	return enc.Encode(f.feed)

      
        
        108
        +	return enc.Encode(f.f)

      
        131
        109
         }

      
        132
        110
         

      
        133
        111
         func (f *FeedBuilder) Bytes() ([]byte, error) {

      
M app/atom_test.go
···
        14
        14
         

      
        15
        15
         func TestFeedBuilderAddEntryDefaults(t *testing.T) {

      
        16
        16
         	feed := NewFeed("test", "feed-id")

      
        17
        
        -	feed.AddEntry(FeedEntry{Title: "entry", Content: "body"})

      
        
        17
        +	feed.Add(FeedEntry{Title: "entry", Content: "body"})

      
        18
        18
         

      
        19
        19
         	is.Equal(t, 1, len(feed.f.Entries))

      
        20
        20
         	entry := feed.f.Entries[0]

      ···
        24
        24
         

      
        25
        25
         func TestFeedBuilderBytesAndWriteTo(t *testing.T) {

      
        26
        26
         	updated := time.Date(2026, 4, 20, 12, 30, 0, 0, time.UTC)

      
        27
        
        -	feed := NewFeed("test", "feed-id", WithFeedSubtitle("subtitle")).AddText("entry", "content", updated)

      
        
        27
        +	feed := NewFeed("test", "feed-id").

      
        
        28
        +		WithSubtitle("subtitle").

      
        
        29
        +		Add(FeedEntry{Title: "entry", Content: "content", Updated: updated})

      
        28
        30
         

      
        29
        31
         	raw, err := feed.Bytes()

      
        30
        32
         	is.Err(t, err, nil)

      ···
        40
        42
         func TestFeedBuilderRender(t *testing.T) {

      
        41
        43
         	r := httptest.NewRecorder()

      
        42
        44
         	err := NewFeed("test", "feed-id").

      
        43
        
        -		Add("entry", "entry-id", "content", time.Date(2026, 4, 20, 8, 0, 0, 0, time.UTC)).

      
        
        45
        +		Add(FeedEntry{

      
        
        46
        +			Title:   "entry",

      
        
        47
        +			ID:      "entry-id",

      
        
        48
        +			Content: "content",

      
        
        49
        +			Updated: time.Date(2026, 4, 20, 8, 0, 0, 0, time.UTC),

      
        
        50
        +		}).

      
        44
        51
         		Render(r)

      
        45
        52
         	is.Err(t, err, nil)

      
        46
        53
         

      ···
        52
        59
         

      
        53
        60
         func TestFeedEntryTextContent(t *testing.T) {

      
        54
        61
         	feed := NewFeed("test", "feed-id").

      
        55
        
        -		AddEntry(FeedEntry{

      
        
        62
        +		Add(FeedEntry{

      
        56
        63
         			Title:       "text entry",

      
        57
        64
         			Content:     "plain text content",

      
        58
        65
         			ContentType: "text",

      ···
        77
        84
         func TestFeedEntryHtmlContent(t *testing.T) {

      
        78
        85
         	htmlContent := "<p>Hello <strong>World</strong></p>"

      
        79
        86
         	feed := NewFeed("test", "feed-id").

      
        80
        
        -		AddEntry(FeedEntry{

      
        
        87
        +		Add(FeedEntry{

      
        81
        88
         			Title:       "html entry",

      
        82
        89
         			Content:     htmlContent,

      
        83
        90
         			ContentType: "html",

      ···
        102
        109
         func TestFeedMultipleEntriesWithMixedContentTypes(t *testing.T) {

      
        103
        110
         	updated := time.Date(2026, 4, 20, 12, 0, 0, 0, time.UTC)

      
        104
        111
         	feed := NewFeed("test", "feed-id").

      
        105
        
        -		AddEntry(FeedEntry{

      
        
        112
        +		Add(FeedEntry{

      
        106
        113
         			Title:       "text entry",

      
        107
        114
         			Content:     "plain text",

      
        108
        115
         			ContentType: "text",

      
        109
        116
         			Updated:     updated,

      
        110
        117
         		}).

      
        111
        
        -		AddEntry(FeedEntry{

      
        
        118
        +		Add(FeedEntry{

      
        112
        119
         			Title:       "html entry",

      
        113
        120
         			Content:     "<p>html content</p>",

      
        114
        121
         			ContentType: "html",

      
        115
        122
         			Updated:     updated,

      
        116
        123
         		}).

      
        117
        
        -		AddEntry(FeedEntry{

      
        
        124
        +		Add(FeedEntry{

      
        118
        125
         			Title:   "default entry",

      
        119
        126
         			Content: "default content",

      
        120
        127
         			Updated: updated,

      
M sources/telegram/telegram.go
···
        61
        61
         		if len(title) > 64 {

      
        62
        62
         			title = title[:64] + "..."

      
        63
        63
         		}

      
        64
        
        -		feed.AddEntry(app.FeedEntry{

      
        
        64
        +		feed.Add(app.FeedEntry{

      
        65
        65
         			Title:   title,

      
        66
        66
         			ID:      fmt.Sprintf("telegram-%d", m.MessageID),

      
        67
        67
         			Content: m.Text,

      
M sources/ztoe/ztoe.go
···
        56
        56
         			fmt.Sprintf("ztoe-%s-%s", group, subgroup))

      
        57
        57
         

      
        58
        58
         		for _, interval := range buildOutageIntervals(slots) {

      
        59
        
        -			feed.Add(

      
        60
        
        -				fmt.Sprintf("Power outage %s-%s", interval.Start, interval.End),

      
        61
        
        -				fmt.Sprintf("ztoe-%s-%s-%s-%s-%s",

      
        62
        
        -					group, subgroup, schedule.Date,

      
        63
        
        -					strings.ReplaceAll(interval.Start, ":", ""),

      
        64
        
        -					strings.ReplaceAll(interval.End, ":", ""),

      
        65
        
        -				),

      
        66
        
        -				fmt.Sprintf("Date: %s\nGroup: %s.%s\nTime: %s-%s",

      
        67
        
        -					schedule.Date, group, subgroup, interval.Start, interval.End),

      
        68
        
        -				intervalTime(schedule.Date, interval.Start),

      
        69
        
        -			)

      
        
        59
        +			feed.Add(app.FeedEntry{

      
        
        60
        +				Title:   fmt.Sprintf("Power outage %s-%s", interval.Start, interval.End),

      
        
        61
        +				ID:      fmt.Sprintf("ztoe-%s-%s-%s-%s-%s", group, subgroup, schedule.Date, strings.ReplaceAll(interval.Start, ":", ""), strings.ReplaceAll(interval.End, ":", "")),

      
        
        62
        +				Content: fmt.Sprintf("Date: %s\nGroup: %s.%s\nTime: %s-%s", schedule.Date, group, subgroup, interval.Start, interval.End),

      
        
        63
        +				Updated: intervalTime(schedule.Date, interval.Start),

      
        
        64
        +			})

      
        70
        65
         		}

      
        71
        66
         		if err := feed.Render(w); err != nil {

      
        72
        67
         			http.Error(w, "failed to render feed", http.StatusInternalServerError)