package handlers import ( "errors" "os" "seanime/internal/api/anilist" "seanime/internal/api/metadata" "seanime/internal/database/models" "seanime/internal/events" hibiketorrent "seanime/internal/extension/hibike/torrent" "seanime/internal/torrentstream" "github.com/labstack/echo/v4" ) // HandleGetTorrentstreamSettings // // @summary get torrentstream settings. // @desc This returns the torrentstream settings. // @returns models.TorrentstreamSettings // @route /api/v1/torrentstream/settings [GET] func (h *Handler) HandleGetTorrentstreamSettings(c echo.Context) error { torrentstreamSettings, found := h.App.Database.GetTorrentstreamSettings() if !found { return h.RespondWithError(c, errors.New("torrent streaming settings not found")) } return h.RespondWithData(c, torrentstreamSettings) } // HandleSaveTorrentstreamSettings // // @summary save torrentstream settings. // @desc This saves the torrentstream settings. // @desc The client should refetch the server status. // @returns models.TorrentstreamSettings // @route /api/v1/torrentstream/settings [PATCH] func (h *Handler) HandleSaveTorrentstreamSettings(c echo.Context) error { type body struct { Settings models.TorrentstreamSettings `json:"settings"` } var b body if err := c.Bind(&b); err != nil { return h.RespondWithError(c, err) } // Validate the download directory if b.Settings.DownloadDir != "" { dir, err := os.Stat(b.Settings.DownloadDir) if err != nil { h.App.Logger.Error().Err(err).Msgf("torrentstream: Download directory %s does not exist", b.Settings.DownloadDir) h.App.WSEventManager.SendEvent(events.ErrorToast, "Download directory does not exist") b.Settings.DownloadDir = "" } if !dir.IsDir() { h.App.Logger.Error().Msgf("torrentstream: Download directory %s is not a directory", b.Settings.DownloadDir) h.App.WSEventManager.SendEvent(events.ErrorToast, "Download directory is not a directory") b.Settings.DownloadDir = "" } } settings, err := h.App.Database.UpsertTorrentstreamSettings(&b.Settings) if err != nil { return h.RespondWithError(c, err) } h.App.InitOrRefreshTorrentstreamSettings() return h.RespondWithData(c, settings) } // HandleGetTorrentstreamTorrentFilePreviews // // @summary get list of torrent files from a batch // @desc This returns a list of file previews from the torrent // @returns []torrentstream.FilePreview // @route /api/v1/torrentstream/torrent-file-previews [POST] func (h *Handler) HandleGetTorrentstreamTorrentFilePreviews(c echo.Context) error { type body struct { Torrent *hibiketorrent.AnimeTorrent `json:"torrent"` EpisodeNumber int `json:"episodeNumber"` Media *anilist.BaseAnime `json:"media"` } var b body if err := c.Bind(&b); err != nil { return h.RespondWithError(c, err) } providerExtension, ok := h.App.ExtensionRepository.GetAnimeTorrentProviderExtensionByID(b.Torrent.Provider) if !ok { return h.RespondWithError(c, errors.New("torrentstream: Torrent provider extension not found")) } magnet, err := providerExtension.GetProvider().GetTorrentMagnetLink(b.Torrent) if err != nil { return h.RespondWithError(c, err) } // Get the media metadata animeMetadata, _ := h.App.MetadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, b.Media.ID) absoluteOffset := 0 if animeMetadata != nil { absoluteOffset = animeMetadata.GetOffset() } files, err := h.App.TorrentstreamRepository.GetTorrentFilePreviewsFromManualSelection(&torrentstream.GetTorrentFilePreviewsOptions{ Torrent: b.Torrent, Magnet: magnet, EpisodeNumber: b.EpisodeNumber, AbsoluteOffset: absoluteOffset, Media: b.Media, }) if err != nil { return h.RespondWithError(c, err) } return h.RespondWithData(c, files) } // HandleTorrentstreamStartStream // // @summary starts a torrent stream. // @desc This starts the entire streaming process. // @returns bool // @route /api/v1/torrentstream/start [POST] func (h *Handler) HandleTorrentstreamStartStream(c echo.Context) error { type body struct { MediaId int `json:"mediaId"` EpisodeNumber int `json:"episodeNumber"` AniDBEpisode string `json:"aniDBEpisode"` AutoSelect bool `json:"autoSelect"` Torrent *hibiketorrent.AnimeTorrent `json:"torrent,omitempty"` // Nil if autoSelect is true FileIndex *int `json:"fileIndex,omitempty"` PlaybackType torrentstream.PlaybackType `json:"playbackType"` // "default" or "externalPlayerLink" ClientId string `json:"clientId"` } var b body if err := c.Bind(&b); err != nil { return h.RespondWithError(c, err) } userAgent := c.Request().Header.Get("User-Agent") err := h.App.TorrentstreamRepository.StartStream(c.Request().Context(), &torrentstream.StartStreamOptions{ MediaId: b.MediaId, EpisodeNumber: b.EpisodeNumber, AniDBEpisode: b.AniDBEpisode, AutoSelect: b.AutoSelect, Torrent: b.Torrent, FileIndex: b.FileIndex, UserAgent: userAgent, ClientId: b.ClientId, PlaybackType: b.PlaybackType, }) if err != nil { return h.RespondWithError(c, err) } return h.RespondWithData(c, true) } // HandleTorrentstreamStopStream // // @summary stop a torrent stream. // @desc This stops the entire streaming process and drops the torrent if it's below a threshold. // @desc This is made to be used while the stream is running. // @returns bool // @route /api/v1/torrentstream/stop [POST] func (h *Handler) HandleTorrentstreamStopStream(c echo.Context) error { err := h.App.TorrentstreamRepository.StopStream() if err != nil { return h.RespondWithError(c, err) } return h.RespondWithData(c, true) } // HandleTorrentstreamDropTorrent // // @summary drops a torrent stream. // @desc This stops the entire streaming process and drops the torrent completely. // @desc This is made to be used to force drop a torrent. // @returns bool // @route /api/v1/torrentstream/drop [POST] func (h *Handler) HandleTorrentstreamDropTorrent(c echo.Context) error { err := h.App.TorrentstreamRepository.DropTorrent() if err != nil { return h.RespondWithError(c, err) } return h.RespondWithData(c, true) } // HandleGetTorrentstreamBatchHistory // // @summary returns the most recent batch selected. // @desc This returns the most recent batch selected. // @returns torrentstream.BatchHistoryResponse // @route /api/v1/torrentstream/batch-history [POST] func (h *Handler) HandleGetTorrentstreamBatchHistory(c echo.Context) error { type body struct { MediaID int `json:"mediaId"` } var b body if err := c.Bind(&b); err != nil { return h.RespondWithError(c, err) } ret := h.App.TorrentstreamRepository.GetBatchHistory(b.MediaID) return h.RespondWithData(c, ret) } // route /api/v1/torrentstream/stream/* func (h *Handler) HandleTorrentstreamServeStream(c echo.Context) error { h.App.TorrentstreamRepository.HTTPStreamHandler().ServeHTTP(c.Response().Writer, c.Request()) return nil }