activity.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. package pub
  2. import (
  3. "errors"
  4. "fmt"
  5. "golang.org/x/exp/slices"
  6. "servitor/ansi"
  7. "servitor/client"
  8. "servitor/mime"
  9. "servitor/object"
  10. "servitor/style"
  11. "net/url"
  12. "sync"
  13. "time"
  14. )
  15. type Activity struct {
  16. kind string
  17. id *url.URL
  18. actor *Actor
  19. actorErr error
  20. created time.Time
  21. createdErr error
  22. target Tangible
  23. }
  24. func NewActivity(input any, source *url.URL) (*Activity, error) {
  25. o, id, err := client.FetchUnknown(input, source)
  26. if err != nil {
  27. return nil, err
  28. }
  29. return NewActivityFromObject(o, id)
  30. }
  31. func NewActivityFromObject(o object.Object, id *url.URL) (*Activity, error) {
  32. a := &Activity{}
  33. a.id = id
  34. var err error
  35. if a.kind, err = o.GetString("type"); err != nil {
  36. return nil, err
  37. }
  38. if !slices.Contains([]string{
  39. "Create", "Announce", "Dislike", "Like",
  40. }, a.kind) {
  41. return nil, fmt.Errorf("%w: %s is not an Activity", ErrWrongType, a.kind)
  42. }
  43. a.created, a.createdErr = o.GetTime("published")
  44. var wg sync.WaitGroup
  45. wg.Add(2)
  46. go func() { a.actor, a.actorErr = getActor(o, "actor", a.id); wg.Done() }()
  47. go func() { a.target = getPostOrActor(o, "object", a.id); wg.Done() }()
  48. wg.Wait()
  49. return a, nil
  50. }
  51. func (a *Activity) Kind() string {
  52. return a.kind
  53. }
  54. func (a *Activity) header(width int) string {
  55. if a.kind == "Create" {
  56. return ""
  57. }
  58. var output string
  59. if a.actorErr != nil {
  60. output += style.Problem(a.actorErr)
  61. } else {
  62. output += a.actor.Name()
  63. }
  64. output += " "
  65. switch a.kind {
  66. case "Announce":
  67. output += "retweeted"
  68. case "Like":
  69. output += "upvoted"
  70. case "Dislike":
  71. output += "downvoted"
  72. default:
  73. panic("encountered unrecognized Activity type: " + a.kind)
  74. }
  75. output += ":\n"
  76. return ansi.Wrap(output, width)
  77. }
  78. func (a *Activity) String(width int) string {
  79. output := a.header(width)
  80. output += a.target.String(width)
  81. return output
  82. }
  83. func (a *Activity) Preview(width int) string {
  84. output := a.header(width)
  85. output += a.target.Preview(width)
  86. return output
  87. }
  88. func (a *Activity) Children() Container {
  89. return a.target.Children()
  90. }
  91. func (a *Activity) Parents(quantity uint) ([]Tangible, Tangible) {
  92. return a.target.Parents(quantity)
  93. }
  94. func (a *Activity) Timestamp() time.Time {
  95. if errors.Is(a.createdErr, object.ErrKeyNotPresent) {
  96. return a.target.Timestamp()
  97. } else if a.createdErr != nil {
  98. return time.Time{}
  99. }
  100. return a.created
  101. }
  102. func (a *Activity) Name() string {
  103. return a.target.Name()
  104. }
  105. func (a *Activity) Actor() Tangible {
  106. if a.actorErr != nil {
  107. return NewFailure(a.actorErr)
  108. }
  109. return a.actor
  110. }
  111. func (a *Activity) Target() Tangible {
  112. return a.target
  113. }
  114. func (a *Activity) SelectLink(input int) (string, *mime.MediaType, bool) {
  115. return a.target.SelectLink(input)
  116. }