multiple content per fetcher

This commit is contained in:
2026-01-20 18:10:26 +01:00
parent 4f168197b8
commit 76933a04d8
7 changed files with 129 additions and 99 deletions

View File

@@ -1,12 +1,18 @@
WIKI_BASE_URL= WIKI_BASE_URL=
WIKI_TOKEN= WIKI_TOKEN=
WIKI_CONTENT_LIMIT=10
REDMINE_BASE_URL= REDMINE_BASE_URL=
REDMINE_KEY= REDMINE_KEY=
REDMINE_CONTENT_LIMIT=10
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
DISCORD_WEBHOOK= DISCORD_WEBHOOK=
INTERVAL_MINUTES=5 INTERVAL_MINUTES=5

View File

@@ -1,16 +1,45 @@
package lib package lib
import "time" import (
"log"
"os"
"strconv"
"time"
"github.com/joho/godotenv"
)
type Config struct { type Config struct {
WikiBaseURL string WikiBaseURL string
WikiToken string WikiToken string
RedmineBaseURL string WikiContentLimit int
RedmineKey string RedmineBaseURL string
GiteaToken string RedmineKey string
GiteaBaseURL string RedmineContentLimit int
GiteaRepos []string GiteaToken string
DiscordWebhook string GiteaBaseURL string
DiscordFake bool GiteaRepos []string
Interval time.Duration GiteaContentLimit int
DiscordWebhook string
DiscordFake bool
Interval time.Duration
}
func envToInteger(key string, defaultValue int) int {
valueStr := os.Getenv(key)
if valueStr == "" {
return defaultValue
}
value, err := strconv.Atoi(valueStr)
if err != nil {
return defaultValue
}
return value
}
func loadEnv() {
if err := godotenv.Load(); err != nil {
log.Println("Warning: .env file not found, using environment variables")
}
} }

View File

@@ -33,41 +33,37 @@ func (f *GiteaFetcher) Fetch() []Entry {
continue continue
} }
path := fmt.Sprintf("/api/v1/repos/%s/commits?limit=1", repo)
headers := map[string]string{}
if f.Token != "" {
headers["Authorization"] = "token " + f.Token
}
req := FetcherRequest{ req := FetcherRequest{
BaseURL: f.BaseURL, BaseURL: f.BaseURL,
Path: path, Path: fmt.Sprintf("/api/v1/repos/%s/commits?limit=%d", repo, f.ContentLimit),
Method: http.MethodGet, Method: http.MethodGet,
Headers: headers, Headers: map[string]string{
"Authorization": "token " + f.Token,
},
} }
var resp GiteaResponse var response GiteaResponse
if err := req.Run(&resp); err != nil { if err := req.Run(&response); err != nil {
continue continue
} }
if len(resp) == 0 { if len(response) == 0 {
continue continue
} }
commit := resp[0] for _, content := range response {
cacheKey := "gitea_" + url.QueryEscape(f.BaseURL+path) cacheKey := "gitea_commit_" + url.QueryEscape(f.BaseURL+repo+"_"+content.Sha)
entry := f.TryCreateEntry( entry := f.TryCreateEntry(
cacheKey, cacheKey,
commit.Sha, content.Sha,
fmt.Sprintf("📝 [%s] - (%s) %s", f.Name(), repo, commit.Commit.Message), fmt.Sprintf("📝 [%s] - (%s) %s", f.Name(), repo, content.Commit.Message),
commit.HTMLURL, content.HTMLURL,
) )
if entry != nil { if entry != nil {
entries = append(entries, *entry) entries = append(entries, *entry)
}
} }
} }

View File

@@ -21,9 +21,10 @@ type Fetcher interface {
// BaseFetcher contains common fields for all fetchers. // BaseFetcher contains common fields for all fetchers.
type BaseFetcher struct { type BaseFetcher struct {
BaseURL string BaseURL string
Token string Token string
Cache *Cache Cache *Cache
ContentLimit int
} }
// TryCreateEntry checks the cache and creates an Entry if the value has changed. // TryCreateEntry checks the cache and creates an Entry if the value has changed.

View File

@@ -22,34 +22,37 @@ func (f *RedmineFetcher) Name() string {
} }
func (f *RedmineFetcher) Fetch() []Entry { func (f *RedmineFetcher) Fetch() []Entry {
var entries []Entry
path := fmt.Sprintf("/issues.json?limit=%d&sort=updated_on:desc", f.ContentLimit)
req := FetcherRequest{ req := FetcherRequest{
BaseURL: f.BaseURL, BaseURL: f.BaseURL,
Path: "/issues.json?limit=1&sort=updated_on:desc", Path: path,
Method: http.MethodGet, Method: http.MethodGet,
Headers: map[string]string{ Headers: map[string]string{
"X-Redmine-API-Key": f.Token, "X-Redmine-API-Key": f.Token,
}, },
} }
var resp RedmineResponse var response RedmineResponse
if err := req.Run(&resp); err != nil { if err := req.Run(&response); err != nil {
return nil return nil
} }
if len(resp.Issues) == 0 { if len(response.Issues) == 0 {
return nil return nil
} }
issue := resp.Issues[0] for _, content := range response.Issues {
entry := f.TryCreateEntry( entry := f.TryCreateEntry(
"redmine", fmt.Sprintf("redmine_%d", content.ID),
issue.UpdatedOn, content.UpdatedOn,
fmt.Sprintf("🎫 [%s] - #%d %s", f.Name(), issue.ID, issue.Subject), fmt.Sprintf("🎫 [%s] - #%d %s", f.Name(), content.ID, content.Subject),
fmt.Sprintf("%s/issues/%d", f.BaseURL, issue.ID), fmt.Sprintf("%s/issues/%d", f.BaseURL, content.ID),
) )
if entry != nil {
if entry != nil { entries = append(entries, *entry)
return []Entry{*entry} }
} }
return nil
return entries
} }

View File

@@ -26,7 +26,8 @@ func (f *WikiFetcher) Name() string {
} }
func (f *WikiFetcher) Fetch() []Entry { func (f *WikiFetcher) Fetch() []Entry {
query := `{"query":"{ pages { list(orderBy: UPDATED, orderByDirection: DESC, limit: 1){ path, updatedAt, title }}}"}` var entries []Entry
query := fmt.Sprintf(`{"query":"{ pages { list(orderBy: UPDATED, orderByDirection: DESC, limit: %d){ path, updatedAt, title }}}"}`, f.ContentLimit)
req := FetcherRequest{ req := FetcherRequest{
BaseURL: f.BaseURL, BaseURL: f.BaseURL,
@@ -39,25 +40,27 @@ func (f *WikiFetcher) Fetch() []Entry {
Body: []byte(query), Body: []byte(query),
} }
var resp WikiResponse var response WikiResponse
if err := req.Run(&resp); err != nil { if err := req.Run(&response); err != nil {
return nil return nil
} }
if len(resp.Data.Pages.List) == 0 { if len(response.Data.Pages.List) == 0 {
return nil return nil
} }
page := resp.Data.Pages.List[0] for _, content := range response.Data.Pages.List {
entry := f.TryCreateEntry( entry := f.TryCreateEntry(
"wiki", "wiki_"+content.Path,
page.UpdatedAt, content.UpdatedAt,
fmt.Sprintf("📖 [%s] - %s", f.Name(), page.Title), fmt.Sprintf("📖 [%s] - %s", f.Name(), content.Title),
fmt.Sprintf("%s/%s", f.BaseURL, page.Path), fmt.Sprintf("%s/%s", f.BaseURL, content.Path),
) )
if entry != nil { if entry != nil {
return []Entry{*entry} entries = append(entries, *entry)
}
} }
return nil
return entries
} }

View File

@@ -4,30 +4,25 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"strconv"
"strings" "strings"
"time" "time"
"github.com/joho/godotenv"
) )
func getConfig() Config { func getConfig() Config {
intervalMinutes, err := strconv.Atoi(os.Getenv("INTERVAL_MINUTES"))
if err != nil {
intervalMinutes = 5
}
return Config{ return Config{
WikiBaseURL: os.Getenv("WIKI_BASE_URL"), WikiBaseURL: os.Getenv("WIKI_BASE_URL"),
WikiToken: os.Getenv("WIKI_TOKEN"), WikiToken: os.Getenv("WIKI_TOKEN"),
RedmineBaseURL: os.Getenv("REDMINE_BASE_URL"), WikiContentLimit: envToInteger("WIKI_CONTENT_LIMIT", 10),
RedmineKey: os.Getenv("REDMINE_KEY"), RedmineBaseURL: os.Getenv("REDMINE_BASE_URL"),
GiteaToken: os.Getenv("GITEA_TOKEN"), RedmineKey: os.Getenv("REDMINE_KEY"),
GiteaBaseURL: os.Getenv("GITEA_BASE_URL"), RedmineContentLimit: envToInteger("REDMINE_CONTENT_LIMIT", 10),
GiteaRepos: strings.Split(os.Getenv("GITEA_REPOS"), ","), GiteaToken: os.Getenv("GITEA_TOKEN"),
DiscordWebhook: os.Getenv("DISCORD_WEBHOOK"), GiteaBaseURL: os.Getenv("GITEA_BASE_URL"),
DiscordFake: os.Getenv("DISCORD_FAKE") == "true", GiteaRepos: strings.Split(os.Getenv("GITEA_REPOS"), ","),
Interval: time.Duration(intervalMinutes) * time.Minute, GiteaContentLimit: envToInteger("GITEA_CONTENT_LIMIT", 10),
DiscordWebhook: os.Getenv("DISCORD_WEBHOOK"),
DiscordFake: os.Getenv("DISCORD_FAKE") == "true",
Interval: time.Duration(envToInteger("INTERVAL_MINUTES", 5)) * time.Minute,
} }
} }
@@ -35,24 +30,27 @@ func getFetchers(config *Config, cache *Cache) []Fetcher {
return []Fetcher{ return []Fetcher{
&GiteaFetcher{ &GiteaFetcher{
BaseFetcher: BaseFetcher{ BaseFetcher: BaseFetcher{
BaseURL: config.GiteaBaseURL, BaseURL: config.GiteaBaseURL,
Token: config.GiteaToken, Token: config.GiteaToken,
Cache: cache, Cache: cache,
ContentLimit: config.GiteaContentLimit,
}, },
Repos: config.GiteaRepos, Repos: config.GiteaRepos,
}, },
&WikiFetcher{ &WikiFetcher{
BaseFetcher: BaseFetcher{ BaseFetcher: BaseFetcher{
BaseURL: config.WikiBaseURL, BaseURL: config.WikiBaseURL,
Token: config.WikiToken, Token: config.WikiToken,
Cache: cache, Cache: cache,
ContentLimit: config.WikiContentLimit,
}, },
}, },
&RedmineFetcher{ &RedmineFetcher{
BaseFetcher: BaseFetcher{ BaseFetcher: BaseFetcher{
BaseURL: config.RedmineBaseURL, BaseURL: config.RedmineBaseURL,
Token: config.RedmineKey, Token: config.RedmineKey,
Cache: cache, Cache: cache,
ContentLimit: config.RedmineContentLimit,
}, },
}, },
} }
@@ -82,12 +80,6 @@ func getMessages(fetchers []Fetcher) []string {
return messages return messages
} }
func loadEnv() {
if err := godotenv.Load(); err != nil {
log.Println("Warning: .env file not found, using environment variables")
}
}
func Runner() { func Runner() {
loadEnv() loadEnv()
config := getConfig() config := getConfig()