Skip to content

Commit

Permalink
feat(atom): Introduce new generic Atom feed reader plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
marvin-roesch committed Oct 25, 2024
1 parent cd96068 commit 7abc811
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 4 deletions.
12 changes: 8 additions & 4 deletions common/discord.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,17 @@ func CreateDiscordClient(webhook string, mentions DiscordMentions) DiscordClient
}

func (discord *DiscordClient) Send(text, name, avatar string, embed interface{}) error {
return discord.trySend(text, name, avatar, embed, 1)
return discord.trySend(text, name, fmt.Sprintf("%s/%s.png", avatarBaseUrl, avatar), embed, 1)
}

func (discord *DiscordClient) trySend(text, name, avatar string, embed interface{}, try int) error {
func (discord *DiscordClient) SendWithCustomAvatar(text, name, avatarURL string, embed interface{}) error {
return discord.trySend(text, name, avatarURL, embed, 1)
}

func (discord *DiscordClient) trySend(text, name, avatarURL string, embed interface{}, try int) error {
body := map[string]interface{}{
"username": name,
"avatar_url": fmt.Sprintf("%s/%s.png", avatarBaseUrl, avatar),
"avatar_url": avatarURL,
"content": fmt.Sprintf("%s%s", text, discord.mentionSuffix),
"allowed_mentions": discord.mentions,
}
Expand Down Expand Up @@ -98,7 +102,7 @@ func (discord *DiscordClient) trySend(text, name, avatar string, embed interface
discord.info.Printf("Being rate late limited by Discord, waiting for %fs\n", data.Delay)
time.Sleep(time.Duration(data.Delay * float32(time.Second)))

return discord.trySend(text, name, avatar, embed, try+1)
return discord.trySend(text, name, avatarURL, embed, try+1)
}

if res.StatusCode != http.StatusNoContent {
Expand Down
3 changes: 3 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ func main() {

configLoader := ConfigLoader{
AvailablePlugins: map[string]func() Plugin{
"atom": func() Plugin {
return &AtomPlugin{}
},
"progress": func() Plugin {
return &ProgressPlugin{}
},
Expand Down
162 changes: 162 additions & 0 deletions plugins/atom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package plugins

import (
"fmt"
"github.com/mmcdole/gofeed/atom"
"net/http"
"sort"
"time"
)

type AtomPlugin struct {
FeedURL string `mapstructure:"feedUrl"`
Nickname string
AvatarURL string `mapstructure:"avatarUrl"`
Message string

client *http.Client
}

func (plugin *AtomPlugin) Name() string {
return "atom"
}

func (plugin *AtomPlugin) Validate() error {
if len(plugin.FeedURL) == 0 {
return fmt.Errorf("feed URL for Atom integration must not be empty")
}

return nil
}

func (plugin *AtomPlugin) OffsetPrototype() interface{} {
return map[string]bool{}
}

type AtomPost struct {
Timestamp *time.Time
ID string
Title string
Link string
}

type ByTimestamp []AtomPost

func (posts ByTimestamp) Len() int {
return len(posts)
}

func (posts ByTimestamp) Less(i, j int) bool {
if posts[i].Timestamp == nil {
return true
}

if posts[j].Timestamp == nil {
return false
}

return posts[i].Timestamp.Before(*posts[j].Timestamp)
}

func (posts ByTimestamp) Swap(i, j int) {
posts[i], posts[j] = posts[j], posts[i]
}

func (plugin *AtomPlugin) Check(offset interface{}, context PluginContext) (interface{}, error) {
context.Info.Printf("Checking Atom feed at %s for updates...", plugin.FeedURL)

plugin.client = &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}

res, err := plugin.client.Get(plugin.FeedURL)
if err != nil {
return offset, fmt.Errorf("could not read Atoom feed at '%s': %w", plugin.FeedURL, err)
}
defer res.Body.Close()

if res.StatusCode == 404 {
logLevel := context.Info
if offset == nil {
logLevel = context.Error
}
logLevel.Printf("Could not find Atom feed at '%s'. Site might be down.", plugin.FeedURL)
return offset, nil
}

fp := atom.Parser{}
atomFeed, err := fp.Parse(res.Body)
if err != nil {
return offset, err
}

if len(atomFeed.Entries) == 0 {
context.Info.Printf("No entries in Atoom feed at '%s'.", plugin.FeedURL)
return offset, nil
}

handledEntries := make(map[string]bool)
if offset != nil {
handledEntries = offset.(map[string]bool)
}

var sortedEntries []AtomPost

for _, entry := range atomFeed.Entries {
if handled, present := handledEntries[entry.ID]; present && handled {
continue
}

sortedEntries = append([]AtomPost{{
Timestamp: entry.PublishedParsed,
ID: entry.ID,
Title: entry.Title,
Link: entry.Links[0].Href,
}}, sortedEntries...)
}

sort.Sort(ByTimestamp(sortedEntries))

if len(sortedEntries) == 0 {
context.Info.Printf("No posts to report from Atom feed at '%s'.", plugin.FeedURL)
return offset, nil
}

context.Info.Printf("Reporting posts from Atom feed at '%s'...", plugin.FeedURL)

if len(plugin.Nickname) == 0 {
plugin.Nickname = atomFeed.Title
context.Info.Printf(
"No nickname was provided for Atom feed at '%s', using feed title '%s' as fallback nickname",
plugin.FeedURL,
plugin.Nickname,
)
}

if len(plugin.Message) == 0 {
plugin.Message = "A new blog post was published"
context.Info.Printf(
"No message was provided for Atom feed at '%s', using default",
plugin.FeedURL,
)
}

for _, entry := range sortedEntries {
if err = context.Discord.SendWithCustomAvatar(
fmt.Sprintf("%s %s", plugin.Message, entry.Link),
plugin.Nickname,
plugin.AvatarURL,
nil,
); err != nil {
return handledEntries, err
}

handledEntries[entry.ID] = true

context.Info.Printf("Reported post '%s' from feed at '%s'", entry.Title, plugin.FeedURL)
}

return handledEntries, nil
}

0 comments on commit 7abc811

Please sign in to comment.