yp.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. package yp
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "io"
  6. "net/http"
  7. "net/url"
  8. "time"
  9. "github.com/owncast/owncast/config"
  10. "github.com/owncast/owncast/core/data"
  11. "github.com/owncast/owncast/models"
  12. log "github.com/sirupsen/logrus"
  13. )
  14. const pingInterval = 4 * time.Minute
  15. var (
  16. getStatus func() models.Status
  17. _inErrorState = false
  18. )
  19. // YP is a service for handling listing in the Owncast directory.
  20. type YP struct {
  21. timer *time.Ticker
  22. }
  23. type ypPingResponse struct {
  24. Key string `json:"key"`
  25. Error string `json:"error"`
  26. ErrorCode int `json:"errorCode"`
  27. Success bool `json:"success"`
  28. }
  29. type ypPingRequest struct {
  30. Key string `json:"key"`
  31. URL string `json:"url"`
  32. }
  33. // NewYP creates a new instance of the YP service handler.
  34. func NewYP(getStatusFunc func() models.Status) *YP {
  35. getStatus = getStatusFunc
  36. return &YP{}
  37. }
  38. // Start is run when a live stream begins to start pinging YP.
  39. func (yp *YP) Start() {
  40. yp.timer = time.NewTicker(pingInterval)
  41. for range yp.timer.C {
  42. yp.ping()
  43. }
  44. yp.ping()
  45. }
  46. // Stop stops the pinging of YP.
  47. func (yp *YP) Stop() {
  48. yp.timer.Stop()
  49. }
  50. func (yp *YP) ping() {
  51. if !data.GetDirectoryEnabled() {
  52. return
  53. }
  54. // Hack: Don't allow ping'ing when offline.
  55. // It shouldn't even be trying to, but on some instances the ping timer isn't stopping.
  56. if !getStatus().Online {
  57. return
  58. }
  59. myInstanceURL := data.GetServerURL()
  60. if myInstanceURL == "" {
  61. log.Warnln("Server URL not set in the configuration. Directory access is disabled until this is set.")
  62. return
  63. }
  64. isValidInstanceURL := isURL(myInstanceURL)
  65. if myInstanceURL == "" || !isValidInstanceURL {
  66. if !_inErrorState {
  67. log.Warnln("YP Error: unable to use", myInstanceURL, "as a public instance URL. Fix this value in your configuration.")
  68. }
  69. _inErrorState = true
  70. return
  71. }
  72. key := data.GetDirectoryRegistrationKey()
  73. log.Traceln("Pinging YP as: ", data.GetServerName(), "with key", key)
  74. request := ypPingRequest{
  75. Key: key,
  76. URL: myInstanceURL,
  77. }
  78. req, err := json.Marshal(request)
  79. if err != nil {
  80. log.Errorln(err)
  81. return
  82. }
  83. pingURL := config.GetDefaults().YPServer + "/api/ping"
  84. resp, err := http.Post(pingURL, "application/json", bytes.NewBuffer(req)) //nolint
  85. if err != nil {
  86. log.Errorln(err)
  87. return
  88. }
  89. defer resp.Body.Close()
  90. body, err := io.ReadAll(resp.Body)
  91. if err != nil {
  92. log.Errorln(err)
  93. }
  94. pingResponse := ypPingResponse{}
  95. if err := json.Unmarshal(body, &pingResponse); err != nil {
  96. log.Errorln(err)
  97. }
  98. if !pingResponse.Success {
  99. if !_inErrorState {
  100. log.Warnln("YP Ping error returned from service:", pingResponse.Error)
  101. }
  102. _inErrorState = true
  103. return
  104. }
  105. _inErrorState = false
  106. if pingResponse.Key != key {
  107. if err := data.SetDirectoryRegistrationKey(key); err != nil {
  108. log.Errorln("unable to save directory key:", err)
  109. }
  110. }
  111. }
  112. func isURL(str string) bool {
  113. u, err := url.Parse(str)
  114. return err == nil && u.Scheme != "" && u.Host != ""
  115. }