123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 |
- package main
- import (
- "bytes"
- "context"
- "fmt"
- "os"
- "os/signal"
- "runtime"
- "syscall"
- "text/template"
- "time"
- "github.com/denisbrodbeck/striphtmltags"
- log "github.com/go-pkgz/lgr"
- flags "github.com/jessevdk/go-flags"
- "github.com/umputun/rss2twitter/app/publisher"
- "github.com/umputun/rss2twitter/app/rss"
- )
- var opts struct {
- Refresh time.Duration `short:"r" long:"refresh" env:"REFRESH" default:"30s" description:"refresh interval"`
- TimeOut time.Duration `short:"t" long:"timeout" env:"TIMEOUT" default:"5s" description:"rss feed timeout"`
- Feed string `short:"f" long:"feed" env:"FEED" required:"true" description:"rss feed url"`
- ConsumerKey string `long:"consumer-key" env:"TWI_CONSUMER_KEY" required:"true" description:"twitter consumer key"`
- ConsumerSecret string `long:"consumer-secret" env:"TWI_CONSUMER_SECRET" required:"true" description:"twitter consumer secret"`
- AccessToken string `long:"access-token" env:"TWI_ACCESS_TOKEN" required:"true" description:"twitter access token"`
- AccessSecret string `long:"access-secret" env:"TWI_ACCESS_SECRET" required:"true" description:"twitter access secret"`
- Template string `long:"template" env:"TEMPLATE" default:"{{.Title}} - {{.Link}}" description:"twitter message template"`
- Dry bool `long:"dry" env:"DRY" description:"dry mode"`
- Dbg bool `long:"dbg" env:"DEBUG" description:"debug mode"`
- }
- var revision = "unknown"
- func main() {
- fmt.Printf("rss2twitter - %s\n", revision)
- if _, err := flags.Parse(&opts); err != nil {
- os.Exit(1)
- }
- if opts.Dbg {
- log.Setup(log.Debug)
- }
- notifier := rss.Notify{Feed: opts.Feed, Duration: opts.Refresh, Timeout: opts.TimeOut}
- var pub publisher.Interface = publisher.Twitter{
- ConsumerKey: opts.ConsumerKey,
- ConsumerSecret: opts.ConsumerSecret,
- AccessToken: opts.AccessToken,
- AccessSecret: opts.AccessSecret,
- }
- if opts.Dry { // override publisher to stdout only, no actual twitter publishing
- pub = publisher.Stdout{}
- log.Print("[INFO] dry mode")
- }
- log.Printf("[INFO] message template - %q", opts.Template)
- ch := notifier.Go(context.Background())
- for event := range ch {
- err := pub.Publish(event, func(r rss.Event) string {
- b1 := bytes.Buffer{}
- if err := template.Must(template.New("twi").Parse(opts.Template)).Execute(&b1, event); err != nil {
- // template failed to parse record, backup predefined format
- return fmt.Sprintf("%s - %s", r.Title, r.Link)
- }
- return format(b1.String(), 275)
- })
- if err != nil {
- log.Printf("[WARN] failed to publish, %s", err)
- }
- }
- log.Print("[INFO] terminated")
- }
- // format cleans text (removes html tags) and shrinks result
- func format(inp string, max int) string {
- res := striphtmltags.StripTags(inp)
- if len([]rune(res)) > max {
- snippet := []rune(res)[:max]
- //go back in snippet and found first space
- for i := len(snippet) - 1; i >= 0; i-- {
- if snippet[i] == ' ' {
- snippet = snippet[:i]
- break
- }
- }
- res = string(snippet) + " ..."
- }
- return res
- }
- // getDump reads runtime stack and returns as a string
- func getDump() string {
- maxSize := 5 * 1024 * 1024
- stacktrace := make([]byte, maxSize)
- length := runtime.Stack(stacktrace, true)
- if length > maxSize {
- length = maxSize
- }
- return string(stacktrace[:length])
- }
- func init() {
- // catch SIGQUIT and print stack traces
- sigChan := make(chan os.Signal)
- go func() {
- for range sigChan {
- log.Printf("[INFO] SIGQUIT detected, dump:\n%s", getDump())
- }
- }()
- signal.Notify(sigChan, syscall.SIGQUIT)
- }
|