commit 12ef16cbeef97b58a5e660230eaa84ceb80afdbc Author: Alexander NeonXP Kiryukhin Date: Thu Jun 27 02:20:00 2024 +0300 first commit diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..828f625 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +# syntax=docker/dockerfile:1 + +FROM golang:1.22.4 + +WORKDIR /app + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +RUN CGO_ENABLED=0 GOOS=linux go build -o /rss2world + +ENV RSS_URL="https://neonxp.ru/feed.atom" +ENV CHECK_INTERVAL="5m" +ENV SEQ_FILE="/store/seq.txt" +ENV TELEGRAM_TOKEN="279146841:AAE9Yd2W........_gAhbO3usM" +ENV TELEGRAM_GROUPS="-1001003209449" + +CMD ["/rss2world"] \ No newline at end of file diff --git a/app/app.go b/app/app.go new file mode 100644 index 0000000..1ea0ee8 --- /dev/null +++ b/app/app.go @@ -0,0 +1,125 @@ +package app + +import ( + "bytes" + "context" + "os" + "text/template" + "time" + + tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" + "github.com/mmcdole/gofeed" + "gitrepo.ru/neonxp/rss2world/templates" +) + +type App struct { + config *Config + telegram *tgbotapi.BotAPI + templates *template.Template +} + +func New(cfg *Config) (*App, error) { + bot, err := tgbotapi.NewBotAPI(cfg.Telegram.BotToken) + if err != nil { + return nil, err + } + + tpl, err := template.ParseFS(templates.Templates, "*.gotmpl") + if err != nil { + return nil, err + } + + return &App{ + config: cfg, + telegram: bot, + templates: tpl, + }, nil +} + +func (a *App) Run(ctx context.Context) error { + ticker := time.NewTicker(a.config.RSS.CheckInterval) + for { + select { + case <-ctx.Done(): + return nil + case <-ticker.C: + if err := a.iteration(); err != nil { + return err + } + } + } +} + +func (a *App) iteration() error { + + seq, err := a.readSeqFile() + if os.IsNotExist(err) { + seq = "" + err = nil + } + if err != nil { + return err + } + + items, err := a.findNewItems(seq) + if err != nil || len(items) == 0 { + return err + } + + for i := len(items) - 1; i >= 0; i-- { + if err := a.processItem(items[i]); err != nil { + return err + } + if err := a.writeSeqFile(items[i].GUID); err != nil { + return err + } + } + + return nil +} + +func (a *App) processItem(item *gofeed.Item) error { + buf := bytes.NewBufferString("") + if err := a.templates.ExecuteTemplate(buf, "telegram", item); err != nil { + return err + } + for _, group := range a.config.Telegram.TargetGroups { + msg := tgbotapi.NewMessage(group, buf.String()) + msg.ParseMode = tgbotapi.ModeHTML + if _, err := a.telegram.Send(msg); err != nil { + return err + } + } + + return nil +} + +func (a *App) readSeqFile() (string, error) { + seqVal, err := os.ReadFile(a.config.RSS.SeqFile) + if err != nil { + return "", err + } + + return string(seqVal), nil +} + +func (a *App) writeSeqFile(seqVal string) error { + return os.WriteFile(a.config.RSS.SeqFile, []byte(seqVal), 0644) +} + +func (a *App) findNewItems(from string) ([]*gofeed.Item, error) { + fp := gofeed.NewParser() + feed, err := fp.ParseURL(a.config.RSS.URL) + if err != nil { + return nil, err + } + out := make([]*gofeed.Item, 0, len(feed.Items)) + for _, item := range feed.Items { + if item.GUID == from { + break + } + out = append(out, item) + } + + return out, nil +} diff --git a/app/config.go b/app/config.go new file mode 100644 index 0000000..66531aa --- /dev/null +++ b/app/config.go @@ -0,0 +1,25 @@ +package app + +import ( + "gitrepo.ru/neonxp/rss2world/internal/rss" + "gitrepo.ru/neonxp/rss2world/internal/telegram" + + "github.com/caarlos0/env/v11" +) + +type Config struct { + RSS *rss.Config + Telegram *telegram.Config +} + +func NewConfig() (*Config, error) { + cfg := &Config{RSS: &rss.Config{}, Telegram: &telegram.Config{}} + if err := env.Parse(cfg.RSS); err != nil { + return nil, err + } + if err := env.Parse(cfg.Telegram); err != nil { + return nil, err + } + + return cfg, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ea89cc8 --- /dev/null +++ b/go.mod @@ -0,0 +1,20 @@ +module gitrepo.ru/neonxp/rss2world + +go 1.22.4 + +require ( + github.com/caarlos0/env/v11 v11.1.0 + github.com/mmcdole/gofeed v1.3.0 +) + +require ( + github.com/PuerkitoBio/goquery v1.8.0 // indirect + github.com/andybalholm/cascadia v1.3.1 // indirect + github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 + github.com/json-iterator/go v1.1.12 // indirect + github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + golang.org/x/net v0.4.0 // indirect + golang.org/x/text v0.5.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1e4dd4c --- /dev/null +++ b/go.sum @@ -0,0 +1,35 @@ +github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U= +github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= +github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= +github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= +github.com/caarlos0/env/v11 v11.1.0 h1:a5qZqieE9ZfzdvbbdhTalRrHT5vu/4V1/ad1Ka6frhI= +github.com/caarlos0/env/v11 v11.1.0/go.mod h1:LwgkYk1kDvfGpHthrWWLof3Ny7PezzFwS4QrsJdHTMo= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= +github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/mmcdole/gofeed v1.3.0 h1:5yn+HeqlcvjMeAI4gu6T+crm7d0anY85+M+v6fIFNG4= +github.com/mmcdole/gofeed v1.3.0/go.mod h1:9TGv2LcJhdXePDzxiuMnukhV2/zb6VtnZt1mS+SjkLE= +github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 h1:Zr92CAlFhy2gL+V1F+EyIuzbQNbSgP4xhTODZtrXUtk= +github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23/go.mod h1:v+25+lT2ViuQ7mVxcncQ8ch1URund48oH+jhjiwEgS8= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/internal/rss/config.go b/internal/rss/config.go new file mode 100644 index 0000000..e1fd34c --- /dev/null +++ b/internal/rss/config.go @@ -0,0 +1,9 @@ +package rss + +import "time" + +type Config struct { + URL string `env:"RSS_URL"` + CheckInterval time.Duration `env:"CHECK_INTERVAL"` + SeqFile string `env:"SEQ_FILE"` +} diff --git a/internal/telegram/config.go b/internal/telegram/config.go new file mode 100644 index 0000000..9fd6029 --- /dev/null +++ b/internal/telegram/config.go @@ -0,0 +1,6 @@ +package telegram + +type Config struct { + BotToken string `env:"TELEGRAM_TOKEN"` + TargetGroups []int64 `env:"TELEGRAM_GROUPS"` +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..017772e --- /dev/null +++ b/main.go @@ -0,0 +1,28 @@ +package main + +import ( + "context" + "os" + "os/signal" + + "gitrepo.ru/neonxp/rss2world/app" +) + +func main() { + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) + defer cancel() + + cfg, err := app.NewConfig() + if err != nil { + panic(err) + } + + a, err := app.New(cfg) + if err != nil { + panic(err) + } + + if err := a.Run(ctx); err != nil { + panic(err) + } +} diff --git a/store/seq.txt b/store/seq.txt new file mode 100644 index 0000000..aeb20ea --- /dev/null +++ b/store/seq.txt @@ -0,0 +1 @@ +https://neonxp.ru/posts/2024/06/02/%D0%BA%D0%BD%D0%B8%D0%B6%D0%BD%D1%8B%D0%B5_%D1%80%D0%B5%D0%BA%D0%BE%D0%BC%D0%B5%D0%BD%D0%B4%D0%B0%D1%86%D0%B8%D0%B8_1/ \ No newline at end of file diff --git a/templates/fs.go b/templates/fs.go new file mode 100644 index 0000000..197a5e1 --- /dev/null +++ b/templates/fs.go @@ -0,0 +1,6 @@ +package templates + +import "embed" + +//go:embed *.gotmpl +var Templates embed.FS diff --git a/templates/telegram.gotmpl b/templates/telegram.gotmpl new file mode 100644 index 0000000..9e2b91a --- /dev/null +++ b/templates/telegram.gotmpl @@ -0,0 +1,5 @@ +{{define "telegram"}} +Новый пост в блоге Уголок NeonXP: + +{{.Title}} +{{end}} \ No newline at end of file