news.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. package news
  2. import (
  3. "bytes"
  4. "encoding/xml"
  5. "fmt"
  6. "html"
  7. "io/ioutil"
  8. "net/http"
  9. "os"
  10. "strings"
  11. "time"
  12. "github.com/Jguer/go-alpm"
  13. "github.com/Jguer/yay/v10/pkg/query"
  14. "github.com/Jguer/yay/v10/pkg/settings"
  15. "github.com/Jguer/yay/v10/pkg/text"
  16. )
  17. type item struct {
  18. Title string `xml:"title"`
  19. Link string `xml:"link"`
  20. Description string `xml:"description"`
  21. PubDate string `xml:"pubDate"`
  22. Creator string `xml:"dc:creator"`
  23. }
  24. func (item *item) print(buildTime time.Time, all, quiet bool) {
  25. var fd string
  26. date, err := time.Parse(time.RFC1123Z, item.PubDate)
  27. if err != nil {
  28. fmt.Fprintln(os.Stderr, err)
  29. } else {
  30. fd = text.FormatTime(int(date.Unix()))
  31. if !all && !buildTime.IsZero() {
  32. if buildTime.After(date) {
  33. return
  34. }
  35. }
  36. }
  37. fmt.Println(text.Bold(text.Magenta(fd)), text.Bold(strings.TrimSpace(item.Title)))
  38. if !quiet {
  39. desc := strings.TrimSpace(parseNews(item.Description))
  40. fmt.Println(desc)
  41. }
  42. }
  43. type channel struct {
  44. Title string `xml:"title"`
  45. Link string `xml:"link"`
  46. Description string `xml:"description"`
  47. Language string `xml:"language"`
  48. Lastbuilddate string `xml:"lastbuilddate"`
  49. Items []item `xml:"item"`
  50. }
  51. type rss struct {
  52. Channel channel `xml:"channel"`
  53. }
  54. func PrintNewsFeed(alpmHandle *alpm.Handle, sortMode int, double, quiet bool) error {
  55. resp, err := http.Get("https://archlinux.org/feeds/news")
  56. if err != nil {
  57. return err
  58. }
  59. defer resp.Body.Close()
  60. body, err := ioutil.ReadAll(resp.Body)
  61. if err != nil {
  62. return err
  63. }
  64. rssGot := rss{}
  65. d := xml.NewDecoder(bytes.NewReader(body))
  66. err = d.Decode(&rssGot)
  67. if err != nil {
  68. return err
  69. }
  70. buildTime, err := lastBuildTime(alpmHandle)
  71. if err != nil {
  72. return err
  73. }
  74. if sortMode == settings.BottomUp {
  75. for i := len(rssGot.Channel.Items) - 1; i >= 0; i-- {
  76. rssGot.Channel.Items[i].print(buildTime, double, quiet)
  77. }
  78. } else {
  79. for i := 0; i < len(rssGot.Channel.Items); i++ {
  80. rssGot.Channel.Items[i].print(buildTime, double, quiet)
  81. }
  82. }
  83. return nil
  84. }
  85. func lastBuildTime(alpmHandle *alpm.Handle) (time.Time, error) {
  86. var lastTime time.Time
  87. pkgs, _, _, _, err := query.FilterPackages(alpmHandle)
  88. if err != nil {
  89. return lastTime, err
  90. }
  91. for _, pkg := range pkgs {
  92. thisTime := pkg.BuildDate()
  93. if thisTime.After(lastTime) {
  94. lastTime = thisTime
  95. }
  96. }
  97. return lastTime, nil
  98. }
  99. // Crude html parsing, good enough for the arch news
  100. // This is only displayed in the terminal so there should be no security
  101. // concerns
  102. func parseNews(str string) string {
  103. var buffer bytes.Buffer
  104. var tagBuffer bytes.Buffer
  105. var escapeBuffer bytes.Buffer
  106. inTag := false
  107. inEscape := false
  108. for _, char := range str {
  109. if inTag {
  110. if char == '>' {
  111. inTag = false
  112. switch tagBuffer.String() {
  113. case "code":
  114. buffer.WriteString(text.CyanCode)
  115. case "/code":
  116. buffer.WriteString(text.ResetCode)
  117. case "/p":
  118. buffer.WriteRune('\n')
  119. }
  120. continue
  121. }
  122. tagBuffer.WriteRune(char)
  123. continue
  124. }
  125. if inEscape {
  126. if char == ';' {
  127. inEscape = false
  128. escapeBuffer.WriteRune(char)
  129. s := html.UnescapeString(escapeBuffer.String())
  130. buffer.WriteString(s)
  131. continue
  132. }
  133. escapeBuffer.WriteRune(char)
  134. continue
  135. }
  136. if char == '<' {
  137. inTag = true
  138. tagBuffer.Reset()
  139. continue
  140. }
  141. if char == '&' {
  142. inEscape = true
  143. escapeBuffer.Reset()
  144. escapeBuffer.WriteRune(char)
  145. continue
  146. }
  147. buffer.WriteRune(char)
  148. }
  149. buffer.WriteString(text.ResetCode)
  150. return buffer.String()
  151. }