246 lines
6.9 KiB
Go
246 lines
6.9 KiB
Go
package nakama
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"seanime/internal/api/anilist"
|
|
"seanime/internal/api/metadata"
|
|
"seanime/internal/events"
|
|
"seanime/internal/library/anime"
|
|
"seanime/internal/library/playbackmanager"
|
|
"seanime/internal/util"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/imroc/req/v3"
|
|
)
|
|
|
|
type (
|
|
HydrateHostAnimeLibraryOptions struct {
|
|
AnimeCollection *anilist.AnimeCollection
|
|
LibraryCollection *anime.LibraryCollection
|
|
MetadataProvider metadata.Provider
|
|
}
|
|
|
|
NakamaAnimeLibrary struct {
|
|
LocalFiles []*anime.LocalFile `json:"localFiles"`
|
|
AnimeCollection *anilist.AnimeCollection `json:"animeCollection"`
|
|
}
|
|
)
|
|
|
|
// generateHMACToken generates an HMAC token for stream authentication
|
|
func (m *Manager) generateHMACToken(endpoint string) (string, error) {
|
|
// Use the Nakama password as the base secret - HostPassword for hosts, RemoteServerPassword for peers
|
|
var secret string
|
|
if m.settings.IsHost {
|
|
secret = m.settings.HostPassword
|
|
} else {
|
|
secret = m.settings.RemoteServerPassword
|
|
}
|
|
|
|
hmacAuth := util.NewHMACAuth(secret, 24*time.Hour)
|
|
return hmacAuth.GenerateToken(endpoint)
|
|
}
|
|
|
|
func (m *Manager) GetHostAnimeLibraryFiles(mId ...int) (lfs []*anime.LocalFile, hydrated bool) {
|
|
if !m.settings.Enabled || !m.settings.IncludeNakamaAnimeLibrary || !m.IsConnectedToHost() {
|
|
return nil, false
|
|
}
|
|
|
|
var response *req.Response
|
|
var err error
|
|
if len(mId) > 0 {
|
|
response, err = m.reqClient.R().
|
|
SetHeader("X-Seanime-Nakama-Token", m.settings.RemoteServerPassword).
|
|
Get(m.GetHostBaseServerURL() + "/api/v1/nakama/host/anime/library/files/" + strconv.Itoa(mId[0]))
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
} else {
|
|
response, err = m.reqClient.R().
|
|
SetHeader("X-Seanime-Nakama-Token", m.settings.RemoteServerPassword).
|
|
Get(m.GetHostBaseServerURL() + "/api/v1/nakama/host/anime/library/files")
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
}
|
|
|
|
if !response.IsSuccessState() {
|
|
return nil, false
|
|
}
|
|
|
|
body := response.Bytes()
|
|
|
|
var entryResponse struct {
|
|
Data []*anime.LocalFile `json:"data"`
|
|
}
|
|
err = json.Unmarshal(body, &entryResponse)
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
|
|
return entryResponse.Data, true
|
|
}
|
|
|
|
func (m *Manager) GetHostAnimeLibrary() (ac *NakamaAnimeLibrary, hydrated bool) {
|
|
if !m.settings.Enabled || !m.settings.IncludeNakamaAnimeLibrary || !m.IsConnectedToHost() {
|
|
return nil, false
|
|
}
|
|
|
|
var response *req.Response
|
|
var err error
|
|
|
|
response, err = m.reqClient.R().
|
|
SetHeader("X-Seanime-Nakama-Token", m.settings.RemoteServerPassword).
|
|
Get(m.GetHostBaseServerURL() + "/api/v1/nakama/host/anime/library")
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
|
|
if !response.IsSuccessState() {
|
|
return nil, false
|
|
}
|
|
|
|
body := response.Bytes()
|
|
|
|
var entryResponse struct {
|
|
Data *NakamaAnimeLibrary `json:"data"`
|
|
}
|
|
err = json.Unmarshal(body, &entryResponse)
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
|
|
if entryResponse.Data == nil {
|
|
return nil, false
|
|
}
|
|
|
|
return entryResponse.Data, true
|
|
}
|
|
|
|
func (m *Manager) getBaseServerURL() string {
|
|
ret := ""
|
|
host := m.serverHost
|
|
if host == "0.0.0.0" {
|
|
host = "127.0.0.1"
|
|
}
|
|
ret = fmt.Sprintf("http://%s:%d", host, m.serverPort)
|
|
if strings.HasPrefix(ret, "http://http") {
|
|
ret = strings.Replace(ret, "http://http", "http", 1)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (m *Manager) PlayHostAnimeLibraryFile(path string, userAgent string, media *anilist.BaseAnime, aniDBEpisode string) error {
|
|
if !m.settings.Enabled || !m.IsConnectedToHost() {
|
|
return errors.New("not connected to host")
|
|
}
|
|
|
|
m.previousPath = path
|
|
|
|
m.logger.Debug().Int("mediaId", media.ID).Msg("nakama: Playing host anime library file")
|
|
m.wsEventManager.SendEvent(events.ShowIndefiniteLoader, "nakama-file")
|
|
m.wsEventManager.SendEvent(events.InfoToast, "Sending stream to player...")
|
|
|
|
// Send a HTTP request to the host to get the anime library
|
|
// If we can access it then the host is sharing its anime library
|
|
response, err := m.reqClient.R().
|
|
SetHeader("X-Seanime-Nakama-Token", m.settings.RemoteServerPassword).
|
|
Get(m.GetHostBaseServerURL() + "/api/v1/nakama/host/anime/library/collection")
|
|
if err != nil {
|
|
return fmt.Errorf("cannot access host's anime library: %w", err)
|
|
}
|
|
|
|
if !response.IsSuccessState() {
|
|
body := response.Bytes()
|
|
code := response.StatusCode
|
|
return fmt.Errorf("cannot access host's anime library: %d, %s", code, string(body))
|
|
}
|
|
|
|
host := m.serverHost
|
|
if host == "0.0.0.0" {
|
|
host = "127.0.0.1"
|
|
}
|
|
address := fmt.Sprintf("%s:%d", host, m.serverPort)
|
|
ret := fmt.Sprintf("http://%s/api/v1/nakama/stream?type=file&path=%s", address, base64.StdEncoding.EncodeToString([]byte(path)))
|
|
if strings.HasPrefix(ret, "http://http") {
|
|
ret = strings.Replace(ret, "http://http", "http", 1)
|
|
}
|
|
|
|
windowTitle := media.GetPreferredTitle()
|
|
if !media.IsMovieOrSingleEpisode() {
|
|
windowTitle += " - Episode " + aniDBEpisode
|
|
}
|
|
|
|
err = m.playbackManager.StartStreamingUsingMediaPlayer(windowTitle, &playbackmanager.StartPlayingOptions{
|
|
Payload: ret,
|
|
UserAgent: userAgent,
|
|
ClientId: "",
|
|
}, media, aniDBEpisode)
|
|
if err != nil {
|
|
m.wsEventManager.SendEvent(events.HideIndefiniteLoader, "nakama-file")
|
|
go m.playbackManager.UnsubscribeFromPlaybackStatus("nakama-file")
|
|
return err
|
|
}
|
|
|
|
m.playbackManager.RegisterMediaPlayerCallback(func(event playbackmanager.PlaybackEvent, cancel func()) {
|
|
switch event.(type) {
|
|
case playbackmanager.StreamStartedEvent:
|
|
m.wsEventManager.SendEvent(events.HideIndefiniteLoader, "nakama-file")
|
|
cancel()
|
|
}
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) PlayHostAnimeStream(streamType string, userAgent string, media *anilist.BaseAnime, aniDBEpisode string) error {
|
|
if !m.settings.Enabled || !m.IsConnectedToHost() {
|
|
return errors.New("not connected to host")
|
|
}
|
|
|
|
m.logger.Debug().Int("mediaId", media.ID).Msg("nakama: Playing host anime stream")
|
|
m.wsEventManager.SendEvent(events.ShowIndefiniteLoader, "nakama-stream")
|
|
m.wsEventManager.SendEvent(events.InfoToast, "Sending stream to player...")
|
|
|
|
host := m.serverHost
|
|
if host == "0.0.0.0" {
|
|
host = "127.0.0.1"
|
|
}
|
|
address := fmt.Sprintf("%s:%d", host, m.serverPort)
|
|
|
|
ret := fmt.Sprintf("http://%s/api/v1/nakama/stream?type=%s", address, streamType)
|
|
if strings.HasPrefix(ret, "http://http") {
|
|
ret = strings.Replace(ret, "http://http", "http", 1)
|
|
}
|
|
|
|
windowTitle := media.GetPreferredTitle()
|
|
if !media.IsMovieOrSingleEpisode() {
|
|
windowTitle += " - Episode " + aniDBEpisode
|
|
}
|
|
|
|
err := m.playbackManager.StartStreamingUsingMediaPlayer(windowTitle, &playbackmanager.StartPlayingOptions{
|
|
Payload: ret,
|
|
UserAgent: userAgent,
|
|
ClientId: "",
|
|
}, media, aniDBEpisode)
|
|
if err != nil {
|
|
m.wsEventManager.SendEvent(events.HideIndefiniteLoader, "nakama-stream")
|
|
go m.playbackManager.UnsubscribeFromPlaybackStatus("nakama-stream")
|
|
return err
|
|
}
|
|
|
|
m.playbackManager.RegisterMediaPlayerCallback(func(event playbackmanager.PlaybackEvent, cancel func()) {
|
|
switch event.(type) {
|
|
case playbackmanager.StreamStartedEvent:
|
|
m.wsEventManager.SendEvent(events.HideIndefiniteLoader, "nakama-stream")
|
|
cancel()
|
|
}
|
|
})
|
|
|
|
return nil
|
|
}
|