main.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. package main
  2. import (
  3. "bytes"
  4. "context"
  5. "fmt"
  6. "os"
  7. "os/signal"
  8. "runtime"
  9. "syscall"
  10. "text/template"
  11. "time"
  12. log "github.com/go-pkgz/lgr"
  13. flags "github.com/jessevdk/go-flags"
  14. "github.com/umputun/rss2twitter/app/publisher"
  15. "github.com/umputun/rss2twitter/app/rss"
  16. )
  17. var opts struct {
  18. Refresh time.Duration `short:"r" long:"refresh" env:"REFRESH" default:"30s" description:"refresh interval"`
  19. TimeOut time.Duration `short:"t" long:"timeout" env:"TIMEOUT" default:"5s" description:"rss feed timeout"`
  20. Feed string `short:"f" long:"feed" env:"FEED" required:"true" description:"rss feed url"`
  21. ConsumerKey string `long:"consumer-key" env:"TWI_CONSUMER_KEY" required:"true" description:"twitter consumer key"`
  22. ConsumerSecret string `long:"consumer-secret" env:"TWI_CONSUMER_SECRET" required:"true" description:"twitter consumer secret"`
  23. AccessToken string `long:"access-token" env:"TWI_ACCESS_TOKEN" required:"true" description:"twitter access token"`
  24. AccessSecret string `long:"access-secret" env:"TWI_ACCESS_SECRET" required:"true" description:"twitter access secret"`
  25. Template string `long:"template" env:"TEMPLATE" default:"{{.Title}} - {{.Link}}" description:"twitter message template"`
  26. Dry bool `long:"dry" env:"DRY" description:"dry mode"`
  27. Dbg bool `long:"dbg" env:"DEBUG" description:"debug mode"`
  28. }
  29. var revision = "unknown"
  30. func main() {
  31. fmt.Printf("rss2twitter - %s\n", revision)
  32. if _, err := flags.Parse(&opts); err != nil {
  33. os.Exit(1)
  34. }
  35. if opts.Dbg {
  36. log.Setup(log.Debug)
  37. }
  38. notifier := rss.Notify{Feed: opts.Feed, Duration: opts.Refresh, Timeout: opts.TimeOut}
  39. var pub publisher.Interface = publisher.Twitter{
  40. ConsumerKey: opts.ConsumerKey,
  41. ConsumerSecret: opts.ConsumerSecret,
  42. AccessToken: opts.AccessToken,
  43. AccessSecret: opts.AccessSecret,
  44. }
  45. if opts.Dry { // override publisher to stdout only, no actual twitter publishing
  46. pub = publisher.Stdout{}
  47. log.Print("[INFO] dry mode")
  48. }
  49. log.Printf("[INFO] message template - %q", opts.Template)
  50. ch := notifier.Go(context.Background())
  51. for event := range ch {
  52. err := pub.Publish(event, func(r rss.Event) string {
  53. b1 := bytes.Buffer{}
  54. if err := template.Must(template.New("twi").Parse(opts.Template)).Execute(&b1, event); err != nil {
  55. // template failed to parse record, backup predefined format
  56. return fmt.Sprintf("%s - %s", r.Title, r.Link)
  57. }
  58. return b1.String()
  59. })
  60. if err != nil {
  61. log.Printf("[WARN] failed to publish, %s", err)
  62. }
  63. }
  64. log.Print("[INFO] terminated")
  65. }
  66. // getDump reads runtime stack and returns as a string
  67. func getDump() string {
  68. maxSize := 5 * 1024 * 1024
  69. stacktrace := make([]byte, maxSize)
  70. length := runtime.Stack(stacktrace, true)
  71. if length > maxSize {
  72. length = maxSize
  73. }
  74. return string(stacktrace[:length])
  75. }
  76. func init() {
  77. // catch SIGQUIT and print stack traces
  78. sigChan := make(chan os.Signal)
  79. go func() {
  80. for range sigChan {
  81. log.Printf("[INFO] SIGQUIT detected, dump:\n%s", getDump())
  82. }
  83. }()
  84. signal.Notify(sigChan, syscall.SIGQUIT)
  85. }