Compare commits

..

5 Commits

  1. 15
      go.mod
  2. 6
      go.sum
  3. 30
      internal/config/config.go
  4. 100
      internal/db/db.go
  5. 16
      internal/models/config.go
  6. 33
      internal/models/rss.go
  7. 27
      internal/rss/rss.go
  8. 48
      internal/send/send.go
  9. 263
      main.go

15
go.mod

@ -1,16 +1,19 @@ @@ -1,16 +1,19 @@
module ssender
go 1.17
go 1.25
require (
github.com/SevereCloud/vksdk/v2 v2.13.1
github.com/boltdb/bolt v1.3.1
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/jessevdk/go-flags v1.5.0
gopkg.in/yaml.v2 v2.4.0
)
require (
github.com/SevereCloud/vksdk/v2 v2.13.1 // indirect
github.com/boltdb/bolt v1.3.1 // indirect
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect
github.com/jessevdk/go-flags v1.5.0 // indirect
github.com/klauspost/compress v1.14.2 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

6
go.sum

@ -2,18 +2,22 @@ github.com/SevereCloud/vksdk/v2 v2.13.1 h1:D11NaP275mW01v2hRF0ycDHdJaIyZEvasZV4M @@ -2,18 +2,22 @@ github.com/SevereCloud/vksdk/v2 v2.13.1 h1:D11NaP275mW01v2hRF0ycDHdJaIyZEvasZV4M
github.com/SevereCloud/vksdk/v2 v2.13.1/go.mod h1:UyOgSj/CYt2dByu3Fyf/y1yT1NoahVi4zECvvrbtPU4=
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/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/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/klauspost/compress v1.14.2 h1:S0OHlFk/Gbon/yauFJ4FfJJF5V0fc5HbBTJazi28pRw=
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
@ -24,7 +28,9 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w @@ -24,7 +28,9 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

30
internal/config/config.go

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
package config
import (
"os"
"ssender/internal/models"
"gopkg.in/yaml.v2"
)
func NewConfig(configPath string) (*models.Config, error) {
// Create config structure
config := &models.Config{}
// Open config file
file, err := os.Open(configPath)
if err != nil {
return nil, err
}
defer file.Close()
// Init new YAML decode
d := yaml.NewDecoder(file)
// Start YAML decoding from file
if err := d.Decode(&config); err != nil {
return nil, err
}
return config, nil
}

100
internal/db/db.go

@ -0,0 +1,100 @@ @@ -0,0 +1,100 @@
package db
import (
"encoding/json"
"log"
"ssender/internal/models"
"github.com/boltdb/bolt"
)
func InitDb(rss models.Rss2, dbpath string) error {
log.Println("Initialize DB")
db, err := bolt.Open(dbpath, 0600, nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
err = db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte("rss"))
if err != nil {
return err
}
for _, v := range rss.ItemList {
encoded, err := json.Marshal(v)
if err != nil {
return err
}
err = b.Put([]byte(v.Link), encoded)
if err != nil {
return err
}
}
return nil
})
if err != nil {
return err
}
return nil
}
func FindItems(rss models.Rss2, dbpath string) (*models.SendItems, error) {
var sendItems models.SendItems
db, err := bolt.Open(dbpath, 0600, nil)
if err != nil {
return nil, err
}
defer db.Close()
for _, v := range rss.ItemList {
err = db.View(func(tx *bolt.Tx) error {
// Assume bucket exists and has keys
b := tx.Bucket([]byte("rss"))
c := b.Cursor()
flag := false
for key, _ := c.First(); key != nil; key, _ = c.Next() {
if v.Link == string(key) {
flag = true
break
}
}
if flag {
sendItems.ItemList = append(sendItems.ItemList, v)
}
return nil
})
if err != nil {
return nil, err
}
}
return &sendItems, nil
}
func UpdateDb(dbpath string, senditems *models.SendItems) error {
log.Println("Update DB")
db, err := bolt.Open(dbpath, 0600, nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
err = db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("rss"))
for _, v := range senditems.ItemList {
encoded, err := json.Marshal(v)
if err != nil {
return err
}
err = b.Put([]byte(v.Link), encoded)
if err != nil {
return err
}
}
return nil
})
if err != nil {
return err
}
return nil
}

16
internal/models/config.go

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
package models
type Config struct {
Dbpath string `yaml:"dbpath"`
Telegram struct {
Send bool `yaml:"send"`
SendDebug bool `yaml:"senddebug"`
ChatId int64 `yaml:"chatid"`
Token string `yaml:"token"`
} `yaml:"telegram"`
VK struct {
Send bool `yaml:"send"`
Token string `yaml:"token"`
OwnerId int64 `yaml:"ownerid"`
} `yaml:"vk"`
}

33
internal/models/rss.go

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
package models
import (
"encoding/xml"
"html/template"
)
type Rss2 struct {
XMLName xml.Name `xml:"rss"`
Version string `xml:"version,attr"`
// Required
Title string `xml:"channel>title"`
Link string `xml:"channel>link"`
Description string `xml:"channel>description"`
// Optional
PubDate string `xml:"channel>pubDate"`
ItemList []Item `xml:"channel>item"`
}
type Item struct {
// Required
Title string `xml:"title"`
Link string `xml:"link"`
Description template.HTML `xml:"description"`
// Optional
Content template.HTML `xml:"encoded"`
PubDate string `xml:"pubDate"`
Comments string `xml:"comments"`
}
type SendItems struct {
ItemList []Item
}

27
internal/rss/rss.go

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
package rss
import (
"encoding/xml"
"os"
"ssender/internal/models"
)
func NewRSS(rssPath string) (*models.Rss2, error) {
rss := &models.Rss2{}
// Open rss2 file
file, err := os.Open(rssPath)
if err != nil {
return nil, err
}
defer file.Close()
d := xml.NewDecoder(file)
// Start RSS decoding from file
if err := d.Decode(&rss); err != nil {
return nil, err
}
return rss, nil
}

48
internal/send/send.go

@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
package send
import (
"log"
"ssender/internal/models"
"github.com/SevereCloud/vksdk/v2/api"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
func RunSend(senditems *models.SendItems, config models.Config) error {
for _, v := range senditems.ItemList {
if config.Telegram.Send {
log.Println("Send to telegram")
bot, err := tgbotapi.NewBotAPI(config.Telegram.Token)
if err != nil {
return err
}
bot.Debug = config.Telegram.SendDebug
// s := "<b>" + string(v.Title) + "</b>\n" + html.UnescapeString(string(v.Description)) +
// "\nhttps://t.me/iv?url=" + v.Link + "&rhash=da76512d0ff2a2" +
// "\n\nСсылка на пост: " + v.Link
msg := tgbotapi.NewMessage(config.Telegram.ChatId, v.Link)
msg.ParseMode = "Html"
_, err = bot.Send(msg)
if err != nil {
return err
}
log.Println("Sended to telegram")
}
if config.VK.Send {
log.Println("Send to VK")
vk := api.NewVK(config.VK.Token)
_, err := vk.WallPost(api.Params{
"owner_id": config.VK.OwnerId,
"attachments": v.Link,
})
if err != nil {
return err
}
log.Println("Sended to VK")
}
}
return nil
}

263
main.go

@ -1,236 +1,25 @@ @@ -1,236 +1,25 @@
package main
import (
"encoding/json"
"encoding/xml"
"html/template"
"log"
"os"
"ssender/internal/config"
"ssender/internal/db"
"ssender/internal/rss"
"ssender/internal/send"
"github.com/SevereCloud/vksdk/v2/api"
"github.com/boltdb/bolt"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/jessevdk/go-flags"
"gopkg.in/yaml.v2"
)
type Config struct {
Dbpath string `yaml:"dbpath"`
Telegram struct {
Send bool `yaml:"send"`
SendDebug bool `yaml:"senddebug"`
ChatId int64 `yaml:"chatid"`
Token string `yaml:"token"`
} `yaml:"telegram"`
VK struct {
Send bool `yaml:"send"`
Token string `yaml:"token"`
OwnerId int64 `yaml:"ownerid"`
} `yaml:"vk"`
Facebook struct {
Send bool `yaml:"send"`
Token string `yaml:"token"`
} `yaml:"facebook"`
}
func NewConfig(configPath string) (*Config, error) {
// Create config structure
config := &Config{}
// Open config file
file, err := os.Open(configPath)
if err != nil {
return nil, err
}
defer file.Close()
// Init new YAML decode
d := yaml.NewDecoder(file)
// Start YAML decoding from file
if err := d.Decode(&config); err != nil {
return nil, err
}
return config, nil
}
type Options struct {
FileParse string `short:"f" long:"fileparse" description:"File for parse (rss xml)" required:"true"`
ConfigPath string `short:"c" long:"configpath" description:"Config file path"`
InitDB bool `short:"i" long:"initdb" description:"Run initialize from current file"`
}
var ConfigPath = "/etc/ssender/config.yml"
type Rss2 struct {
XMLName xml.Name `xml:"rss"`
Version string `xml:"version,attr"`
// Required
Title string `xml:"channel>title"`
Link string `xml:"channel>link"`
Description string `xml:"channel>description"`
// Optional
PubDate string `xml:"channel>pubDate"`
ItemList []Item `xml:"channel>item"`
}
type Item struct {
// Required
Title string `xml:"title"`
Link string `xml:"link"`
Description template.HTML `xml:"description"`
// Optional
Content template.HTML `xml:"encoded"`
PubDate string `xml:"pubDate"`
Comments string `xml:"comments"`
}
func NewRSS(rssPath string) (*Rss2, error) {
rss := &Rss2{}
// Open rss2 file
file, err := os.Open(rssPath)
if err != nil {
return nil, err
}
defer file.Close()
d := xml.NewDecoder(file)
// Start RSS decoding from file
if err := d.Decode(&rss); err != nil {
return nil, err
}
return rss, nil
}
type SendItems struct {
ItemList []Item
}
var senditems SendItems
func FindItems(rss Rss2, dbpath string) {
db, err := bolt.Open(dbpath, 0600, nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
for _, v := range rss.ItemList {
db.View(func(tx *bolt.Tx) error {
// Assume bucket exists and has keys
b := tx.Bucket([]byte("rss"))
c := b.Cursor()
flag := false
for key, _ := c.First(); key != nil; key, _ = c.Next() {
if v.Link == string(key) {
flag = true
break
}
}
if flag != true {
senditems.ItemList = append(senditems.ItemList, v)
}
return nil
})
}
}
func InitDb(rss Rss2, dbpath string) {
log.Println("Initialize DB")
db, err := bolt.Open(dbpath, 0600, nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte("rss"))
if err != nil {
return err
}
for _, v := range rss.ItemList {
encoded, err := json.Marshal(v)
if err != nil {
return err
}
err = b.Put([]byte(v.Link), encoded)
if err != nil {
return err
}
}
return nil
})
}
func (config Config) RunSend() {
for _, v := range senditems.ItemList {
if config.Telegram.Send {
log.Println("Send to telegram")
bot, err := tgbotapi.NewBotAPI(config.Telegram.Token)
if err != nil {
log.Panic(err)
}
bot.Debug = config.Telegram.SendDebug
// s := "<b>" + string(v.Title) + "</b>\n" + html.UnescapeString(string(v.Description)) +
// "\nhttps://t.me/iv?url=" + v.Link + "&rhash=da76512d0ff2a2" +
// "\n\nСсылка на пост: " + v.Link
msg := tgbotapi.NewMessage(config.Telegram.ChatId, v.Link)
msg.ParseMode = "Html"
_, err = bot.Send(msg)
if err != nil {
log.Panic(err)
}
log.Println("Sended to telegram")
}
if config.VK.Send {
log.Println("Send to VK")
vk := api.NewVK(config.VK.Token)
_, err := vk.WallPost(api.Params{
"owner_id": config.VK.OwnerId,
"attachments": v.Link,
})
if err != nil {
log.Fatal(err)
}
log.Println("Sended to VK")
}
if config.Facebook.Send {
log.Println("Send to Facebook")
log.Println("Sending to facebook is not implemented yet")
}
}
}
func UpdateDb(dbpath string) {
log.Println("Update DB")
db, err := bolt.Open(dbpath, 0600, nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("rss"))
for _, v := range senditems.ItemList {
encoded, err := json.Marshal(v)
if err != nil {
return err
}
err = b.Put([]byte(v.Link), encoded)
if err != nil {
return err
}
}
return nil
})
}
var (
ConfigPath = "/etc/ssender/config.yml"
)
func main() {
log.Println("Run processing")
@ -250,13 +39,15 @@ func main() { @@ -250,13 +39,15 @@ func main() {
}
log.Println("Flags processed")
if options.ConfigPath != "" {
if options.ConfigPath == "" {
log.Println("Config path not found in options! Use default from /etc/ssender/config.yml")
} else {
log.Printf("Config from: %s\n", options.ConfigPath)
ConfigPath = options.ConfigPath
}
// Get config
cfg, err := NewConfig(ConfigPath)
cfg, err := config.NewConfig(ConfigPath)
if err != nil {
log.Fatal(err)
}
@ -264,22 +55,38 @@ func main() { @@ -264,22 +55,38 @@ func main() {
// Parse rss file
log.Printf("Parse file %s \n", options.FileParse)
rss, err := NewRSS(options.FileParse)
rss, err := rss.NewRSS(options.FileParse)
if err != nil {
log.Fatal(err)
}
if options.InitDB {
InitDb(*rss, cfg.Dbpath)
} else {
//Find new items
FindItems(*rss, cfg.Dbpath)
err := db.InitDb(*rss, cfg.Dbpath)
if err != nil {
log.Fatal(err)
}
}
// Find new items
sendItems, err := db.FindItems(*rss, cfg.Dbpath)
if err != nil {
log.Fatal(err)
}
log.Printf("Found %d new items to send\n", len(sendItems.ItemList))
if len(senditems.ItemList) > 0 {
if len(sendItems.ItemList) > 0 {
// Run send data depended on configuration options
log.Println("Run send process")
cfg.RunSend()
UpdateDb(cfg.Dbpath)
err := send.RunSend(sendItems, *cfg)
if err != nil {
log.Fatal(err)
}
// Run update db
err = db.UpdateDb(cfg.Dbpath, sendItems)
if err != nil {
log.Fatal(err)
}
}
log.Println("End processing")
}

Loading…
Cancel
Save