package handlers import ( "errors" "path/filepath" "seanime/internal/api/anilist" "seanime/internal/api/metadata" "seanime/internal/database/models" debrid_client "seanime/internal/debrid/client" "seanime/internal/debrid/debrid" "seanime/internal/events" hibiketorrent "seanime/internal/extension/hibike/torrent" "github.com/labstack/echo/v4" ) // HandleGetDebridSettings // // @summary get debrid settings. // @desc This returns the debrid settings. // @returns models.DebridSettings // @route /api/v1/debrid/settings [GET] func (h *Handler) HandleGetDebridSettings(c echo.Context) error { debridSettings, found := h.App.Database.GetDebridSettings() if !found { return h.RespondWithError(c, errors.New("debrid settings not found")) } return h.RespondWithData(c, debridSettings) } // HandleSaveDebridSettings // // @summary save debrid settings. // @desc This saves the debrid settings. // @desc The client should refetch the server status. // @returns models.DebridSettings // @route /api/v1/debrid/settings [PATCH] func (h *Handler) HandleSaveDebridSettings(c echo.Context) error { type body struct { Settings models.DebridSettings `json:"settings"` } var b body if err := c.Bind(&b); err != nil { return h.RespondWithError(c, err) } settings, err := h.App.Database.UpsertDebridSettings(&b.Settings) if err != nil { return h.RespondWithError(c, err) } h.App.InitOrRefreshDebridSettings() return h.RespondWithData(c, settings) } // HandleDebridAddTorrents // // @summary add torrent to debrid. // @desc This adds a torrent to the debrid service. // @returns bool // @route /api/v1/debrid/torrents [POST] func (h *Handler) HandleDebridAddTorrents(c echo.Context) error { type body struct { Torrents []hibiketorrent.AnimeTorrent `json:"torrents"` Media *anilist.BaseAnime `json:"media"` Destination string `json:"destination"` } var b body if err := c.Bind(&b); err != nil { return h.RespondWithError(c, err) } if !h.App.DebridClientRepository.HasProvider() { return h.RespondWithError(c, errors.New("debrid provider not set")) } for _, torrent := range b.Torrents { // Get the torrent's provider extension animeTorrentProviderExtension, ok := h.App.TorrentRepository.GetAnimeProviderExtension(torrent.Provider) if !ok { return h.RespondWithError(c, errors.New("provider extension not found for torrent")) } magnet, err := animeTorrentProviderExtension.GetProvider().GetTorrentMagnetLink(&torrent) if err != nil { if len(b.Torrents) == 1 { return h.RespondWithError(c, err) } else { h.App.Logger.Err(err).Msg("debrid: Failed to get magnet link") h.App.WSEventManager.SendEvent(events.ErrorToast, err.Error()) continue } } torrent.MagnetLink = magnet // Add the torrent to the debrid service _, err = h.App.DebridClientRepository.AddAndQueueTorrent(debrid.AddTorrentOptions{ MagnetLink: magnet, SelectFileId: "all", }, b.Destination, b.Media.ID) if err != nil { // If there is only one torrent, return the error if len(b.Torrents) == 1 { return h.RespondWithError(c, err) } else { // If there are multiple torrents, send an error toast and continue to the next torrent h.App.Logger.Err(err).Msg("debrid: Failed to add torrent to debrid") h.App.WSEventManager.SendEvent(events.ErrorToast, err.Error()) continue } } } return h.RespondWithData(c, true) } // HandleDebridDownloadTorrent // // @summary download torrent from debrid. // @desc Manually downloads a torrent from the debrid service locally. // @returns bool // @route /api/v1/debrid/torrents/download [POST] func (h *Handler) HandleDebridDownloadTorrent(c echo.Context) error { type body struct { TorrentItem debrid.TorrentItem `json:"torrentItem"` Destination string `json:"destination"` } var b body if err := c.Bind(&b); err != nil { return h.RespondWithError(c, err) } if !filepath.IsAbs(b.Destination) { return h.RespondWithError(c, errors.New("destination must be an absolute path")) } // Remove the torrent from the database // This is done so that the torrent is not downloaded automatically // We ignore the error here because the torrent might not be in the database _ = h.App.Database.DeleteDebridTorrentItemByTorrentItemId(b.TorrentItem.ID) // Download the torrent locally err := h.App.DebridClientRepository.DownloadTorrent(b.TorrentItem, b.Destination) if err != nil { return h.RespondWithError(c, err) } return h.RespondWithData(c, true) } // HandleDebridCancelDownload // // @summary cancel download from debrid. // @desc This cancels a download from the debrid service. // @returns bool // @route /api/v1/debrid/torrents/cancel [POST] func (h *Handler) HandleDebridCancelDownload(c echo.Context) error { type body struct { ItemID string `json:"itemID"` } var b body if err := c.Bind(&b); err != nil { return h.RespondWithError(c, err) } err := h.App.DebridClientRepository.CancelDownload(b.ItemID) if err != nil { return h.RespondWithError(c, err) } return h.RespondWithData(c, true) } // HandleDebridDeleteTorrent // // @summary remove torrent from debrid. // @desc This removes a torrent from the debrid service. // @returns bool // @route /api/v1/debrid/torrent [DELETE] func (h *Handler) HandleDebridDeleteTorrent(c echo.Context) error { type body struct { TorrentItem debrid.TorrentItem `json:"torrentItem"` } var b body if err := c.Bind(&b); err != nil { return h.RespondWithError(c, err) } provider, err := h.App.DebridClientRepository.GetProvider() if err != nil { return h.RespondWithError(c, err) } err = provider.DeleteTorrent(b.TorrentItem.ID) if err != nil { return h.RespondWithError(c, err) } return h.RespondWithData(c, true) } // HandleDebridGetTorrents // // @summary get torrents from debrid. // @desc This gets the torrents from the debrid service. // @returns []debrid.TorrentItem // @route /api/v1/debrid/torrents [GET] func (h *Handler) HandleDebridGetTorrents(c echo.Context) error { provider, err := h.App.DebridClientRepository.GetProvider() if err != nil { return h.RespondWithError(c, err) } torrents, err := provider.GetTorrents() if err != nil { h.App.Logger.Err(err).Msg("debrid: Failed to get torrents") return h.RespondWithError(c, err) } return h.RespondWithData(c, torrents) } // HandleDebridGetTorrentInfo // // @summary get torrent info from debrid. // @desc This gets the torrent info from the debrid service. // @returns debrid.TorrentInfo // @route /api/v1/debrid/torrents/info [POST] func (h *Handler) HandleDebridGetTorrentInfo(c echo.Context) error { type body struct { Torrent hibiketorrent.AnimeTorrent `json:"torrent"` } var b body if err := c.Bind(&b); err != nil { return h.RespondWithError(c, err) } animeTorrentProviderExtension, ok := h.App.TorrentRepository.GetAnimeProviderExtension(b.Torrent.Provider) if !ok { return h.RespondWithError(c, errors.New("provider extension not found for torrent")) } magnet, err := animeTorrentProviderExtension.GetProvider().GetTorrentMagnetLink(&b.Torrent) if err != nil { return h.RespondWithError(c, err) } b.Torrent.MagnetLink = magnet torrentInfo, err := h.App.DebridClientRepository.GetTorrentInfo(debrid.GetTorrentInfoOptions{ MagnetLink: b.Torrent.MagnetLink, InfoHash: b.Torrent.InfoHash, }) if err != nil { return h.RespondWithError(c, err) } return h.RespondWithData(c, torrentInfo) } // HandleDebridGetTorrentFilePreviews // // @summary get list of torrent files // @returns []debrid_client.FilePreview // @route /api/v1/debrid/torrents/file-previews [POST] func (h *Handler) HandleDebridGetTorrentFilePreviews(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) } animeTorrentProviderExtension, ok := h.App.TorrentRepository.GetAnimeProviderExtension(b.Torrent.Provider) if !ok { return h.RespondWithError(c, errors.New("provider extension not found for torrent")) } magnet, err := animeTorrentProviderExtension.GetProvider().GetTorrentMagnetLink(b.Torrent) if err != nil { return h.RespondWithError(c, err) } b.Torrent.MagnetLink = magnet // Get the media animeMetadata, _ := h.App.MetadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, b.Media.ID) absoluteOffset := 0 if animeMetadata != nil { absoluteOffset = animeMetadata.GetOffset() } torrentInfo, err := h.App.DebridClientRepository.GetTorrentFilePreviewsFromManualSelection(&debrid_client.GetTorrentFilePreviewsOptions{ Torrent: b.Torrent, Magnet: magnet, EpisodeNumber: b.EpisodeNumber, Media: b.Media, AbsoluteOffset: absoluteOffset, }) if err != nil { return h.RespondWithError(c, err) } return h.RespondWithData(c, torrentInfo) } // HandleDebridStartStream // // @summary start stream from debrid. // @desc This starts streaming a torrent from the debrid service. // @returns bool // @route /api/v1/debrid/stream/start [POST] func (h *Handler) HandleDebridStartStream(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"` FileId string `json:"fileId"` FileIndex *int `json:"fileIndex"` PlaybackType debrid_client.StreamPlaybackType `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") if b.Torrent != nil { animeTorrentProviderExtension, ok := h.App.TorrentRepository.GetAnimeProviderExtension(b.Torrent.Provider) if !ok { return h.RespondWithError(c, errors.New("provider extension not found for torrent")) } magnet, err := animeTorrentProviderExtension.GetProvider().GetTorrentMagnetLink(b.Torrent) if err != nil { return h.RespondWithError(c, err) } b.Torrent.MagnetLink = magnet } err := h.App.DebridClientRepository.StartStream(c.Request().Context(), &debrid_client.StartStreamOptions{ MediaId: b.MediaId, EpisodeNumber: b.EpisodeNumber, AniDBEpisode: b.AniDBEpisode, Torrent: b.Torrent, FileId: b.FileId, FileIndex: b.FileIndex, UserAgent: userAgent, ClientId: b.ClientId, PlaybackType: b.PlaybackType, AutoSelect: b.AutoSelect, }) if err != nil { return h.RespondWithError(c, err) } return h.RespondWithData(c, true) } // HandleDebridCancelStream // // @summary cancel stream from debrid. // @desc This cancels a stream from the debrid service. // @returns bool // @route /api/v1/debrid/stream/cancel [POST] func (h *Handler) HandleDebridCancelStream(c echo.Context) error { type body struct { Options *debrid_client.CancelStreamOptions `json:"options"` } var b body if err := c.Bind(&b); err != nil { return h.RespondWithError(c, err) } h.App.DebridClientRepository.CancelStream(b.Options) return h.RespondWithData(c, true) }