followers.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. package controllers
  2. import (
  3. "fmt"
  4. "net/http"
  5. "net/url"
  6. "strconv"
  7. "strings"
  8. "github.com/pkg/errors"
  9. log "github.com/sirupsen/logrus"
  10. "github.com/go-fed/activity/streams"
  11. "github.com/go-fed/activity/streams/vocab"
  12. "github.com/owncast/owncast/activitypub/apmodels"
  13. "github.com/owncast/owncast/activitypub/crypto"
  14. "github.com/owncast/owncast/activitypub/persistence"
  15. "github.com/owncast/owncast/activitypub/requests"
  16. "github.com/owncast/owncast/core/data"
  17. )
  18. const (
  19. followersPageSize = 50
  20. )
  21. // FollowersHandler will return the list of remote followers on the Fediverse.
  22. func FollowersHandler(w http.ResponseWriter, r *http.Request) {
  23. if r.Method != http.MethodGet {
  24. w.WriteHeader(http.StatusMethodNotAllowed)
  25. return
  26. }
  27. var response interface{}
  28. var err error
  29. if r.URL.Query().Get("page") != "" {
  30. response, err = getFollowersPage(r.URL.Query().Get("page"), r)
  31. } else {
  32. response, err = getInitialFollowersRequest(r)
  33. }
  34. if response == nil {
  35. w.WriteHeader(http.StatusInternalServerError)
  36. return
  37. }
  38. if err != nil {
  39. _, _ = w.Write([]byte(err.Error()))
  40. w.WriteHeader(http.StatusInternalServerError)
  41. return
  42. }
  43. pathComponents := strings.Split(r.URL.Path, "/")
  44. accountName := pathComponents[3]
  45. actorIRI := apmodels.MakeLocalIRIForAccount(accountName)
  46. publicKey := crypto.GetPublicKey(actorIRI)
  47. if err := requests.WriteStreamResponse(response.(vocab.Type), w, publicKey); err != nil {
  48. log.Errorln("unable to write stream response for followers handler", err)
  49. }
  50. }
  51. func getInitialFollowersRequest(r *http.Request) (vocab.ActivityStreamsOrderedCollection, error) {
  52. followerCount, _ := persistence.GetFollowerCount()
  53. collection := streams.NewActivityStreamsOrderedCollection()
  54. idProperty := streams.NewJSONLDIdProperty()
  55. id, err := createPageURL(r, nil)
  56. if err != nil {
  57. return nil, errors.Wrap(err, "unable to create followers page property")
  58. }
  59. idProperty.SetIRI(id)
  60. collection.SetJSONLDId(idProperty)
  61. totalItemsProperty := streams.NewActivityStreamsTotalItemsProperty()
  62. totalItemsProperty.Set(int(followerCount))
  63. collection.SetActivityStreamsTotalItems(totalItemsProperty)
  64. first := streams.NewActivityStreamsFirstProperty()
  65. page := "1"
  66. firstIRI, err := createPageURL(r, &page)
  67. if err != nil {
  68. return nil, errors.Wrap(err, "unable to create first page property")
  69. }
  70. first.SetIRI(firstIRI)
  71. collection.SetActivityStreamsFirst(first)
  72. return collection, nil
  73. }
  74. func getFollowersPage(page string, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error) {
  75. pageInt, err := strconv.Atoi(page)
  76. if err != nil {
  77. return nil, errors.Wrap(err, "unable to parse page number")
  78. }
  79. followerCount, err := persistence.GetFollowerCount()
  80. if err != nil {
  81. return nil, errors.Wrap(err, "unable to get follower count")
  82. }
  83. followers, _, err := persistence.GetFederationFollowers(followersPageSize, (pageInt-1)*followersPageSize)
  84. if err != nil {
  85. return nil, errors.Wrap(err, "unable to get federation followers")
  86. }
  87. collectionPage := streams.NewActivityStreamsOrderedCollectionPage()
  88. idProperty := streams.NewJSONLDIdProperty()
  89. id, err := createPageURL(r, &page)
  90. if err != nil {
  91. return nil, errors.Wrap(err, "unable to create followers page ID")
  92. }
  93. idProperty.SetIRI(id)
  94. collectionPage.SetJSONLDId(idProperty)
  95. orderedItems := streams.NewActivityStreamsOrderedItemsProperty()
  96. for _, follower := range followers {
  97. u, _ := url.Parse(follower.ActorIRI)
  98. orderedItems.AppendIRI(u)
  99. }
  100. collectionPage.SetActivityStreamsOrderedItems(orderedItems)
  101. partOf := streams.NewActivityStreamsPartOfProperty()
  102. partOfIRI, err := createPageURL(r, nil)
  103. if err != nil {
  104. return nil, errors.Wrap(err, "unable to create partOf property for followers page")
  105. }
  106. partOf.SetIRI(partOfIRI)
  107. collectionPage.SetActivityStreamsPartOf(partOf)
  108. if pageInt*followersPageSize < int(followerCount) {
  109. next := streams.NewActivityStreamsNextProperty()
  110. nextPage := fmt.Sprintf("%d", pageInt+1)
  111. nextIRI, err := createPageURL(r, &nextPage)
  112. if err != nil {
  113. return nil, errors.Wrap(err, "unable to create next page property")
  114. }
  115. next.SetIRI(nextIRI)
  116. collectionPage.SetActivityStreamsNext(next)
  117. }
  118. return collectionPage, nil
  119. }
  120. func createPageURL(r *http.Request, page *string) (*url.URL, error) {
  121. domain := data.GetServerURL()
  122. if domain == "" {
  123. return nil, errors.New("unable to get server URL")
  124. }
  125. pageURL, err := url.Parse(domain)
  126. if err != nil {
  127. return nil, errors.Wrap(err, "unable to parse server URL")
  128. }
  129. if page != nil {
  130. query := pageURL.Query()
  131. query.Add("page", *page)
  132. pageURL.RawQuery = query.Encode()
  133. }
  134. pageURL.Path = r.URL.Path
  135. return pageURL, nil
  136. }