main.go 3.4 KB

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