activity.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  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) header(width int) string {
  52. if a.kind == "Create" {
  53. return ""
  54. }
  55. var output string
  56. if a.actorErr != nil {
  57. output += style.Problem(a.actorErr)
  58. } else {
  59. output += a.actor.Name()
  60. }
  61. output += " "
  62. switch a.kind {
  63. case "Announce":
  64. output += "retweeted"
  65. case "Like":
  66. output += "upvoted"
  67. case "Dislike":
  68. output += "downvoted"
  69. default:
  70. panic("encountered unrecognized Activity type: " + a.kind)
  71. }
  72. output += ":\n"
  73. return ansi.Wrap(output, width)
  74. }
  75. func (a *Activity) String(width int) string {
  76. output := a.header(width)
  77. output += a.target.String(width)
  78. return output
  79. }
  80. func (a *Activity) Preview(width int) string {
  81. output := a.header(width)
  82. output += a.target.Preview(width)
  83. return output
  84. }
  85. func (a *Activity) Children() Container {
  86. return a.target.Children()
  87. }
  88. func (a *Activity) Parents(quantity uint) ([]Tangible, Tangible) {
  89. return a.target.Parents(quantity)
  90. }
  91. func (a *Activity) Timestamp() time.Time {
  92. if errors.Is(a.createdErr, object.ErrKeyNotPresent) {
  93. return a.target.Timestamp()
  94. } else if a.createdErr != nil {
  95. return time.Time{}
  96. }
  97. return a.created
  98. }
  99. func (a *Activity) Name() string {
  100. return a.target.Name()
  101. }
  102. func (a *Activity) Actor() Tangible {
  103. if a.actorErr != nil {
  104. return NewFailure(a.actorErr)
  105. }
  106. return a.actor
  107. }
  108. func (a *Activity) ActorIdentifier() *url.URL {
  109. if a.actorErr != nil {
  110. return nil
  111. }
  112. return a.actor.Identifier()
  113. }
  114. func (a *Activity) Target() Tangible {
  115. return a.target
  116. }
  117. func (a *Activity) SelectLink(input int) (string, *mime.MediaType, bool) {
  118. return a.target.SelectLink(input)
  119. }