multiple discord senders

This commit is contained in:
2026-01-20 20:58:18 +01:00
parent 76933a04d8
commit 53e6eecb3f
9 changed files with 100 additions and 69 deletions

View File

@@ -1,16 +1,19 @@
WIKI_BASE_URL= WIKI_BASE_URL=
WIKI_TOKEN= WIKI_TOKEN=
WIKI_CONTENT_LIMIT=10 WIKI_CONTENT_LIMIT=10
WIKI_DISCORD_WEBHOOK=
REDMINE_BASE_URL= REDMINE_BASE_URL=
REDMINE_KEY= REDMINE_KEY=
REDMINE_CONTENT_LIMIT=10 REDMINE_CONTENT_LIMIT=10
REDMINE_DISCORD_WEBHOOK=
GITEA_TOKEN= GITEA_TOKEN=
GITEA_BASE_URL= GITEA_BASE_URL=
GITEA_REPOS=org/repo1,org/repo2 GITEA_REPOS=org/repo1,org/repo2
GITEA_CONTENT_LIMIT=10 GITEA_CONTENT_LIMIT=10
GITEA_DISCORD_WEBHOOK=
DISCORD_WEBHOOK= DISCORD_WEBHOOK=

View File

@@ -10,19 +10,22 @@ import (
) )
type Config struct { type Config struct {
WikiBaseURL string WikiBaseURL string
WikiToken string WikiToken string
WikiContentLimit int WikiContentLimit int
RedmineBaseURL string WikiDiscordWebhook string
RedmineKey string RedmineBaseURL string
RedmineContentLimit int RedmineKey string
GiteaToken string RedmineContentLimit int
GiteaBaseURL string RedmineDiscordWebhook string
GiteaRepos []string GiteaToken string
GiteaContentLimit int GiteaBaseURL string
DiscordWebhook string GiteaRepos []string
DiscordFake bool GiteaContentLimit int
Interval time.Duration GiteaDiscordWebhook string
DiscordWebhook string
DiscordFake bool
Interval time.Duration
} }
func envToInteger(key string, defaultValue int) int { func envToInteger(key string, defaultValue int) int {
@@ -40,6 +43,6 @@ func envToInteger(key string, defaultValue int) int {
func loadEnv() { func loadEnv() {
if err := godotenv.Load(); err != nil { if err := godotenv.Load(); err != nil {
log.Println("Warning: .env file not found, using environment variables") log.Println("[BOOT] .env file not found")
} }
} }

View File

@@ -24,8 +24,8 @@ func (f *GiteaFetcher) Name() string {
return "Gitea" return "Gitea"
} }
func (f *GiteaFetcher) Fetch() []Entry { func (f *GiteaFetcher) Fetch() []Message {
var entries []Entry var messages []Message
for _, repo := range f.Repos { for _, repo := range f.Repos {
repo = strings.TrimSpace(repo) repo = strings.TrimSpace(repo)
@@ -54,18 +54,19 @@ func (f *GiteaFetcher) Fetch() []Entry {
for _, content := range response { for _, content := range response {
cacheKey := "gitea_commit_" + url.QueryEscape(f.BaseURL+repo+"_"+content.Sha) cacheKey := "gitea_commit_" + url.QueryEscape(f.BaseURL+repo+"_"+content.Sha)
entry := f.TryCreateEntry( message := f.TryCreateMessage(
"gitea",
cacheKey, cacheKey,
content.Sha, content.Sha,
fmt.Sprintf("📝 [%s] - (%s) %s", f.Name(), repo, content.Commit.Message), fmt.Sprintf("📝 [%s] - (%s) %s", f.Name(), repo, content.Commit.Message),
content.HTMLURL, content.HTMLURL,
) )
if entry != nil { if message != nil {
entries = append(entries, *entry) messages = append(messages, *message)
} }
} }
} }
return entries return messages
} }

View File

@@ -7,16 +7,10 @@ import (
"net/http" "net/http"
) )
// Entry represents a single fetched item.
type Entry struct {
Title string
URL string
}
// Fetcher is the interface for a fetcher. // Fetcher is the interface for a fetcher.
type Fetcher interface { type Fetcher interface {
Name() string Name() string
Fetch() []Entry Fetch() []Message
} }
// BaseFetcher contains common fields for all fetchers. // BaseFetcher contains common fields for all fetchers.
@@ -27,10 +21,14 @@ type BaseFetcher struct {
ContentLimit int ContentLimit int
} }
// TryCreateEntry checks the cache and creates an Entry if the value has changed. // TryCreateMessage checks the cache and creates a Message if the value has changed.
func (b *BaseFetcher) TryCreateEntry(cacheKey, cacheValue, title, url string) *Entry { func (b *BaseFetcher) TryCreateMessage(channel, cacheKey, cacheValue, title, url string) *Message {
if b.Cache.TryUpdate(cacheKey, cacheValue) { if b.Cache.TryUpdate(cacheKey, cacheValue) {
return &Entry{Title: title, URL: url} return &Message{
Channel: channel,
Title: title,
URL: url,
}
} }
return nil return nil
} }

View File

@@ -21,8 +21,8 @@ func (f *RedmineFetcher) Name() string {
return "Redmine" return "Redmine"
} }
func (f *RedmineFetcher) Fetch() []Entry { func (f *RedmineFetcher) Fetch() []Message {
var entries []Entry var messages []Message
path := fmt.Sprintf("/issues.json?limit=%d&sort=updated_on:desc", f.ContentLimit) path := fmt.Sprintf("/issues.json?limit=%d&sort=updated_on:desc", f.ContentLimit)
req := FetcherRequest{ req := FetcherRequest{
BaseURL: f.BaseURL, BaseURL: f.BaseURL,
@@ -43,16 +43,17 @@ func (f *RedmineFetcher) Fetch() []Entry {
} }
for _, content := range response.Issues { for _, content := range response.Issues {
entry := f.TryCreateEntry( message := f.TryCreateMessage(
"redmine",
fmt.Sprintf("redmine_%d", content.ID), fmt.Sprintf("redmine_%d", content.ID),
content.UpdatedOn, content.UpdatedOn,
fmt.Sprintf("🎫 [%s] - #%d %s", f.Name(), content.ID, content.Subject), fmt.Sprintf("🎫 [%s] - #%d %s", f.Name(), content.ID, content.Subject),
fmt.Sprintf("%s/issues/%d", f.BaseURL, content.ID), fmt.Sprintf("%s/issues/%d", f.BaseURL, content.ID),
) )
if entry != nil { if message != nil {
entries = append(entries, *entry) messages = append(messages, *message)
} }
} }
return entries return messages
} }

View File

@@ -25,8 +25,8 @@ func (f *WikiFetcher) Name() string {
return "WikiJS" return "WikiJS"
} }
func (f *WikiFetcher) Fetch() []Entry { func (f *WikiFetcher) Fetch() []Message {
var entries []Entry var messages []Message
query := fmt.Sprintf(`{"query":"{ pages { list(orderBy: UPDATED, orderByDirection: DESC, limit: %d){ path, updatedAt, title }}}"}`, f.ContentLimit) query := fmt.Sprintf(`{"query":"{ pages { list(orderBy: UPDATED, orderByDirection: DESC, limit: %d){ path, updatedAt, title }}}"}`, f.ContentLimit)
req := FetcherRequest{ req := FetcherRequest{
@@ -50,17 +50,18 @@ func (f *WikiFetcher) Fetch() []Entry {
} }
for _, content := range response.Data.Pages.List { for _, content := range response.Data.Pages.List {
entry := f.TryCreateEntry( message := f.TryCreateMessage(
"wiki",
"wiki_"+content.Path, "wiki_"+content.Path,
content.UpdatedAt, content.UpdatedAt,
fmt.Sprintf("📖 [%s] - %s", f.Name(), content.Title), fmt.Sprintf("📖 [%s] - %s", f.Name(), content.Title),
fmt.Sprintf("%s/%s", f.BaseURL, content.Path), fmt.Sprintf("%s/%s", f.BaseURL, content.Path),
) )
if entry != nil { if message != nil {
entries = append(entries, *entry) messages = append(messages, *message)
} }
} }
return entries return messages
} }

13
lib/message.go Normal file
View File

@@ -0,0 +1,13 @@
package lib
import "fmt"
type Message struct {
Channel string
Title string
URL string
}
func (m Message) ToDiscord() string {
return fmt.Sprintf("[%s](%s)", m.Title, m.URL)
}

View File

@@ -1,7 +1,6 @@
package lib package lib
import ( import (
"fmt"
"log" "log"
"os" "os"
"strings" "strings"
@@ -62,34 +61,50 @@ func getCache() Cache {
return cache return cache
} }
func getDiscordSender(config Config) DiscordSender { func getMessages(fetchers []Fetcher) []Message {
return DiscordSender{ messages := []Message{}
Webhook: config.DiscordWebhook,
Fake: config.DiscordFake,
}
}
func getMessages(fetchers []Fetcher) []string {
messages := []string{}
for _, fetcher := range fetchers { for _, fetcher := range fetchers {
entries := fetcher.Fetch() fetchedMessages := fetcher.Fetch()
for _, entry := range entries { for _, message := range fetchedMessages {
messages = append(messages, fmt.Sprintf("[%s](%s)", entry.Title, entry.URL)) messages = append(messages, message)
} }
} }
return messages return messages
} }
func sendMessageToDiscord(sender DiscordSender, message Message) {
log.Printf("[SCHEDULED] [SEND] (%s) %s\n", message.Channel, message.ToDiscord())
if sender.Fake {
return
}
sender.Send(message.ToDiscord())
}
func Runner() { func Runner() {
loadEnv() loadEnv()
config := getConfig() config := getConfig()
cache := getCache() cache := getCache()
discord_sender := getDiscordSender(config)
fetchers := getFetchers(&config, &cache) fetchers := getFetchers(&config, &cache)
default_discord_sender := NewDiscordSender(config.DiscordWebhook, config.DiscordFake)
wiki_discord_sender := NewDiscordSender(config.WikiDiscordWebhook, config.DiscordFake)
redmine_discord_sender := NewDiscordSender(config.RedmineDiscordWebhook, config.DiscordFake)
gita_discord_sender := NewDiscordSender(config.GiteaDiscordWebhook, config.DiscordFake)
for { for {
log.Println("Run updater...") log.Println("[SCHEDULED] Run updater...")
messages := getMessages(fetchers) messages := getMessages(fetchers)
discord_sender.SendBatch(messages) for _, message := range messages {
switch message.Channel {
case "wiki":
sendMessageToDiscord(wiki_discord_sender, message)
case "redmine":
sendMessageToDiscord(redmine_discord_sender, message)
case "gitea":
sendMessageToDiscord(gita_discord_sender, message)
default:
sendMessageToDiscord(default_discord_sender, message)
}
}
cache.Save() cache.Save()
time.Sleep(config.Interval) time.Sleep(config.Interval)
} }

View File

@@ -3,10 +3,16 @@ package lib
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"log"
"net/http" "net/http"
) )
func NewDiscordSender(webhook string, fake bool) DiscordSender {
return DiscordSender{
Webhook: webhook,
Fake: fake,
}
}
type DiscordSender struct { type DiscordSender struct {
Webhook string Webhook string
Fake bool Fake bool
@@ -19,13 +25,3 @@ func (d DiscordSender) Send(msg string) {
} }
http.Post(d.Webhook, "application/json", bytes.NewBuffer(b)) http.Post(d.Webhook, "application/json", bytes.NewBuffer(b))
} }
func (d DiscordSender) SendBatch(msgs []string) {
for _, msg := range msgs {
log.Println("Sending to Discord:", msg)
if d.Fake {
continue
}
d.Send(msg)
}
}