package qbittorrent import ( "context" "encoding/json" "errors" "fmt" "net/http" "net/url" "strings" ) type RSS interface { // AddFolder create new folder for rss, full path of added folder such as "The Pirate Bay\Top100" AddFolder(ctx context.Context, path string) error // AddFeed add feed AddFeed(ctx context.Context, option *RssAddFeedOption) error // RemoveItem remove folder or feed RemoveItem(ctx context.Context, path string) error // MoveItem move or rename folder or feed MoveItem(ctx context.Context, srcPath, destPath string) error // GetItems list all items, if withData is true, will return all data GetItems(ctx context.Context, withData bool) (map[string]interface{}, error) // MarkAsRead if articleId is provided only the article is marked as read otherwise the whole feed // is going to be marked as read. MarkAsRead(ctx context.Context, option *RssMarkAsReadOption) error // RefreshItem refresh folder or feed RefreshItem(ctx context.Context, itemPath string) error // SetAutoDownloadingRule set auto-downloading rule SetAutoDownloadingRule(ctx context.Context, ruleName string, ruleDef *RssAutoDownloadingRuleDef) error // RenameAutoDownloadingRule rename auto-downloading rule RenameAutoDownloadingRule(ctx context.Context, ruleName, newRuleName string) error // RemoveAutoDownloadingRule remove auto-downloading rule RemoveAutoDownloadingRule(ctx context.Context, ruleName string) error // GetAllAutoDownloadingRules get all auto-downloading rules GetAllAutoDownloadingRules(ctx context.Context) (map[string]*RssAutoDownloadingRuleDef, error) // GetAllArticlesMatchingRule get all articles matching a rule GetAllArticlesMatchingRule(ctx context.Context, ruleName string) (map[string][]string, error) } type RssAddFeedOption struct { // URL feed of rss such as http://thepiratebay.org/rss//top100/200 URL string `schema:"url"` // Folder full path of added folder, optional Folder string `schema:"path,omitempty"` } type RssMarkAsReadOption struct { // ItemPath current full path of item ItemPath string `schema:"itemPath"` // ArticleId id of article, optional ArticleId string `schema:"articleId,omitempty"` } type RssAutoDownloadingRuleDefTorrentParams struct { Category string `json:"category,omitempty"` DownloadLimit int `json:"download_limit,omitempty"` DownloadPath int `json:"download_path,omitempty"` InactiveSeedingTimeLimit int `json:"inactive_seeding_time_limit,omitempty"` OperatingMode string `json:"operating_mode,omitempty"` RatioLimit int `json:"ratio_limit,omitempty"` SavePath string `json:"save_path,omitempty"` SeedingTimeLimit int `json:"seeding_time_limit,omitempty"` SkipChecking bool `json:"skip_checking,omitempty"` Tags []string `json:"tags,omitempty"` UploadLimit int `json:"upload_limit,omitempty"` Stopped bool `json:"stopped,omitempty"` UseAutoTMM bool `json:"use_auto_tmm,omitempty"` } type RssAutoDownloadingRuleDef struct { AddPaused bool `json:"addPaused,omitempty"` AffectedFeeds []string `json:"affectedFeeds,omitempty"` AssignedCategory string `json:"assignedCategory,omitempty"` Enabled bool `json:"enabled,omitempty"` EpisodeFilter string `json:"episodeFilter,omitempty"` IgnoreDays int `json:"ignoreDays,omitempty"` LastMatch string `json:"lastMatch,omitempty"` MustContain string `json:"mustContain,omitempty"` MustNotContain string `json:"mustNotContain,omitempty"` PreviouslyMatchedEpisodes []string `json:"previouslyMatchedEpisodes,omitempty"` Priority int `json:"priority,omitempty"` SavePath string `json:"savePath,omitempty"` SmartFilter bool `json:"smartFilter,omitempty"` TorrentParams *RssAutoDownloadingRuleDefTorrentParams `json:"torrentParams,omitempty"` UseRegex bool `json:"useRegex,omitempty"` } func (c *client) AddFolder(ctx context.Context, path string) error { ctx, span := trace.Start(ctx, "qbittorrent.RSS.AddFolder") defer span.End() var formData = url.Values{} formData.Add("path", path) var apiUrl = fmt.Sprintf("%s/api/v2/rss/addFolder", c.config.Address) result, err := c.doRequest(ctx, &requestData{ url: apiUrl, method: http.MethodPost, body: strings.NewReader(formData.Encode()), }) if err != nil { return err } if result.code != 200 { return errors.New("add rss folder failed: " + string(result.body)) } return nil } func (c *client) AddFeed(ctx context.Context, opt *RssAddFeedOption) error { ctx, span := trace.Start(ctx, "qbittorrent.RSS.AddFeed") defer span.End() var formData = url.Values{} err := encoder.Encode(opt, formData) if err != nil { return err } var apiUrl = fmt.Sprintf("%s/api/v2/rss/addFolder", c.config.Address) result, err := c.doRequest(ctx, &requestData{ url: apiUrl, method: http.MethodPost, body: strings.NewReader(formData.Encode()), }) if err != nil { return err } if result.code != 200 { return errors.New("add rss feed failed: " + string(result.body)) } return nil } func (c *client) RemoveItem(ctx context.Context, path string) error { ctx, span := trace.Start(ctx, "qbittorrent.RSS.RemoveItem") defer span.End() var formData = url.Values{} formData.Add("path", path) var apiUrl = fmt.Sprintf("%s/api/v2/rss/removeItem", c.config.Address) result, err := c.doRequest(ctx, &requestData{ url: apiUrl, method: http.MethodPost, body: strings.NewReader(formData.Encode()), }) if err != nil { return err } if result.code != 200 { return errors.New("remove rss item failed: " + string(result.body)) } return nil } func (c *client) MoveItem(ctx context.Context, srcPath, destPath string) error { ctx, span := trace.Start(ctx, "qbittorrent.RSS.MoveItem") defer span.End() var formData = url.Values{} formData.Add("itemPath", srcPath) formData.Add("destPath", destPath) var apiUrl = fmt.Sprintf("%s/api/v2/rss/moveItem", c.config.Address) result, err := c.doRequest(ctx, &requestData{ url: apiUrl, method: http.MethodPost, body: strings.NewReader(formData.Encode()), }) if err != nil { return err } if result.code != 200 { return errors.New("move rss item failed: " + string(result.body)) } return nil } func (c *client) GetItems(ctx context.Context, withData bool) (map[string]interface{}, error) { ctx, span := trace.Start(ctx, "qbittorrent.RSS.GetItems") defer span.End() var apiUrl = fmt.Sprintf("%s/api/v2/rss/items?withData=%t", c.config.Address, withData) result, err := c.doRequest(ctx, &requestData{ url: apiUrl, method: http.MethodGet, }) if err != nil { return nil, err } if result.code != 200 { return nil, errors.New("get rss items failed: " + string(result.body)) } var data = make(map[string]interface{}) err = json.Unmarshal(result.body, &data) return data, err } func (c *client) MarkAsRead(ctx context.Context, opt *RssMarkAsReadOption) error { ctx, span := trace.Start(ctx, "qbittorrent.RSS.MarkAsRead") defer span.End() var formData = url.Values{} err := encoder.Encode(opt, formData) if err != nil { return err } var apiUrl = fmt.Sprintf("%s/api/v2/rss/markAsRead", c.config.Address) result, err := c.doRequest(ctx, &requestData{ url: apiUrl, method: http.MethodPost, body: strings.NewReader(formData.Encode()), }) if err != nil { return err } if result.code != 200 { return errors.New("mark as read rss item failed: " + string(result.body)) } return nil } func (c *client) RefreshItem(ctx context.Context, itemPath string) error { ctx, span := trace.Start(ctx, "qbittorrent.RSS.RefreshItem") defer span.End() var formData = url.Values{} formData.Add("itemPath", itemPath) var apiUrl = fmt.Sprintf("%s/api/v2/rss/refreshItem", c.config.Address) result, err := c.doRequest(ctx, &requestData{ url: apiUrl, method: http.MethodPost, body: strings.NewReader(formData.Encode()), }) if err != nil { return err } if result.code != 200 { return errors.New("refresh rss item failed: " + string(result.body)) } return nil } func (c *client) SetAutoDownloadingRule(ctx context.Context, ruleName string, ruleDef *RssAutoDownloadingRuleDef) error { ctx, span := trace.Start(ctx, "qbittorrent.RSS.SetAutoDownloadingRule") defer span.End() var formData = url.Values{} formData.Add("ruleName", ruleName) ruleDefBytes, err := json.Marshal(ruleDef) if err != nil { return err } formData.Add("ruleDef", string(ruleDefBytes)) var apiUrl = fmt.Sprintf("%s/api/v2/rss/setRule", c.config.Address) result, err := c.doRequest(ctx, &requestData{ url: apiUrl, method: http.MethodPost, body: strings.NewReader(formData.Encode()), }) if err != nil { return err } if result.code != 200 { return errors.New("set auto downloading rule failed: " + string(result.body)) } return nil } func (c *client) RenameAutoDownloadingRule(ctx context.Context, ruleName, newRuleName string) error { ctx, span := trace.Start(ctx, "qbittorrent.RSS.RenameAutoDownloadingRule") defer span.End() var formData = url.Values{} formData.Add("ruleName", ruleName) formData.Add("newRuleName", newRuleName) var apiUrl = fmt.Sprintf("%s/api/v2/rss/renameRule", c.config.Address) result, err := c.doRequest(ctx, &requestData{ url: apiUrl, method: http.MethodPost, body: strings.NewReader(formData.Encode()), }) if err != nil { return err } if result.code != 200 { return errors.New("rename auto downloading rule failed: " + string(result.body)) } return nil } func (c *client) RemoveAutoDownloadingRule(ctx context.Context, ruleName string) error { ctx, span := trace.Start(ctx, "qbittorrent.RSS.RemoveAutoDownloadingRule") defer span.End() var formData = url.Values{} formData.Add("ruleName", ruleName) var apiUrl = fmt.Sprintf("%s/api/v2/rss/removeRule", c.config.Address) result, err := c.doRequest(ctx, &requestData{ url: apiUrl, method: http.MethodPost, body: strings.NewReader(formData.Encode()), }) if err != nil { return err } if result.code != 200 { return errors.New("remove auto downloading rule failed: " + string(result.body)) } return nil } func (c *client) GetAllAutoDownloadingRules(ctx context.Context) (map[string]*RssAutoDownloadingRuleDef, error) { ctx, span := trace.Start(ctx, "qbittorrent.RSS.GetAllAutoDownloadingRules") defer span.End() var apiUrl = fmt.Sprintf("%s/api/v2/rss/matchingArticles", c.config.Address) result, err := c.doRequest(ctx, &requestData{ url: apiUrl, }) if err != nil { return nil, err } if result.code != 200 { return nil, errors.New("get rss rules failed: " + string(result.body)) } var data = make(map[string]*RssAutoDownloadingRuleDef) err = json.Unmarshal(result.body, &data) return data, err } func (c *client) GetAllArticlesMatchingRule(ctx context.Context, ruleName string) (map[string][]string, error) { ctx, span := trace.Start(ctx, "qbittorrent.RSS.GetAllArticlesMatchingRule") defer span.End() var formData = url.Values{} formData.Add("ruleName", ruleName) var apiUrl = fmt.Sprintf("%s/api/v2/rss/matchingArticles?%s", c.config.Address, formData.Encode()) result, err := c.doRequest(ctx, &requestData{ url: apiUrl, }) if err != nil { return nil, err } if result.code != 200 { return nil, errors.New("get rss rule match articles failed: " + string(result.body)) } var data = make(map[string][]string) err = json.Unmarshal(result.body, &data) return data, err }