news.go 3.1 KB

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